summaryrefslogtreecommitdiff
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/components/Admin.tsx29
-rw-r--r--client/src/components/AdminLogin.tsx90
-rw-r--r--client/src/components/Dashboard.tsx60
-rw-r--r--client/src/components/GuestLogin.tsx14
-rw-r--r--client/src/components/Rsvp.tsx4
-rw-r--r--client/src/components/RsvpForm.tsx4
-rw-r--r--client/src/main.tsx16
-rw-r--r--client/src/pages.ts1
-rw-r--r--client/src/slices/api/adminSlice.ts53
-rw-r--r--client/src/slices/api/guestSlice.ts (renamed from client/src/slices/apiSlice.ts)37
-rw-r--r--client/src/slices/auth/adminSlice.ts29
-rw-r--r--client/src/slices/auth/guestSlice.ts29
-rw-r--r--client/src/slices/authSlice.ts31
-rw-r--r--client/src/store.ts14
-rw-r--r--client/src/vite-env.d.ts8
15 files changed, 350 insertions, 69 deletions
diff --git a/client/src/components/Admin.tsx b/client/src/components/Admin.tsx
new file mode 100644
index 0000000..6e772ab
--- /dev/null
+++ b/client/src/components/Admin.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { useMemo } from 'react';
+import { useLocation, Navigate, Outlet } from 'react-router-dom';
+import { useSelector } from 'react-redux';
+import CssBaseline from '@mui/material/CssBaseline';
+import NavBar from './NavBar';
+import { selectGuests } from '../slices/auth/adminSlice';
+
+const authenticate = () => {
+ const guests = useSelector(selectGuests);
+ return useMemo(() => ({ guests }), [guests]);
+};
+
+function Rsvp() {
+ const auth = authenticate();
+ const location = useLocation();
+
+ return auth?.guests ? (
+ <>
+ <CssBaseline />
+ <NavBar />
+ <Outlet context={auth?.guests} />
+ </>
+ ) : (
+ <Navigate to="/admin/login" state={{ from: location }} replace />
+ );
+}
+
+export default Rsvp;
diff --git a/client/src/components/AdminLogin.tsx b/client/src/components/AdminLogin.tsx
new file mode 100644
index 0000000..d9c1260
--- /dev/null
+++ b/client/src/components/AdminLogin.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useDispatch } from 'react-redux';
+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 { AdminLoginRequest } from '../slices/api/adminSlice';
+
+function GuestLogin() {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const [login] = useLoginAdminMutation();
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm<AdminLoginRequest>({
+ defaultValues: {
+ username: '',
+ password: '',
+ },
+ });
+
+ const onSubmit = async (data: AdminLoginRequest) => {
+ try {
+ dispatch(setAdmin(await login(data).unwrap()));
+ navigate('/dashboard');
+ } catch (e) {
+ console.log(e);
+ }
+ };
+
+ return (
+ <form
+ style={{
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'start',
+ }}
+ noValidate
+ onSubmit={handleSubmit(onSubmit)}
+ >
+ <Paper
+ elevation={3}
+ sx={{
+ '&:hover': { boxShadow: 8 },
+ width: { xs: '90%', md: 400 },
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ alignItems: 'center',
+ mt: 16,
+ p: 2,
+ borderRadius: '8px',
+ }}
+ >
+ <Typography variant="h6">Admin Login</Typography>
+ <TextField
+ label="Username"
+ variant="outlined"
+ margin="normal"
+ fullWidth
+ error={!!errors.username}
+ helperText={errors.username?.message}
+ required
+ {...register('username', { required: 'This field is required' })}
+ />
+ <TextField
+ label="Password"
+ variant="outlined"
+ margin="normal"
+ fullWidth
+ error={!!errors.password}
+ helperText={errors.password?.message}
+ required
+ {...register('password', { required: 'This field is required' })}
+ />
+ <Button type="submit" variant="contained" fullWidth sx={{ mt: 2 }}>
+ Log in
+ </Button>
+ </Paper>
+ </form>
+ );
+}
+
+export default GuestLogin;
diff --git a/client/src/components/Dashboard.tsx b/client/src/components/Dashboard.tsx
new file mode 100644
index 0000000..20758fc
--- /dev/null
+++ b/client/src/components/Dashboard.tsx
@@ -0,0 +1,60 @@
+import React, { useMemo } from 'react';
+import { useOutletContext } from 'react-router-dom';
+import {
+ MaterialReactTable,
+ useMaterialReactTable,
+ type MRT_ColumnDef,
+} from 'material-react-table';
+import type { Guest } from '../slices/api/adminSlice';
+
+function Dashboard() {
+ const guests: Guest[] = useOutletContext();
+ const columns = useMemo<MRT_ColumnDef<Guest>[]>(
+ () => [
+ {
+ accessorKey: 'firstName',
+ header: 'First Name',
+ size: 150,
+ },
+ {
+ accessorKey: 'lastName',
+ header: 'Last Name',
+ size: 150,
+ },
+ {
+ accessorKey: 'attendance',
+ header: 'Attendance',
+ size: 50,
+ },
+ {
+ accessorKey: 'email',
+ header: 'Email',
+ size: 150,
+ },
+ {
+ accessorKey: 'message',
+ header: 'Message',
+ size: 200,
+ },
+ {
+ accessorKey: 'partySize',
+ header: 'Party Size',
+ size: 50,
+ },
+ // {
+ // accessorKey: 'partyList',
+ // header: 'Party List',
+ // size: 150,
+ // },
+ ],
+ []
+ );
+ const table = useMaterialReactTable({
+ columns,
+ data: guests,
+ });
+
+ return <MaterialReactTable table={table} />;
+}
+
+export default Dashboard;
diff --git a/client/src/components/GuestLogin.tsx b/client/src/components/GuestLogin.tsx
index cca2179..0e47384 100644
--- a/client/src/components/GuestLogin.tsx
+++ b/client/src/components/GuestLogin.tsx
@@ -3,29 +3,29 @@ import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { Button, Paper, TextField, Typography } from '@mui/material';
import { useForm } from 'react-hook-form';
-import { setCredentials } from '../slices/authSlice';
-import { useLoginMutation } from '../slices/apiSlice';
-import type { LoginRequest } from '../slices/apiSlice';
+import { setGuest } from '../slices/auth/guestSlice';
+import { useLoginGuestMutation } from '../slices/api/guestSlice';
+import type { GuestLoginRequest } from '../slices/api/guestSlice';
function GuestLogin() {
const dispatch = useDispatch();
const navigate = useNavigate();
- const [login] = useLoginMutation();
+ const [login] = useLoginGuestMutation();
const {
register,
handleSubmit,
formState: { errors },
- } = useForm<LoginRequest>({
+ } = useForm<GuestLoginRequest>({
defaultValues: {
firstName: '',
lastName: '',
},
});
- const onSubmit = async (data: LoginRequest) => {
+ const onSubmit = async (data: GuestLoginRequest) => {
try {
- dispatch(setCredentials(await login(data).unwrap()));
+ dispatch(setGuest(await login(data).unwrap()));
navigate('/rsvp');
} catch (e) {
console.log(e);
diff --git a/client/src/components/Rsvp.tsx b/client/src/components/Rsvp.tsx
index d3d9677..ab83cd7 100644
--- a/client/src/components/Rsvp.tsx
+++ b/client/src/components/Rsvp.tsx
@@ -4,10 +4,10 @@ import { useLocation, Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
import CssBaseline from '@mui/material/CssBaseline';
import NavBar from './NavBar';
-import { selectCurrentGuest } from '../slices/authSlice';
+import { selectGuest } from '../slices/auth/guestSlice';
const authenticate = () => {
- const guest = useSelector(selectCurrentGuest);
+ const guest = useSelector(selectGuest);
return useMemo(() => ({ guest }), [guest]);
};
diff --git a/client/src/components/RsvpForm.tsx b/client/src/components/RsvpForm.tsx
index 1e03227..d72b92d 100644
--- a/client/src/components/RsvpForm.tsx
+++ b/client/src/components/RsvpForm.tsx
@@ -16,8 +16,8 @@ import {
} from '@mui/material';
import MailIcon from '@mui/icons-material/Mail';
import { useForm, Controller, useFieldArray } from 'react-hook-form';
-import { useUpdateGuestMutation } from '../slices/apiSlice';
-import type { Guest } from '../slices/apiSlice';
+import { useUpdateGuestMutation } from '../slices/api/guestSlice';
+import type { Guest } from '../slices/api/guestSlice';
interface StatusProps {
isError: boolean;
diff --git a/client/src/main.tsx b/client/src/main.tsx
index 70aad60..2268d5f 100644
--- a/client/src/main.tsx
+++ b/client/src/main.tsx
@@ -11,6 +11,9 @@ import GuestLogin from './components/GuestLogin';
import Rsvp from './components/Rsvp';
import RsvpForm from './components/RsvpForm';
import Home from './components/Home';
+import AdminLogin from './components/AdminLogin';
+import Admin from './components/Admin';
+import Dashboard from './components/Dashboard';
import './main.css';
const router = createBrowserRouter([
@@ -33,6 +36,10 @@ const router = createBrowserRouter([
path: 'guest/login',
element: <GuestLogin />,
},
+ {
+ path: 'admin/login',
+ element: <AdminLogin />,
+ },
],
},
{
@@ -44,6 +51,15 @@ const router = createBrowserRouter([
},
],
},
+ {
+ element: <Admin />,
+ children: [
+ {
+ path: 'dashboard',
+ element: <Dashboard />,
+ },
+ ],
+ },
]);
ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/client/src/pages.ts b/client/src/pages.ts
index 8bf5d23..5ebb9c4 100644
--- a/client/src/pages.ts
+++ b/client/src/pages.ts
@@ -3,6 +3,7 @@ const pages = [
{ name: 'Schedule', to: '/schedule' },
{ name: 'RSVP', to: '/guest/login' },
{ name: 'Registry', to: '/registry' },
+ { name: 'Dashboard', to: '/admin/login' },
];
export default pages;
diff --git a/client/src/slices/api/adminSlice.ts b/client/src/slices/api/adminSlice.ts
new file mode 100644
index 0000000..cd1638d
--- /dev/null
+++ b/client/src/slices/api/adminSlice.ts
@@ -0,0 +1,53 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+import type { RootState } from '../../store';
+
+export interface Guest {
+ id: number;
+ firstName: string;
+ lastName: string;
+ attendance: string;
+ email: string;
+ message: string;
+ partySize: number;
+ partyList: Array<PartyGuest>;
+}
+
+export interface PartyGuest {
+ firstName: string;
+ lastName: string;
+}
+
+export interface AdminLoginRequest {
+ username: string;
+ password: string;
+}
+
+export interface AdminLoginResponse {
+ guests: Guest[];
+ token: string;
+}
+
+export const adminSlice = createApi({
+ reducerPath: 'adminApi',
+ baseQuery: fetchBaseQuery({
+ baseUrl: import.meta.env.VITE_BASE_URL,
+ prepareHeaders: (headers, { getState }) => {
+ const token = (getState() as RootState).admin.token;
+ if (token) {
+ headers.set('authorization', `${token}`);
+ }
+ return headers;
+ },
+ }),
+ endpoints: (builder) => ({
+ loginAdmin: builder.mutation<AdminLoginResponse, AdminLoginRequest>({
+ query: (credentials) => ({
+ url: 'admin/login',
+ method: 'POST',
+ body: credentials,
+ }),
+ }),
+ }),
+});
+
+export const { useLoginAdminMutation } = adminSlice;
diff --git a/client/src/slices/apiSlice.ts b/client/src/slices/api/guestSlice.ts
index 90cdc48..38deb9a 100644
--- a/client/src/slices/apiSlice.ts
+++ b/client/src/slices/api/guestSlice.ts
@@ -1,12 +1,12 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
-import type { RootState } from '../store';
+import type { RootState } from '../../store';
-export interface LoginRequest {
+export interface GuestLoginRequest {
firstName: string;
lastName: string;
}
-export interface LoginResponse {
+export interface GuestLoginResponse {
guest: Guest;
token: string;
}
@@ -27,41 +27,34 @@ export interface PartyGuest {
lastName: string;
}
-export const apiSlice = createApi({
- reducerPath: 'api',
+export const guestSlice = createApi({
+ reducerPath: 'guestApi',
baseQuery: fetchBaseQuery({
- baseUrl: 'http://192.168.1.41:8080/',
+ baseUrl: import.meta.env.VITE_BASE_URL + 'guest/',
prepareHeaders: (headers, { getState }) => {
- const token = (getState() as RootState).auth.token;
+ const token = (getState() as RootState).guest.token;
if (token) {
headers.set('authorization', `${token}`);
}
return headers;
},
}),
- tagTypes: ['Guest'],
endpoints: (builder) => ({
- getGuests: builder.query<void, void>({
- query: () => 'guest',
- providesTags: ['Guest'],
+ loginGuest: builder.mutation<GuestLoginResponse, GuestLoginRequest>({
+ query: (credentials) => ({
+ url: 'login',
+ method: 'POST',
+ body: credentials,
+ }),
}),
updateGuest: builder.mutation<Guest, Guest>({
query: (guest) => ({
- url: `guest/${guest?.id}`,
+ url: `${guest?.id}`,
method: 'PUT',
body: guest,
- providesTags: ['Guest'],
- }),
- }),
- login: builder.mutation<LoginResponse, LoginRequest>({
- query: (credentials) => ({
- url: 'guest/login',
- method: 'POST',
- body: credentials,
}),
}),
}),
});
-export const { useGetGuestsQuery, useUpdateGuestMutation, useLoginMutation } =
- apiSlice;
+export const { useLoginGuestMutation, useUpdateGuestMutation } = guestSlice;
diff --git a/client/src/slices/auth/adminSlice.ts b/client/src/slices/auth/adminSlice.ts
new file mode 100644
index 0000000..8753b55
--- /dev/null
+++ b/client/src/slices/auth/adminSlice.ts
@@ -0,0 +1,29 @@
+import { createSlice } from '@reduxjs/toolkit';
+import type { PayloadAction } from '@reduxjs/toolkit';
+import type { RootState } from '../../store';
+import type { Guest } from '../api/guestSlice';
+
+type AdminAuth = {
+ guests?: Guest[];
+ token?: string;
+};
+
+const adminSlice = createSlice({
+ name: 'admin',
+ initialState: { guest: undefined, token: undefined } as AdminAuth,
+ reducers: {
+ setAdmin: (
+ state,
+ { payload: { guests, token } }: PayloadAction<AdminAuth>
+ ) => {
+ state.guests = guests;
+ state.token = token;
+ },
+ },
+});
+
+export const { setAdmin } = adminSlice.actions;
+
+export default adminSlice.reducer;
+
+export const selectGuests = (state: RootState) => state.admin.guests;
diff --git a/client/src/slices/auth/guestSlice.ts b/client/src/slices/auth/guestSlice.ts
new file mode 100644
index 0000000..701148e
--- /dev/null
+++ b/client/src/slices/auth/guestSlice.ts
@@ -0,0 +1,29 @@
+import { createSlice } from '@reduxjs/toolkit';
+import type { PayloadAction } from '@reduxjs/toolkit';
+import type { RootState } from '../../store';
+import type { Guest } from '../api/guestSlice';
+
+type GuestAuth = {
+ guest?: Guest;
+ token?: string;
+};
+
+const guestSlice = createSlice({
+ name: 'guest',
+ initialState: { guest: undefined, token: undefined } as GuestAuth,
+ reducers: {
+ setGuest: (
+ state,
+ { payload: { guest, token } }: PayloadAction<GuestAuth>
+ ) => {
+ state.guest = guest;
+ state.token = token;
+ },
+ },
+});
+
+export const { setGuest } = guestSlice.actions;
+
+export default guestSlice.reducer;
+
+export const selectGuest = (state: RootState) => state.guest.guest;
diff --git a/client/src/slices/authSlice.ts b/client/src/slices/authSlice.ts
deleted file mode 100644
index e1fec78..0000000
--- a/client/src/slices/authSlice.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createSlice } from '@reduxjs/toolkit';
-import type { PayloadAction } from '@reduxjs/toolkit';
-import type { RootState } from '../store';
-import type { Guest } from './apiSlice';
-
-type AuthState = {
- guest?: Guest;
- token?: string;
-};
-
-const authSlice = createSlice({
- name: 'auth',
- initialState: { guest: undefined, token: undefined } as AuthState,
- reducers: {
- setCredentials: (
- state,
- {
- payload: { guest, token },
- }: PayloadAction<{ guest: Guest; token: string }>
- ) => {
- state.guest = guest;
- state.token = token;
- },
- },
-});
-
-export const { setCredentials } = authSlice.actions;
-
-export default authSlice.reducer;
-
-export const selectCurrentGuest = (state: RootState) => state.auth.guest;
diff --git a/client/src/store.ts b/client/src/store.ts
index 18b3461..c30a30d 100644
--- a/client/src/store.ts
+++ b/client/src/store.ts
@@ -1,14 +1,18 @@
import { configureStore } from '@reduxjs/toolkit';
-import { apiSlice } from './slices/apiSlice';
-import authReducer from './slices/authSlice';
+import guestReducer from './slices/auth/guestSlice';
+import adminReducer from './slices/auth/adminSlice';
+import { guestSlice } from './slices/api/guestSlice';
+import { adminSlice } from './slices/api/adminSlice';
const store = configureStore({
reducer: {
- [apiSlice.reducerPath]: apiSlice.reducer,
- auth: authReducer,
+ [guestSlice.reducerPath]: guestSlice.reducer,
+ [adminSlice.reducerPath]: adminSlice.reducer,
+ guest: guestReducer,
+ admin: adminReducer,
},
middleware: (getDefaultMiddleware) =>
- getDefaultMiddleware().concat(apiSlice.middleware),
+ getDefaultMiddleware().concat(guestSlice.middleware, adminSlice.middleware),
});
export default store;
diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts
index 11f02fe..72eb128 100644
--- a/client/src/vite-env.d.ts
+++ b/client/src/vite-env.d.ts
@@ -1 +1,9 @@
/// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+ readonly VITE_BASE_URL: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}