From a54a93b724a6104213b17271fc298e37adedc1c5 Mon Sep 17 00:00:00 2001 From: Michael Hunteman Date: Fri, 20 Sep 2024 11:19:34 -0700 Subject: Use redux for updating snackbar --- client/src/ThemeContextProvider.tsx | 7 ++++ client/src/components/AdminLogin.tsx | 41 +++++++++++--------- client/src/components/GuestLogin.tsx | 41 +++++++++++--------- client/src/components/Registry.tsx | 1 - client/src/components/RsvpForm.tsx | 73 ++++++++++++++++-------------------- client/src/components/Schedule.tsx | 1 - client/src/components/Status.tsx | 36 +++++++++++------- client/src/error.ts | 3 ++ client/src/slices/auth/adminSlice.ts | 4 +- client/src/slices/auth/guestSlice.ts | 4 +- client/src/slices/snackbarSlice.ts | 32 ++++++++++++++++ client/src/store.ts | 2 + 12 files changed, 150 insertions(+), 95 deletions(-) create mode 100644 client/src/slices/snackbarSlice.ts (limited to 'client') diff --git a/client/src/ThemeContextProvider.tsx b/client/src/ThemeContextProvider.tsx index 1b2e3b4..6aedc35 100644 --- a/client/src/ThemeContextProvider.tsx +++ b/client/src/ThemeContextProvider.tsx @@ -75,6 +75,13 @@ function ThemeContextProvider({ children }: ThemeProviderProps) { }, }, }, + MuiAlert: { + defaultProps: { + sx: { + fontFamily: 'Roboto, sans-serif', + }, + }, + }, }, }), [mode] diff --git a/client/src/components/AdminLogin.tsx b/client/src/components/AdminLogin.tsx index 1881b6b..25f2063 100644 --- a/client/src/components/AdminLogin.tsx +++ b/client/src/components/AdminLogin.tsx @@ -1,18 +1,20 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useNavigate } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { Button, Paper, Snackbar, TextField, Typography } from '@mui/material'; +import { Button, Paper, TextField, Typography } from '@mui/material'; import { useForm } from 'react-hook-form'; import { setAdmin } from '../slices/auth/adminSlice'; import { useLoginAdminMutation } from '../slices/api/adminSlice'; -import type { Credentials, StatusProps } from '../models'; import Status from './Status'; +import { open } from '../slices/snackbarSlice'; +import { isFetchBaseQueryError } from '../error'; +import type { Credentials } from '../models'; +import type { Data } from '../error'; function GuestLogin() { const dispatch = useDispatch(); const navigate = useNavigate(); - const [login, { isLoading, error }] = useLoginAdminMutation(); - const [open, setOpen] = useState(false); + const [login] = useLoginAdminMutation(); const { register, @@ -29,8 +31,22 @@ function GuestLogin() { try { dispatch(setAdmin(await login(data).unwrap())); navigate('/dashboard'); - } catch (e) { - setOpen(true); + } catch (error) { + if (isFetchBaseQueryError(error)) { + dispatch( + open({ + message: (error.data as Data).message, + severity: 'error', + }) + ); + } else { + dispatch( + open({ + message: 'No response', + severity: 'error', + }) + ); + } } }; @@ -57,7 +73,6 @@ function GuestLogin() { alignItems: 'center', mt: 16, p: 2, - borderRadius: '8px', }} > Admin Login @@ -85,15 +100,7 @@ function GuestLogin() { Log in - setOpen(false)} - autoHideDuration={5000} - > -
- -
-
+ ); } diff --git a/client/src/components/GuestLogin.tsx b/client/src/components/GuestLogin.tsx index dca157a..0d3e8b1 100644 --- a/client/src/components/GuestLogin.tsx +++ b/client/src/components/GuestLogin.tsx @@ -1,18 +1,20 @@ -import React, { useState } from 'react'; +import React from 'react'; import { useNavigate } from 'react-router-dom'; import { useDispatch } from 'react-redux'; -import { Button, Paper, Snackbar, TextField, Typography } from '@mui/material'; +import { Button, Paper, TextField, Typography } from '@mui/material'; import { useForm } from 'react-hook-form'; import { setGuest } from '../slices/auth/guestSlice'; import { useLoginGuestMutation } from '../slices/api/guestSlice'; -import type { Name, StatusProps } from '../models'; import Status from './Status'; +import { open } from '../slices/snackbarSlice'; +import { isFetchBaseQueryError } from '../error'; +import type { Data } from '../error'; +import type { Name } from '../models'; function GuestLogin() { const dispatch = useDispatch(); const navigate = useNavigate(); - const [login, { isLoading, error }] = useLoginGuestMutation(); - const [open, setOpen] = useState(false); + const [login] = useLoginGuestMutation(); const { register, @@ -29,8 +31,22 @@ function GuestLogin() { try { dispatch(setGuest(await login(data).unwrap())); navigate('/rsvp'); - } catch (e) { - setOpen(true); + } catch (error) { + if (isFetchBaseQueryError(error)) { + dispatch( + open({ + message: (error.data as Data).message, + severity: 'error', + }) + ); + } else { + dispatch( + open({ + message: 'No response', + severity: 'error', + }) + ); + } } }; @@ -57,7 +73,6 @@ function GuestLogin() { alignItems: 'center', mt: 16, p: 2, - borderRadius: '8px', }} > Guest Login @@ -84,15 +99,7 @@ function GuestLogin() { - setOpen(false)} - autoHideDuration={5000} - > -
- -
-
+ ); diff --git a/client/src/components/Registry.tsx b/client/src/components/Registry.tsx index 8af4f4c..7a3d1ad 100644 --- a/client/src/components/Registry.tsx +++ b/client/src/components/Registry.tsx @@ -24,7 +24,6 @@ function Registry() { flexDirection: 'column', px: 1, pb: 2, - borderRadius: '8px', }} >

