Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect } from 'react';

Check warning on line 1 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / check

'React' is defined but never used. Allowed unused vars must match /^_/u
import { BrowserRouter as Router } from 'react-router-dom';
import { ToastProvider } from './context/toastContext';
import AuthManager from './components/AuthManager/AuthManager';
import { ErrorModalProvider } from './context/errorModal';
import './styles/App.css';
import { setAuthToken } from './util/axios';

Expand All @@ -18,9 +19,11 @@

return (
<Router>
<ToastProvider>
<AuthManager />
</ToastProvider>
<ErrorModalProvider>
<ToastProvider>
<AuthManager />
</ToastProvider>
</ErrorModalProvider>
</Router>
);
};
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/AuthManager/AuthManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import LandingPage from '../../pages/Landing/Landing';
import styles from './authmanager.module.css';
import { studentLanding, car, admin } from '../../icons/other';
import SubscribeWrapper from './SubscrbeWrapper';

Check warning on line 15 in frontend/src/components/AuthManager/AuthManager.tsx

View workflow job for this annotation

GitHub Actions / check

'SubscribeWrapper' is defined but never used. Allowed unused vars must match /^_/u
import Toast from '../ConfirmationToast/ConfirmationToast';

import AdminRoutes from '../../pages/Admin/Routes';
Expand All @@ -27,6 +27,7 @@
import CryptoJS from 'crypto-js';
import axios, { setAuthToken } from '../../util/axios';
import UnregisteredUserPage from '../Onboarding/UnregisteredUserPage';
import { useErrorModal, formatErrorMessage } from '../../context/errorModal';

const secretKey = `${import.meta.env.VITE_ENCRYPTION_KEY!}`;

Expand Down Expand Up @@ -65,6 +66,7 @@
setUnregisteredUser(null);
logout();
};
const { showError } = useErrorModal();

useEffect(() => {
const token = jwtValue();
Expand Down Expand Up @@ -107,7 +109,7 @@
// sessions are same-site (e.g., local development). In production, we
// prefer the stateless JWT passed via the URL query parameter.
const handleSSOCallback = async (
event?: React.FormEvent<HTMLFormElement>

Check warning on line 112 in frontend/src/components/AuthManager/AuthManager.tsx

View workflow job for this annotation

GitHub Actions / check

'event' is defined but never used. Allowed unused args must match /^_/u
) => {
try {
const response = await fetch(
Expand Down Expand Up @@ -213,6 +215,10 @@
}
} catch (error) {
console.error('Error decrypting JWT:', error);
showError(
`Error decrypting JWT: ${formatErrorMessage(error)}`,
'Authentication Error'
);
}
return '';
}
Expand Down Expand Up @@ -255,10 +261,10 @@
setAuthToken('');
setSignedIn(false);
setRefreshUser(() => () => {});
window.location.href = `${process.env.VITE_SERVER_URL}/api/sso/logout`;
window.location.href = `${import.meta.env.VITE_SERVER_URL}/api/sso/logout`;
}

function createRefresh(userId: string, userType: string, token: string) {

Check warning on line 267 in frontend/src/components/AuthManager/AuthManager.tsx

View workflow job for this annotation

GitHub Actions / check

'token' is defined but never used. Allowed unused args must match /^_/u
let endpoint = '';

if (userType === 'Admin') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
padding: 0.75rem 1rem;
box-shadow: 4px 6px 30px 5px rgba(0, 0, 0, 0.15);
border-radius: 0.625rem;
z-index: 999;
z-index: 2000;
display: flex;
align-items: center;
}
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/EmployeeCards/EmployeeCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
const secondPart = phoneNumber.substring(6, 10);
return `${areaCode}-${firstPart}-${secondPart}`;
} else {
console.error('Undefined PhoneNumber');
return '';
}
};
Expand All @@ -39,7 +38,7 @@

// Determine if employee is admin, driver, or both
const adminEmployee = isAdmin(employee);
const driverEmployee = isDriver(employee);

Check warning on line 41 in frontend/src/components/EmployeeCards/EmployeeCards.tsx

View workflow job for this annotation

GitHub Actions / check

'driverEmployee' is assigned a value but never used. Allowed unused vars must match /^_/u
const isBoth = adminEmployee && employee.isDriver;

