diff options
Diffstat (limited to 'client/src/components')
-rw-r--r-- | client/src/components/AdminLogin.tsx | 2 | ||||
-rw-r--r-- | client/src/components/CalendarDialog.tsx | 71 | ||||
-rw-r--r-- | client/src/components/GlobalSnackbar.test.tsx | 2 | ||||
-rw-r--r-- | client/src/components/GlobalSnackbar.tsx | 6 | ||||
-rw-r--r-- | client/src/components/GuestLogin.tsx | 2 | ||||
-rw-r--r-- | client/src/components/RsvpForm.tsx | 2 | ||||
-rw-r--r-- | client/src/components/Schedule.test.tsx | 22 | ||||
-rw-r--r-- | client/src/components/Schedule.tsx | 32 |
8 files changed, 130 insertions, 9 deletions
diff --git a/client/src/components/AdminLogin.tsx b/client/src/components/AdminLogin.tsx index a4fce8d..bfc96d2 100644 --- a/client/src/components/AdminLogin.tsx +++ b/client/src/components/AdminLogin.tsx @@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form'; import { useAppDispatch } from '../hooks'; import { setAdmin } from '../slices/auth/adminSlice'; import { useLoginAdminMutation } from '../slices/api/adminSlice'; -import { showSnackbar } from '../slices/snackbarSlice'; +import { showSnackbar } from '../slices/uiSlice'; import { isFetchBaseQueryError } from '../error'; import type { Credentials } from '../models'; import type { Data } from '../error'; diff --git a/client/src/components/CalendarDialog.tsx b/client/src/components/CalendarDialog.tsx new file mode 100644 index 0000000..cff5512 --- /dev/null +++ b/client/src/components/CalendarDialog.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { + Dialog, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, + useTheme, +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { useAppDispatch, useAppSelector } from '../hooks'; +import { hideDialog, selectUIState } from '../slices/uiSlice'; + +function CalendarDialog() { + const dispatch = useAppDispatch(); + const { dialogOpen } = useAppSelector(selectUIState); + const theme = useTheme(); + + const handleClose = () => { + dispatch(hideDialog()); + }; + + return ( + <Dialog + open={dialogOpen} + onClose={handleClose} + PaperProps={{ sx: { borderRadius: 2 } }} + > + <DialogTitle sx={{ textAlign: 'center' }}> + Calendar Invitation + </DialogTitle> + <IconButton + aria-label="close" + onClick={handleClose} + sx={(theme) => ({ + position: 'absolute', + right: 8, + top: 8, + color: theme.palette.grey[500], + })} + > + <CloseIcon /> + </IconButton> + <DialogContent + sx={{ + height: '100%', + width: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + gap: 4, + }} + > + <DialogContentText color="inherit"> + Scan the QR code or click the link below to add the calendar invite to + your device. + </DialogContentText> + <img src="/madison-michael-qr-code.png" /> + <a + href="/madison-michael-wedding.ics" + style={{ color: theme.palette.primary.main }} + > + Add to calendar + </a> + </DialogContent> + </Dialog> + ); +} + +export default CalendarDialog; diff --git a/client/src/components/GlobalSnackbar.test.tsx b/client/src/components/GlobalSnackbar.test.tsx index 2643816..ce6a6ba 100644 --- a/client/src/components/GlobalSnackbar.test.tsx +++ b/client/src/components/GlobalSnackbar.test.tsx @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import { renderWithProviders } from '../renderWithProviders'; import routes from '../routes'; -import { showSnackbar } from '../slices/snackbarSlice'; +import { showSnackbar } from '../slices/uiSlice'; import setupStore from '../store'; describe('Global Snackbar', async () => { diff --git a/client/src/components/GlobalSnackbar.tsx b/client/src/components/GlobalSnackbar.tsx index c4457af..83d6582 100644 --- a/client/src/components/GlobalSnackbar.tsx +++ b/client/src/components/GlobalSnackbar.tsx @@ -1,18 +1,18 @@ import React from 'react'; import { Alert, Snackbar } from '@mui/material'; import { useAppDispatch, useAppSelector } from '../hooks'; -import { hideSnackbar, selectSnackbarState } from '../slices/snackbarSlice'; +import { hideSnackbar, selectUIState } from '../slices/uiSlice'; function GlobalSnackbar() { const dispatch = useAppDispatch(); - const { open, message, severity } = useAppSelector(selectSnackbarState); + const { snackbarOpen, message, severity } = useAppSelector(selectUIState); const handleClose = () => { dispatch(hideSnackbar()); }; return ( - <Snackbar open={open} onClose={handleClose} autoHideDuration={5000}> + <Snackbar open={snackbarOpen} onClose={handleClose} autoHideDuration={5000}> <div> <Alert severity={severity} onClose={handleClose}> {message} diff --git a/client/src/components/GuestLogin.tsx b/client/src/components/GuestLogin.tsx index 2f5a3eb..c2bfeb9 100644 --- a/client/src/components/GuestLogin.tsx +++ b/client/src/components/GuestLogin.tsx @@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form'; import { useAppDispatch } from '../hooks'; import { setGuest } from '../slices/auth/guestSlice'; import { useLoginGuestMutation } from '../slices/api/guestSlice'; -import { showSnackbar } from '../slices/snackbarSlice'; +import { showSnackbar } from '../slices/uiSlice'; import { isFetchBaseQueryError } from '../error'; import type { Data } from '../error'; import type { Name } from '../models'; diff --git a/client/src/components/RsvpForm.tsx b/client/src/components/RsvpForm.tsx index eae34c3..2a3552f 100644 --- a/client/src/components/RsvpForm.tsx +++ b/client/src/components/RsvpForm.tsx @@ -17,7 +17,7 @@ import { useForm, Controller, useFieldArray } from 'react-hook-form'; import { useAppDispatch } from '../hooks'; import { useUpdateGuestMutation } from '../slices/api/guestSlice'; import { isFetchBaseQueryError } from '../error'; -import { showSnackbar } from '../slices/snackbarSlice'; +import { showSnackbar } from '../slices/uiSlice'; import type { Data } from '../error'; import type { Guest } from '../models'; diff --git a/client/src/components/Schedule.test.tsx b/client/src/components/Schedule.test.tsx new file mode 100644 index 0000000..76f0f91 --- /dev/null +++ b/client/src/components/Schedule.test.tsx @@ -0,0 +1,22 @@ +import '@testing-library/jest-dom'; +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; +import { describe, expect, it } from 'vitest'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; +import { renderWithProviders } from '../renderWithProviders'; +import routes from '../routes'; + +describe('Schedule', async () => { + const memoryRouter = createMemoryRouter(routes, { + initialEntries: ['/schedule'], + }); + it('displays calendar dialog', async () => { + const { getByLabelText, findByText } = renderWithProviders( + <RouterProvider router={memoryRouter} /> + ); + + fireEvent.click(getByLabelText(/insert invitation/i)); + expect(await findByText(/calendar invitation/i)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/Schedule.tsx b/client/src/components/Schedule.tsx index 0ce7e4c..64140e4 100644 --- a/client/src/components/Schedule.tsx +++ b/client/src/components/Schedule.tsx @@ -1,10 +1,25 @@ import React from 'react'; -import { Paper, Typography, useMediaQuery, useTheme } from '@mui/material'; +import { + IconButton, + Paper, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; import divineShepherd from '/divine-shepherd.jpg'; +import InsertInvitationIcon from '@mui/icons-material/InsertInvitation'; +import { useAppDispatch } from '../hooks'; +import { showDialog } from '../slices/uiSlice'; function Schedule() { + const dispatch = useAppDispatch(); const theme = useTheme(); const isMobile = useMediaQuery('(max-width: 768px)'); + + const handleOpen = () => { + dispatch(showDialog()); + }; + return ( <div style={{ @@ -28,10 +43,23 @@ function Schedule() { <div style={{ width: '35%' }}> <p>April 26, 2025</p> </div> - <div style={{ width: '65%' }}> + <div + style={{ + display: 'flex', + justifyContent: 'space-around', + width: '65%', + }} + > <Typography variant="h5" sx={{ lineHeight: 1.6 }}> Wedding Schedule </Typography> + <IconButton + color="inherit" + onClick={handleOpen} + aria-label="insert invitation" + > + <InsertInvitationIcon /> + </IconButton> </div> </div> <hr style={{ width: '100%' }} /> |