summaryrefslogtreecommitdiff
path: root/client/src
diff options
context:
space:
mode:
authorMichael Hunteman <michael@huntm.net>2024-09-20 11:19:34 -0700
committerMichael Hunteman <michael@huntm.net>2024-09-20 11:19:34 -0700
commita54a93b724a6104213b17271fc298e37adedc1c5 (patch)
treeea5d013e5a25806788b0dfd18d5e638ed141e4a7 /client/src
parent3aad1960e4ee9bce1155b4154c596daf09ee0ae5 (diff)
Use redux for updating snackbar
Diffstat (limited to 'client/src')
-rw-r--r--client/src/ThemeContextProvider.tsx7
-rw-r--r--client/src/components/AdminLogin.tsx41
-rw-r--r--client/src/components/GuestLogin.tsx41
-rw-r--r--client/src/components/Registry.tsx1
-rw-r--r--client/src/components/RsvpForm.tsx73
-rw-r--r--client/src/components/Schedule.tsx1
-rw-r--r--client/src/components/Status.tsx36
-rw-r--r--client/src/error.ts3
-rw-r--r--client/src/slices/auth/adminSlice.ts4
-rw-r--r--client/src/slices/auth/guestSlice.ts4
-rw-r--r--client/src/slices/snackbarSlice.ts32
-rw-r--r--client/src/store.ts2
12 files changed, 150 insertions, 95 deletions
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<boolean>(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',
}}
>
<Typography variant="h6">Admin Login</Typography>
@@ -85,15 +100,7 @@ function GuestLogin() {
Log in
</Button>
</Paper>
- <Snackbar
- open={!isLoading && open}
- onClose={() => setOpen(false)}
- autoHideDuration={5000}
- >
- <div>
- <Status {...({ error, setOpen, type: 'Admin' } as StatusProps)} />
- </div>
- </Snackbar>
+ <Status />
</form>
);
}
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<boolean>(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',
}}
>
<Typography variant="h6">Guest Login</Typography>
@@ -84,15 +99,7 @@ function GuestLogin() {
<Button type="submit" variant="contained" fullWidth sx={{ mt: 2 }}>
Log in
</Button>
- <Snackbar
- open={!isLoading && open}
- onClose={() => setOpen(false)}
- autoHideDuration={5000}
- >
- <div>
- <Status {...({ error, setOpen, type: 'Guest' } as StatusProps)} />
- </div>
- </Snackbar>
+ <Status />
</Paper>
</form>
);
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',
}}
>
<p style={{ textAlign: 'center' }}>
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) ? (
- <Alert severity="error" onClose={() => setOpen(false)}>
- {(error.data as Data).message}
- </Alert>
- ) : (
- <Alert severity="error" onClose={() => setOpen(false)}>
- RSVP failed
- </Alert>
- )
- ) : (
- <Alert severity="success" onClose={() => setOpen(false)}>
- RSVP updated
- </Alert>
- );
-};
+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<boolean>(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',
}}
>
<Grid container spacing={2}>
@@ -275,15 +276,7 @@ function RsvpForm() {
</Button>
</div>
</Grid>
- <Snackbar
- open={!isLoading && open}
- onClose={() => setOpen(false)}
- autoHideDuration={5000}
- >
- <div>
- <Status {...({ isError, error, setOpen } as StatusProps)} />
- </div>
- </Snackbar>
+ <Status />
</Grid>
</Paper>
</form>
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',
}}
>
<div style={{ display: 'flex', alignItems: 'center' }}>
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) ? (
- <Alert severity="error" onClose={() => setOpen(false)}>
- {(error.data as Data).message}
- </Alert>
- ) : (
- <Alert severity="error" onClose={() => setOpen(false)}>
- {`${type} login failed`}
- </Alert>
+const Status = () => {
+ const dispatch = useDispatch();
+ const snackbarState = useSelector(selectSnackbarState);
+
+ const closeSnackbar = () => {
+ dispatch(close());
+ };
+
+ return (
+ <Snackbar
+ open={snackbarState.open}
+ onClose={closeSnackbar}
+ autoHideDuration={5000}
+ >
+ <div>
+ <Alert severity={snackbarState.severity} onClose={closeSnackbar}>
+ {snackbarState.message}
+ </Alert>
+ </div>
+ </Snackbar>
);
};
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),