const roles = (): string => {
Expand Down
147 changes: 118 additions & 29 deletions frontend/src/components/EmployeeModal/EmployeeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import styles from './employeemodal.module.css';
import { useEmployees } from '../../context/EmployeesContext';
import { useToast, ToastStatus } from '../../context/toastContext';
import axios from '../../util/axios';
import { useErrorModal, formatErrorMessage } from '../../context/errorModal';
import { extractNetIdFromEmail } from 'util/userUtils';

type AdminData = {
Expand Down Expand Up @@ -77,6 +78,7 @@ const EmployeeModal = ({
setIsOpen,
}: EmployeeModalProps) => {
const { showToast } = useToast();
const { showError } = useErrorModal();
const {
updateAdminInfo,
updateDriverInfo,
Expand Down Expand Up @@ -158,6 +160,10 @@ const EmployeeModal = ({
});
} catch (error) {
console.error('Error uploading photo:', error);
showError(
`Error uploading photo: ${formatErrorMessage(error)}`,
'Employees Error'
);
throw new Error('Failed to upload employee photo. Please try again.');
}
}
Expand All @@ -183,7 +189,12 @@ const EmployeeModal = ({
break;
case '/api/admins':
// Use optimistic create from context
await createAdmin(extractAdminData(employeeData));
await createAdmin(extractAdminData(employeeData)).catch((error) => {
showError(
`Failed to create admin: ${formatErrorMessage(error)}`,
'Employees Error'
);
});
res = employeeData; // The context will handle server response and ID assignment
break;
default:
Expand Down Expand Up @@ -234,9 +245,19 @@ const EmployeeModal = ({
async function deleteEmployee(id: string, endpoint: string): Promise<void> {
// Use optimistic delete from context
if (endpoint === '/api/admins') {
await deleteAdmin(id);
await deleteAdmin(id).catch((error) => {
showError(
`Failed to delete admin: ${formatErrorMessage(error)}`,
'Employees Error'
);
});
} else if (endpoint === '/api/drivers') {
await deleteDriver(id);
await deleteDriver(id).catch((error) => {
showError(
`Failed to delete driver: ${formatErrorMessage(error)}`,
'Employees Error'
);
});
}
}

Expand All @@ -263,42 +284,98 @@ const EmployeeModal = ({
// If no employee exists, create one using a primary role.
if (!currentId || currentId === '') {
if (hasAdmin) {
employeeData.id = (
await createEmployee(employeeData, '/api/admins')
).id;
showToast(
`Created a new employee with the admin role`,
ToastStatus.SUCCESS
);
try {
employeeData.id = (
await createEmployee(employeeData, '/api/admins')
).id;
showToast(
`Created a new employee with the admin role`,
ToastStatus.SUCCESS
);
} catch (error) {
showError(
`Failed to create admin: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
}
if (hasDriver) {
employeeData.id = (
await createEmployee(employeeData, '/api/drivers')
).id;
showToast(
`Created a new employee with the driver role`,
ToastStatus.SUCCESS
);
try {
employeeData.id = (
await createEmployee(employeeData, '/api/drivers')
).id;
showToast(
`Created a new employee with the driver role`,
ToastStatus.SUCCESS
);
} catch (error) {
showError(
`Failed to create driver: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
}
} else {
if (hasAdmin) {
if (employeeData.admin) {
await updateEmployee(employeeData, '/api/admins');
try {
await updateEmployee(employeeData, '/api/admins');
} catch (error) {
showError(
`Failed to update admin: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
} else {
await createEmployee(employeeData, '/api/admins');
try {
await createEmployee(employeeData, '/api/admins');
} catch (error) {
showError(
`Failed to create admin: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
}
} else if (employeeData.admin) {
await deleteEmployee(employeeData.id, '/api/admins');
try {
await deleteEmployee(employeeData.id, '/api/admins');
} catch (error) {
showError(
`Failed to delete admin: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
}

if (hasDriver) {
if (employeeData.driver) {
await updateEmployee(employeeData, '/api/drivers');
try {
await updateEmployee(employeeData, '/api/drivers');
} catch (error) {
showError(
`Failed to update driver: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
} else {
await createEmployee(employeeData, '/api/drivers');
try {
await createEmployee(employeeData, '/api/drivers');
} catch (error) {
showError(
`Failed to create driver: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
}
} else if (employeeData.driver) {
await deleteEmployee(employeeData.id, '/api/drivers');
try {
await deleteEmployee(employeeData.id, '/api/drivers');
} catch (error) {
showError(
`Failed to delete driver: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
}
}
const id = employeeData.id;
Expand Down Expand Up @@ -365,11 +442,20 @@ const EmployeeModal = ({
: 'Drivers';
try {
setIsUploadingImage(true);
await uploadEmployeePhoto(id, targetTable, imageBase64);
await uploadEmployeePhoto(id, targetTable, imageBase64).catch(
(error) => {
showError(
`Failed to upload photo: ${formatErrorMessage(error)}`,
'Employees Error'
);
}
);
} catch (uploadError) {
showToast(
'Employee created but photo upload failed. You can try uploading the photo again later.',
ToastStatus.ERROR
showError(
`Employee created but photo upload failed: ${formatErrorMessage(
uploadError
)}. You can try uploading the photo again later.`,
'Employees Error'
);
// Don't throw here - we want the employee creation to succeed even if photo upload fails
} finally {
Expand All @@ -378,9 +464,12 @@ const EmployeeModal = ({
}

// Note: No need to manually refresh - optimistic updates handle this automatically
showToast(`Employee information processed`, ToastStatus.SUCCESS);
showToast('Employee information processed', ToastStatus.SUCCESS);
} catch (error) {
showToast('An error occurred: ', ToastStatus.ERROR);
showError(
`An error occurred while saving employee: ${formatErrorMessage(error)}`,
'Employees Error'
);
} finally {
closeModal();
}
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/components/ExportButton/ExportButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Button } from '../FormElements/FormElements';
import styles from './exportButton.module.css';
import { ToastStatus, useToast } from '../../context/toastContext';
import axios from '../../util/axios';
import { formatErrorMessage } from '../../context/errorModal';
import { useErrorModal } from '../../context/errorModal';

type clickHandler = {
toastMsg: string;
Expand All @@ -21,6 +23,7 @@ const ExportButton = ({
}: clickHandler) => {
const [downloadData, setDownloadData] = useState<string>('');
const { showToast } = useToast();
const { showError } = useErrorModal();
const csvLink = useRef<
CSVLink & HTMLAnchorElement & { link: HTMLAnchorElement }
>(null);
Expand All @@ -42,7 +45,13 @@ const ExportButton = ({
csvLink.current.link.click();
}
})
.then(() => showToast(toastMsg, ToastStatus.SUCCESS));
.then(() => showToast(toastMsg, ToastStatus.SUCCESS))
.catch((error) => {
showError(
`Failed to download data: ${formatErrorMessage(error)}`,
'Export Error'
);
});
};

return (
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/components/Locations/LocationFormModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { Tag } from 'types';
import { LocationType } from '@carriage-web/shared/types/location';
import styles from './locations.module.css';
import LocationImagesUpload, { LocationImage } from './LocationImagesUpload';
import { useErrorModal, formatErrorMessage } from '../../context/errorModal';
import { useToast, ToastStatus } from '../../context/toastContext';

const CAMPUS_OPTIONS = [
{ value: Tag.NORTH, label: 'North Campus' },
Expand Down Expand Up @@ -68,6 +70,8 @@ export const LocationFormModal: React.FC<Props> = ({
const [loadingAddr, setLoadingAddr] = useState(false);
const [error, setError] = useState<string | null>(null);
const [locationImages, setLocationImages] = useState<LocationImage[]>([]);
const { showError } = useErrorModal();
const { showToast } = useToast();

useEffect(() => {
if (!open) return;
Expand Down Expand Up @@ -99,6 +103,10 @@ export const LocationFormModal: React.FC<Props> = ({
} catch (e) {
setError("Couldn't retrieve address for this location");
console.error(e);
showError(
`Couldn't retrieve address for this location: ${formatErrorMessage(e)}`,
'Locations Error'
);
} finally {
setLoadingAddr(false);
}
Expand All @@ -115,6 +123,10 @@ export const LocationFormModal: React.FC<Props> = ({
} catch (e) {
setError("Couldn't find coordinates for this address");
console.error(e);
showError(
`Couldn't find coordinates for this address: ${formatErrorMessage(e)}`,
'Locations Error'
);
} finally {
setLoadingAddr(false);
}
Expand Down Expand Up @@ -148,11 +160,18 @@ export const LocationFormModal: React.FC<Props> = ({
imagesList: locationImages,
};
onSubmit(updatedLocation);
showToast('Location saved successfully', ToastStatus.SUCCESS);
onClose();
};

return (
<Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth>
<Dialog
open={open}
onClose={onClose}
maxWidth="lg"
fullWidth
disableEnforceFocus
>
<DialogTitle>
{mode === 'add' ? 'Add New Location' : 'Edit Location'}
</DialogTitle>
Expand Down
Loading
Loading