From 589e53f152d7363074049dfd1bd5a34286ae74d6 Mon Sep 17 00:00:00 2001 From: Michael Hunteman Date: Wed, 21 Feb 2024 19:43:07 -0600 Subject: Update example guests.json with RTK query --- data/guests.json | 14 +++++ package-lock.json | 91 ++++++++++++++++++++++++++++++++ package.json | 2 + src/App.tsx | 8 ++- src/ThemeContextProvider.tsx | 22 ++++---- src/apiSlice.ts | 26 ++++++++++ src/components/Admin.tsx | 35 +++++++++++++ src/components/GuestLogin.tsx | 18 +++++++ src/components/NavBar.tsx | 13 ++--- src/components/Registry.tsx | 2 +- src/components/Rsvp.tsx | 118 +++++++----------------------------------- src/components/RsvpForm.tsx | 109 ++++++++++++++++++++++++++++++++++++++ src/main.tsx | 21 ++++++-- 13 files changed, 353 insertions(+), 126 deletions(-) create mode 100644 data/guests.json create mode 100644 src/apiSlice.ts create mode 100644 src/components/Admin.tsx create mode 100644 src/components/GuestLogin.tsx create mode 100644 src/components/RsvpForm.tsx diff --git a/data/guests.json b/data/guests.json new file mode 100644 index 0000000..21edde2 --- /dev/null +++ b/data/guests.json @@ -0,0 +1,14 @@ +{ + "guests": [ + { + "id": "1", + "first_name": "John", + "last_name": "Doe", + "attendance": "false", + "meal": "beef", + "restrictions": "none", + "plus-one": "none", + "advice": "" + } + ] +} diff --git a/package-lock.json b/package-lock.json index 7d50e2f..1b493be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,10 @@ "@mui/icons-material": "^5.15.10", "@mui/lab": "^5.0.0-alpha.165", "@mui/material": "^5.15.9", + "@reduxjs/toolkit": "^2.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^9.1.0", "react-router-dom": "^6.22.0" }, "devDependencies": { @@ -1499,6 +1501,29 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.1.tgz", + "integrity": "sha512-8CREoqJovQW/5I4yvvijm/emUiCCmcs4Ev4XPWd4mizSO+dD3g5G6w34QK5AGeNrSH7qM8Fl66j4vuV7dpOdkw==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", @@ -1777,6 +1802,11 @@ "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", @@ -2939,6 +2969,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3499,6 +3538,32 @@ "react": "^18.2.0" } }, + "node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -3553,11 +3618,29 @@ "react-dom": ">=16.6.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3915,6 +3998,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", diff --git a/package.json b/package.json index 14b10ec..d944d8e 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,10 @@ "@mui/icons-material": "^5.15.10", "@mui/lab": "^5.0.0-alpha.165", "@mui/material": "^5.15.9", + "@reduxjs/toolkit": "^2.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^9.1.0", "react-router-dom": "^6.22.0" }, "devDependencies": { diff --git a/src/App.tsx b/src/App.tsx index 073b9d6..c0ea612 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,16 @@ -import { createContext, useState, useMemo } from 'react'; import { Outlet } from 'react-router-dom'; import CssBaseline from '@mui/material/CssBaseline'; import NavBar from './components/NavBar'; -import ThemeContextProvider from './ThemeContextProvider' function App() { return ( - + <> - - ); + + ) } export default App; diff --git a/src/ThemeContextProvider.tsx b/src/ThemeContextProvider.tsx index 970f9d8..e1e928c 100644 --- a/src/ThemeContextProvider.tsx +++ b/src/ThemeContextProvider.tsx @@ -1,8 +1,8 @@ import { ReactNode, createContext, useMemo, useState } from 'react'; -import { StyledEngineProvider, ThemeProvider, createTheme } from '@mui/material/styles'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; type ThemeContextType = { - switchColorMode: () => void; + toggleColorMode: () => void; }; type ThemeProviderProps = { @@ -10,13 +10,13 @@ type ThemeProviderProps = { }; export const ThemeContext = createContext({ - switchColorMode: () => {} + toggleColorMode: () => {} }); function ThemeContextProvider({ children }: ThemeProviderProps) { const [mode, setMode] = useState<'light' | 'dark'>('light'); - const switchColorMode = () => { + const toggleColorMode = () => { setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light')); }; @@ -31,14 +31,12 @@ function ThemeContextProvider({ children }: ThemeProviderProps) { ); return ( - - - - {children} - - - + + + {children} + + ); -} +}; export default ThemeContextProvider; diff --git a/src/apiSlice.ts b/src/apiSlice.ts new file mode 100644 index 0000000..6d779ed --- /dev/null +++ b/src/apiSlice.ts @@ -0,0 +1,26 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +export const apiSlice = createApi({ + reducerPath: 'api', + baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3000' }), + tagTypes: ['Guests'], + endpoints: builder => ({ + getGuests: builder.query({ + query: () => '/guests', + providesTags: ['Guests'] + }), + updateGuest: builder.mutation({ + query: guest => ({ + url: `/guests/${guest.id}`, + method: 'PATCH', + body: guest, + providesTags: ['Guests'] + }) + }) + }) +}); + +export const { + useGetGuestsQuery, + useUpdateGuestMutation +} = apiSlice; diff --git a/src/components/Admin.tsx b/src/components/Admin.tsx new file mode 100644 index 0000000..8f6ce12 --- /dev/null +++ b/src/components/Admin.tsx @@ -0,0 +1,35 @@ +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; + +import { useGetGuestsQuery } from '../apiSlice' + +function Admin() { + const { + data: guests, + isLoading, + isSuccess, + isError, + error + } = useGetGuestsQuery() + + let content + + if (isLoading) { + content = Loading... + } else if (isSuccess) { + content = JSON.stringify(guests) + } else if (isError) { + content = <>{error.toString()} + } + + return ( + + + Admin + + {content} + + ) +} + +export default Admin; diff --git a/src/components/GuestLogin.tsx b/src/components/GuestLogin.tsx new file mode 100644 index 0000000..5637276 --- /dev/null +++ b/src/components/GuestLogin.tsx @@ -0,0 +1,18 @@ +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; + +function GuestLogin({ loggedIn, setLoggedIn }) { + return ( + + + Enter your name to RSVP + + + + ) +} + +export default GuestLogin; diff --git a/src/components/NavBar.tsx b/src/components/NavBar.tsx index a4b46c8..2cf1b31 100644 --- a/src/components/NavBar.tsx +++ b/src/components/NavBar.tsx @@ -1,4 +1,4 @@ -import { useContext, useMemo } from 'react'; +import { useContext } from 'react'; import { Link } from 'react-router-dom'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; @@ -15,12 +15,13 @@ import { ThemeContext } from '../ThemeContextProvider'; function NavBar({ mode }) { const theme = useTheme(); - const { switchColorMode } = useContext(ThemeContext); + const { toggleColorMode } = useContext(ThemeContext); const pages = [ { name: 'Schedule', to: '/schedule'}, { name: 'RSVP', to: '/rsvp' }, - { name: 'Registry', to: '/registry' } + { name: 'Registry', to: '/registry' }, + { name: 'Admin', to: '/admin' } ]; return ( @@ -36,18 +37,18 @@ function NavBar({ mode }) { Madison and Michael's Wedding - {pages.map((page) => ( + {pages.map(page => ( ))} - + {theme.palette.mode === 'dark' ? : } ); -} +}; export default NavBar; diff --git a/src/components/Registry.tsx b/src/components/Registry.tsx index b07d680..5856909 100644 --- a/src/components/Registry.tsx +++ b/src/components/Registry.tsx @@ -8,7 +8,7 @@ function Registry() { Registry - ); + ) } export default Registry; diff --git a/src/components/Rsvp.tsx b/src/components/Rsvp.tsx index 81f37fc..858ca71 100644 --- a/src/components/Rsvp.tsx +++ b/src/components/Rsvp.tsx @@ -1,107 +1,29 @@ import { useState } from 'react'; -import Button from '@mui/material/Button'; -import Paper from '@mui/material/Paper'; -import Grid from '@mui/material/Grid'; -import TextField from '@mui/material/TextField'; -import Typography from '@mui/material/Typography'; -import Radio from '@mui/material/Radio'; -import RadioGroup from '@mui/material/RadioGroup'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import FormControl from '@mui/material/FormControl'; -import FormLabel from '@mui/material/FormLabel'; +import RsvpForm from './RsvpForm'; +import GuestLogin from './GuestLogin'; +import { useGetGuestsQuery } from '../apiSlice' function Rsvp() { - const [guestList, setGuestList] = useState([]); + // Enter your name to RSVP; query the database + const [loggedIn, setLoggedIn] = useState(false); - const onAddBtnClick = event => { - setGuestList(guestList.concat( - - - - - - - Meal Preference - - } - label="Beef" - /> - } - label="Chicken" - /> - } - label="Fish" - /> - } - label="Vegetarian" - /> - - - - - )); - } + const { + data: guests, + isLoading, + isSuccess, + isError, + error + } = useGetGuestsQuery() return ( - - - - Date: April 14, 2025 - - - Location: - - - RSVP Deadline: - - - - - - - Are you attending? - - } label="Yes" /> - } label="No" /> - - - - - - - {guestList} - - - - - - - - - - - - ); + <> + {loggedIn ? ( + + ) : ( + + )} + + ) } export default Rsvp; diff --git a/src/components/RsvpForm.tsx b/src/components/RsvpForm.tsx new file mode 100644 index 0000000..9dbc54c --- /dev/null +++ b/src/components/RsvpForm.tsx @@ -0,0 +1,109 @@ +import { useState } from 'react'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Grid from '@mui/material/Grid'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; + +import { useGetGuestsQuery, useUpdateGuestMutation } from '../apiSlice'; + +function RsvpForm() { + const { + data: guests, + isLoading, + isSuccess, + isError, + error + } = useGetGuestsQuery() + + const [updateGuest] = useUpdateGuestMutation() + + const handleSubmit = (e) => { + e.preventDefault() + console.log('handle') + let guest = guests[0] + if (guest.attendance === 'true') { + updateGuest({...guest, attendance: 'false'}) + } else { + updateGuest({...guest, attendance: 'true'}) + } + } + + return ( + + + + Date: April 14, 2025 + + + Location: + + + RSVP Deadline: + + + + + + + Will you attend our wedding? + + } label="Yes" /> + } label="No" /> + + + + + + + + + Meal Preference + } + label="Beef" + /> + } + label="Chicken" + /> + } + label="Fish" + /> + } + label="Vegetarian" + /> + + + + + + + + + + + + + + ) +} + +export default RsvpForm; diff --git a/src/main.tsx b/src/main.tsx index 182761d..ecc4803 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,11 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import { render } from 'react-dom'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { ApiProvider } from '@reduxjs/toolkit/query/react'; import App from './App'; +import { apiSlice } from './apiSlice'; +import ThemeContextProvider from './ThemeContextProvider' import Schedule from './components/Schedule'; import Registry from './components/Registry'; import Rsvp from './components/Rsvp'; +import Admin from './components/Admin'; const router = createBrowserRouter([ { @@ -23,13 +28,21 @@ const router = createBrowserRouter([ { path: "rsvp", element: + }, + { + path: "admin", + element: } ] } -]); +]) -ReactDOM.createRoot(document.getElementById('root')).render( +ReactDOM.createRoot(document.getElementById('root')!).render( - + + + + + -); +) -- cgit v1.2.3