diff --git a/client/src/components/RsvpForm.tsx b/client/src/components/RsvpForm.tsx index af2dcb1..fd7bcd5 100644 --- a/client/src/components/RsvpForm.tsx +++ b/client/src/components/RsvpForm.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { useRef } from 'react'; import { useOutletContext } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; import { - Alert, Button, FormControl, FormControlLabel, @@ -11,44 +11,22 @@ import { Paper, Radio, RadioGroup, - Snackbar, TextField, } from '@mui/material'; import MailIcon from '@mui/icons-material/Mail'; import { useForm, Controller, useFieldArray } from 'react-hook-form'; import { useUpdateGuestMutation } from '../slices/api/guestSlice'; -import type { Guest, StatusProps } from '../models'; import { isFetchBaseQueryError } from '../error'; +import Status from './Status'; +import { open } from '../slices/snackbarSlice'; import type { Data } from '../error'; - -const Status = ({ isError, error, setOpen }: StatusProps) => { - return isError ? ( - isFetchBaseQueryError(error) ? ( - setOpen(false)}> - {(error.data as Data).message} - - ) : ( - setOpen(false)}> - RSVP failed - - ) - ) : ( - setOpen(false)}> - RSVP updated - - ); -}; +import type { Guest } from '../models'; function RsvpForm() { - const [updateGuest, { isLoading, isSuccess, isError, error }] = - useUpdateGuestMutation(); + const dispatch = useDispatch(); + const [updateGuest] = useUpdateGuestMutation(); const guest: Guest = useOutletContext(); const previousPartySize = useRef((guest.partySize ?? 1) - 1); - const [open, setOpen] = useState(false); - - useEffect(() => { - setOpen(isSuccess || isError); - }, [isSuccess, isError]); const { register, @@ -70,7 +48,31 @@ function RsvpForm() { }); const onSubmit = async (data: Guest) => { - updateGuest({ ...data }); + try { + await updateGuest({ ...data }).unwrap(); + dispatch( + open({ + message: 'RSVP updated', + severity: 'success', + }) + ); + } catch (error) { + if (isFetchBaseQueryError(error)) { + dispatch( + open({ + message: (error.data as Data).message, + severity: 'error', + }) + ); + } else { + dispatch( + open({ + message: 'RSVP failed', + severity: 'error', + }) + ); + } + } }; const { fields, append, remove } = useFieldArray({ @@ -124,7 +126,6 @@ function RsvpForm() { px: 2, pb: 2, mt: { xs: 10, md: 16 }, - borderRadius: '8px', }} > @@ -275,15 +276,7 @@ function RsvpForm() { - setOpen(false)} - autoHideDuration={5000} - > -

- -
- + diff --git a/client/src/components/Schedule.tsx b/client/src/components/Schedule.tsx index a901060..c3651fd 100644 --- a/client/src/components/Schedule.tsx +++ b/client/src/components/Schedule.tsx @@ -21,7 +21,6 @@ function Schedule() { sx={{ width: { xs: '90%', md: 512 }, px: 2, - borderRadius: '8px', }} >
diff --git a/client/src/components/Status.tsx b/client/src/components/Status.tsx index 4bb60ff..bdd7fb2 100644 --- a/client/src/components/Status.tsx +++ b/client/src/components/Status.tsx @@ -1,18 +1,28 @@ import React from 'react'; -import { Alert } from '@mui/material'; -import { isFetchBaseQueryError } from '../error'; -import type { StatusProps } from '../models'; -import type { Data } from '../error'; +import { useDispatch, useSelector } from 'react-redux'; +import { Alert, Snackbar } from '@mui/material'; +import { close, selectSnackbarState } from '../slices/snackbarSlice'; -const Status = ({ error, setOpen, type }: StatusProps) => { - return isFetchBaseQueryError(error) ? ( - setOpen(false)}> - {(error.data as Data).message} - - ) : ( - setOpen(false)}> - {`${type} login failed`} - +const Status = () => { + const dispatch = useDispatch(); + const snackbarState = useSelector(selectSnackbarState); + + const closeSnackbar = () => { + dispatch(close()); + }; + + return ( + +
+ + {snackbarState.message} + +
+
); }; diff --git a/client/src/error.ts b/client/src/error.ts index 8935e23..e5848f1 100644 --- a/client/src/error.ts +++ b/client/src/error.ts @@ -3,6 +3,9 @@ import type { FetchBaseQueryError } from '@reduxjs/toolkit/query/react'; export const isFetchBaseQueryError = ( error: any ): error is FetchBaseQueryError => { + if (error === undefined) { + return false; + } return 'data' in error && 'message' in error.data; }; diff --git a/client/src/slices/auth/adminSlice.ts b/client/src/slices/auth/adminSlice.ts index cc2205e..3007059 100644 --- a/client/src/slices/auth/adminSlice.ts +++ b/client/src/slices/auth/adminSlice.ts @@ -23,7 +23,5 @@ const adminSlice = createSlice({ }); export const { setAdmin } = adminSlice.actions; - -export default adminSlice.reducer; - export const selectGuests = (state: RootState) => state.admin.guests; +export default adminSlice.reducer; diff --git a/client/src/slices/auth/guestSlice.ts b/client/src/slices/auth/guestSlice.ts index fb4afaf..238781f 100644 --- a/client/src/slices/auth/guestSlice.ts +++ b/client/src/slices/auth/guestSlice.ts @@ -23,7 +23,5 @@ const guestSlice = createSlice({ }); export const { setGuest } = guestSlice.actions; - -export default guestSlice.reducer; - export const selectGuest = (state: RootState) => state.guest.guest; +export default guestSlice.reducer; diff --git a/client/src/slices/snackbarSlice.ts b/client/src/slices/snackbarSlice.ts new file mode 100644 index 0000000..eca9575 --- /dev/null +++ b/client/src/slices/snackbarSlice.ts @@ -0,0 +1,32 @@ +import { createSlice } from '@reduxjs/toolkit'; +import type { RootState } from '../store'; + +export interface SnackbarState { + open: boolean; + message: string; + severity?: 'success' | 'error'; +} + +const initialState: SnackbarState = { + open: false, + message: '', +}; + +export const snackbarSlice = createSlice({ + name: 'snackbar', + initialState, + reducers: { + open(state, action) { + state.open = true; + state.message = action.payload.message; + state.severity = action.payload.severity; + }, + close(state) { + state.open = false; + }, + }, +}); + +export const { open, close } = snackbarSlice.actions; +export const selectSnackbarState = (state: RootState) => state.snackbar; +export default snackbarSlice.reducer; diff --git a/client/src/store.ts b/client/src/store.ts index c30a30d..4814868 100644 --- a/client/src/store.ts +++ b/client/src/store.ts @@ -1,6 +1,7 @@ import { configureStore } from '@reduxjs/toolkit'; import guestReducer from './slices/auth/guestSlice'; import adminReducer from './slices/auth/adminSlice'; +import snackbarReducer from './slices/snackbarSlice'; import { guestSlice } from './slices/api/guestSlice'; import { adminSlice } from './slices/api/adminSlice'; @@ -10,6 +11,7 @@ const store = configureStore({ [adminSlice.reducerPath]: adminSlice.reducer, guest: guestReducer, admin: adminReducer, + snackbar: snackbarReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(guestSlice.middleware, adminSlice.middleware), -- cgit v1.2.3