Skip to content

Commit 93a501a

Browse files
committed
fix: Add user-facing error notifications for failed operations
Add toast notifications using react-hot-toast across the application to inform users when operations fail. This addresses issue #13 which identified that multiple components catch errors without displaying user-facing notifications. Components updated: - SettingsPanel: Shows toast on company info fetch failure - TeamList: Shows toast on employee data retrieval failure - TimeEntryModal: Shows toast on save/delete failures, success on completion - AuthProvider: Shows toast on login/logout failures - tokenService: Shows toast on session expiry and auth failures - timeEntryService: Shows toast on sync success/failure with entry counts Closes #13
1 parent 5e34996 commit 93a501a

6 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/components/settings/SettingsPanel.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState, useEffect } from 'react';
4+
import toast from 'react-hot-toast';
45
import { Card } from '@/components/ui';
56
import { bcClient } from '@/services/bc/bcClient';
67
import { useAuth } from '@/services/auth';
@@ -38,6 +39,7 @@ export function SettingsPanel() {
3839
} catch (err) {
3940
console.error('Failed to fetch company info:', err);
4041
setError('Failed to load company information');
42+
toast.error('Failed to load company information. Please try again.');
4143
} finally {
4244
setIsLoading(false);
4345
}

src/components/team/TeamList.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState, useEffect } from 'react';
4+
import toast from 'react-hot-toast';
45
import { TeamMemberCard } from './TeamMemberCard';
56
import { Card } from '@/components/ui';
67
import { bcClient } from '@/services/bc/bcClient';
@@ -51,6 +52,7 @@ export function TeamList() {
5152
} catch (err) {
5253
console.error('Failed to fetch employees:', err);
5354
setError('Failed to load team members');
55+
toast.error('Failed to load team members. Please try again.');
5456
} finally {
5557
setIsLoading(false);
5658
}

src/components/timesheet/TimeEntryModal.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState, useEffect } from 'react';
4+
import toast from 'react-hot-toast';
45
import { TrashIcon } from '@heroicons/react/24/outline';
56
import { Modal, Button, Input, Select } from '@/components/ui';
67
import { useTimeEntriesStore, useProjectsStore } from '@/hooks';
@@ -113,9 +114,11 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP
113114
isRunning: false,
114115
});
115116
}
117+
toast.success(entry ? 'Time entry updated' : 'Time entry saved');
116118
onClose();
117119
} catch (error) {
118120
console.error('Failed to save entry:', error);
121+
toast.error('Failed to save time entry. Please try again.');
119122
} finally {
120123
setIsSubmitting(false);
121124
}
@@ -131,9 +134,11 @@ export function TimeEntryModal({ isOpen, onClose, date, entry }: TimeEntryModalP
131134
setIsSubmitting(true);
132135
try {
133136
await deleteEntry(entry.id);
137+
toast.success('Time entry deleted');
134138
onClose();
135139
} catch (error) {
136140
console.error('Failed to delete entry:', error);
141+
toast.error('Failed to delete time entry. Please try again.');
137142
} finally {
138143
setIsSubmitting(false);
139144
}

src/services/auth/AuthProvider.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { ReactNode, useEffect, useState } from 'react';
4+
import toast from 'react-hot-toast';
45
import {
56
MsalProvider,
67
AuthenticatedTemplate,
@@ -80,6 +81,7 @@ export function useAuth() {
8081
await instance.loginRedirect(loginRequest);
8182
} catch (error) {
8283
console.error('Login failed:', error);
84+
toast.error('Login failed. Please try again.');
8385
throw error;
8486
}
8587
};
@@ -91,6 +93,7 @@ export function useAuth() {
9193
});
9294
} catch (error) {
9395
console.error('Logout failed:', error);
96+
toast.error('Logout failed. Please try again.');
9497
throw error;
9598
}
9699
};

src/services/auth/tokenService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PublicClientApplication, SilentRequest } from '@azure/msal-browser';
2+
import toast from 'react-hot-toast';
23
import { msalConfig, bcTokenRequest, graphTokenRequest } from './msalConfig';
34

45
let msalInstance: PublicClientApplication | null = null;
@@ -20,6 +21,7 @@ export async function getAccessToken(
2021

2122
if (!account) {
2223
console.error('No active account found');
24+
toast.error('Session expired. Please sign in again.');
2325
return null;
2426
}
2527

@@ -41,6 +43,7 @@ export async function getAccessToken(
4143
return null;
4244
} catch (redirectError) {
4345
console.error('Failed to acquire token via redirect:', redirectError);
46+
toast.error('Authentication failed. Please try signing in again.');
4447
return null;
4548
}
4649
}

src/services/bc/timeEntryService.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import toast from 'react-hot-toast';
12
import { bcClient } from './bcClient';
2-
import type { TimeEntry, BCJobJournalLine, Project, Task } from '@/types';
3+
import type { TimeEntry, BCJobJournalLine, Project } from '@/types';
34
import { format, parseISO } from 'date-fns';
45

56
// Local storage key for time entries (cached/pending sync)
@@ -147,6 +148,22 @@ export const timeEntryService = {
147148
}
148149

149150
saveLocalEntries(entries);
151+
152+
// Show user-friendly notifications for sync results
153+
if (synced > 0 && failed === 0) {
154+
toast.success(
155+
`Successfully synced ${synced} time ${synced === 1 ? 'entry' : 'entries'} to Business Central`
156+
);
157+
} else if (synced > 0 && failed > 0) {
158+
toast.error(
159+
`Synced ${synced} ${synced === 1 ? 'entry' : 'entries'}, but ${failed} failed. Please try again.`
160+
);
161+
} else if (failed > 0) {
162+
toast.error(
163+
`Failed to sync ${failed} time ${failed === 1 ? 'entry' : 'entries'}. Please try again.`
164+
);
165+
}
166+
150167
return { synced, failed };
151168
},
152169

0 commit comments

Comments
 (0)