diff --git a/apps/frontend/src/components/floatingAlert.tsx b/apps/frontend/src/components/floatingAlert.tsx new file mode 100644 index 000000000..0257b0b36 --- /dev/null +++ b/apps/frontend/src/components/floatingAlert.tsx @@ -0,0 +1,57 @@ +import { Alert } from '@chakra-ui/react'; +import { useEffect, useState } from 'react'; + +type FloatingAlertProps = { + message?: string | null; + status?: 'info' | 'error'; + timeout?: number; +}; + +export function FloatingAlert({ + message, + status, + timeout, +}: FloatingAlertProps) { + const [visible, setVisible] = useState(!!message); + + useEffect(() => { + if (!message) { + setVisible(false); + return; + } + + setVisible(true); + + if (!timeout) return; + + const timer = setTimeout(() => { + setVisible(false); + }, timeout); + + return () => clearTimeout(timer); + }, [message, timeout]); + + if (!message || !visible) return null; + + return ( + + + + {message} + + + ); +} diff --git a/apps/frontend/src/components/forms/donationDetailsModal.tsx b/apps/frontend/src/components/forms/donationDetailsModal.tsx index e33792c73..389fd2537 100644 --- a/apps/frontend/src/components/forms/donationDetailsModal.tsx +++ b/apps/frontend/src/components/forms/donationDetailsModal.tsx @@ -10,6 +10,7 @@ import { import ApiClient from '@api/apiClient'; import { Donation, DonationItem, FoodType } from 'types/types'; import { formatDate } from '@utils/utils'; +import { FloatingAlert } from '@components/floatingAlert'; interface DonationDetailsModalProps { donation: Donation; @@ -24,6 +25,8 @@ const DonationDetailsModal: React.FC = ({ }) => { const [items, setItems] = useState([]); + const [alertMessage, setAlertMessage] = useState(''); + const donationId = donation.donationId; useEffect(() => { @@ -37,7 +40,7 @@ const DonationDetailsModal: React.FC = ({ setItems(itemsData); } catch (err) { - alert('Error fetching donation details: ' + err); + setAlertMessage('Error fetching donation details: ' + err); } }; @@ -60,6 +63,9 @@ const DonationDetailsModal: React.FC = ({ closeOnInteractOutside scrollBehavior="inside" > + {alertMessage && ( + + )} diff --git a/apps/frontend/src/components/forms/orderDetailsModal.tsx b/apps/frontend/src/components/forms/orderDetailsModal.tsx index 309e15998..ce5e22f11 100644 --- a/apps/frontend/src/components/forms/orderDetailsModal.tsx +++ b/apps/frontend/src/components/forms/orderDetailsModal.tsx @@ -3,6 +3,7 @@ import { Text, Dialog, CloseButton, Textarea, Field } from '@chakra-ui/react'; import ApiClient from '@api/apiClient'; import { FoodRequest, OrderSummary } from 'types/types'; import { formatDate } from '@utils/utils'; +import { FloatingAlert } from '@components/floatingAlert'; import { TagGroup } from './tagGroup'; interface OrderDetailsModalProps { @@ -18,6 +19,8 @@ const OrderDetailsModal: React.FC = ({ }) => { const [foodRequest, setFoodRequest] = useState(null); + const [alertMessage, setAlertMessage] = useState(''); + useEffect(() => { if (isOpen) { const fetchData = async () => { @@ -27,7 +30,7 @@ const OrderDetailsModal: React.FC = ({ ); setFoodRequest(foodRequestData); } catch (error) { - alert('Error fetching food request details:' + error); + setAlertMessage('Error fetching food request details:' + error); } }; @@ -44,6 +47,9 @@ const OrderDetailsModal: React.FC = ({ }} closeOnInteractOutside > + {alertMessage && ( + + )} diff --git a/apps/frontend/src/components/forms/requestFormModal.tsx b/apps/frontend/src/components/forms/requestFormModal.tsx index 04d8cfb67..4d27bfdea 100644 --- a/apps/frontend/src/components/forms/requestFormModal.tsx +++ b/apps/frontend/src/components/forms/requestFormModal.tsx @@ -17,6 +17,7 @@ import { RequestSize, } from '../../types/types'; import { ChevronDownIcon } from 'lucide-react'; +import { FloatingAlert } from '@components/floatingAlert'; import apiClient from '@api/apiClient'; import { TagGroup } from './tagGroup'; @@ -38,8 +39,13 @@ const FoodRequestFormModal: React.FC = ({ const [selectedItems, setSelectedItems] = useState([]); const [requestedSize, setRequestedSize] = useState(''); const [additionalNotes, setAdditionalNotes] = useState(''); - - const [alertMessage, setAlertMessage] = useState(''); + const [alert, setAlert] = useState<{ + isError: boolean; + message: string; + }>({ + isError: true, + message: '', + }); const isFormValid = requestedSize !== '' && selectedItems.length > 0; @@ -67,10 +73,11 @@ const FoodRequestFormModal: React.FC = ({ try { await apiClient.createFoodRequest(foodRequestData); + setAlert({ isError: false, message: 'Request Submitted' }); onClose(); onSuccess(); } catch { - setAlertMessage('Failed to submit food request'); + setAlert({ isError: true, message: 'Request could not be submitted.' }); } }; @@ -83,10 +90,11 @@ const FoodRequestFormModal: React.FC = ({ }} closeOnInteractOutside > - {alertMessage && ( - // TODO: add Justin's alert component/uncomment below out and remove text component - // - {alertMessage} + {alert.message && alert.isError && ( + + )} + {alert.message && !alert.isError && ( + )} @@ -263,7 +271,10 @@ const FoodRequestFormModal: React.FC = ({ if (words.length <= 250) { setAdditionalNotes(e.target.value); } else { - alert('Exceeded word limit'); + setAlert({ + isError: true, + message: 'Exceeded word limit', + }); } }} /> diff --git a/apps/frontend/src/components/forms/resetPasswordModal.tsx b/apps/frontend/src/components/forms/resetPasswordModal.tsx index fa11904af..8649a9174 100644 --- a/apps/frontend/src/components/forms/resetPasswordModal.tsx +++ b/apps/frontend/src/components/forms/resetPasswordModal.tsx @@ -10,6 +10,7 @@ import { Field, } from '@chakra-ui/react'; import { resetPassword, confirmResetPassword } from 'aws-amplify/auth'; +import { FloatingAlert } from '@components/floatingAlert'; const ResetPasswordModal: React.FC = () => { const [email, setEmail] = useState(''); @@ -17,6 +18,7 @@ const ResetPasswordModal: React.FC = () => { const [step, setStep] = useState<'reset' | 'new'>('reset'); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [alertMessage, setAlertMessage] = useState(''); const navigate = useNavigate(); @@ -25,27 +27,26 @@ const ResetPasswordModal: React.FC = () => { await resetPassword({ username: email }); setStep('new'); } catch (error) { - alert(error || 'Failed to send verification code'); + setAlertMessage('Failed to send verification code: ' + error); } }; const handleResendCode = async () => { try { await resetPassword({ username: email }); - alert('Successfully sent verification code'); } catch (error) { - alert(error || 'Failed to send verification code'); + setAlertMessage('Failed to send verification code: ' + error); } }; const handleResetPassword = async () => { if (password !== confirmPassword) { - alert('Passwords need to match'); + setAlertMessage('Passwords need to match'); return; } if (password.length < 8) { - alert('Password needs to be at least 8 characters'); + setAlertMessage('Password needs to be at least 8 characters'); return; } @@ -55,10 +56,9 @@ const ResetPasswordModal: React.FC = () => { confirmationCode: code, newPassword: password, }); - alert('Password reset successful!'); navigate('/login'); } catch (error) { - alert(error || 'Failed to set new password'); + setAlertMessage('Failed to set new password: ' + error); } }; @@ -92,6 +92,9 @@ const ResetPasswordModal: React.FC = () => { borderRadius="xl" boxShadow="xl" > + {alertMessage && ( + + )} diff --git a/apps/frontend/src/containers/adminDonation.tsx b/apps/frontend/src/containers/adminDonation.tsx index c444803bb..9c9a761f3 100644 --- a/apps/frontend/src/containers/adminDonation.tsx +++ b/apps/frontend/src/containers/adminDonation.tsx @@ -15,6 +15,7 @@ import { Donation } from 'types/types'; import DonationDetailsModal from '@components/forms/donationDetailsModal'; import ApiClient from '@api/apiClient'; import { formatDate } from '@utils/utils'; +import { FloatingAlert } from '@components/floatingAlert'; const AdminDonation: React.FC = () => { const [donations, setDonations] = useState([]); @@ -28,13 +29,15 @@ const AdminDonation: React.FC = () => { null, ); + const [alertMessage, setAlertMessage] = useState(''); + useEffect(() => { const fetchDonations = async () => { try { const data = await ApiClient.getAllDonations(); setDonations(data); } catch (error) { - alert('Error fetching donations: ' + error); + setAlertMessage('Error fetching donations: ' + error); } }; fetchDonations(); @@ -99,6 +102,9 @@ const AdminDonation: React.FC = () => { Donation Management + {alertMessage && ( + + )}