diff --git a/.gitignore b/.gitignore index 1bd7226..2cc42ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +db/*.db node_modules *.swp diff --git a/client/favicon.xcf b/client/favicon.xcf new file mode 100644 index 0000000..d4b65f7 Binary files /dev/null and b/client/favicon.xcf differ diff --git a/client/public/assets/favicon-128.png b/client/public/assets/favicon-128.png new file mode 100644 index 0000000..e38e665 Binary files /dev/null and b/client/public/assets/favicon-128.png differ diff --git a/client/public/assets/favicon-152.png b/client/public/assets/favicon-152.png new file mode 100644 index 0000000..0e06562 Binary files /dev/null and b/client/public/assets/favicon-152.png differ diff --git a/client/public/assets/favicon-167.png b/client/public/assets/favicon-167.png new file mode 100644 index 0000000..f38da3d Binary files /dev/null and b/client/public/assets/favicon-167.png differ diff --git a/client/public/assets/favicon-180.png b/client/public/assets/favicon-180.png new file mode 100644 index 0000000..f21e31b Binary files /dev/null and b/client/public/assets/favicon-180.png differ diff --git a/client/public/assets/favicon-192.png b/client/public/assets/favicon-192.png new file mode 100644 index 0000000..ddb51da Binary files /dev/null and b/client/public/assets/favicon-192.png differ diff --git a/client/public/assets/favicon-256.png b/client/public/assets/favicon-256.png new file mode 100644 index 0000000..0b32f4b Binary files /dev/null and b/client/public/assets/favicon-256.png differ diff --git a/client/public/assets/favicon-32.png b/client/public/assets/favicon-32.png new file mode 100644 index 0000000..176b7a5 Binary files /dev/null and b/client/public/assets/favicon-32.png differ diff --git a/client/src/App.js b/client/src/App.js index 83be7bc..44f8f07 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -16,6 +16,8 @@ import { GlobalContext } from "./GlobalContext.js"; import SignIn from "./SignIn.js"; import SignUp from "./SignUp.js"; import Group from "./Group.js"; +import VerifyEmail from "./VerifyEmail.js"; +import Dashboard from "./Dashboard.js"; import { base } from "./Common.js"; @@ -46,23 +48,21 @@ const App = () => { }/> }/> + }/> Not implemented... yet. }/> }/> - -
Goodtimes
-
The eventual new site for the legacy Beer Tuesday... coming soon... -
- { user &&
- Logged in as {user.email} -
} - - }/> + { user && user.mailVerified && + }/> + } + { user && !user.mailVerified && + You need to verify your email via the link sent to {user.email}.}/> + } + { !user && + } /> + }
diff --git a/client/src/Dashboard.css b/client/src/Dashboard.css new file mode 100644 index 0000000..51002a8 --- /dev/null +++ b/client/src/Dashboard.css @@ -0,0 +1,4 @@ +.Dashboard { + text-align: center; +} + diff --git a/client/src/Dashboard.js b/client/src/Dashboard.js new file mode 100644 index 0000000..0f577d2 --- /dev/null +++ b/client/src/Dashboard.js @@ -0,0 +1,78 @@ +import React, { useState, useEffect, useContext } from "react"; +import Paper from '@mui/material/Paper'; +import Moment from 'react-moment'; + +import { + useParams, + useNavigate +} from "react-router-dom"; + +import './Dashboard.css'; +import { GlobalContext } from "./GlobalContext.js"; +import { base } from "./Common.js"; + +function Dashboard() { + const { csrfToken, user, setUser } = useContext(GlobalContext); + const [ groups, setGroups ] = useState([]); + const [ error, setError ] = useState(null); + + useEffect(() => { + if (!user || !csrfToken) { + return; + } + const effect = async () => { + const res = await window.fetch( + `${base}/api/v1/groups`, { + method: 'POST', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'CSRF-Token': csrfToken + } + }); + const data = await res.json(); + if (res.status >= 400) { + setError(data.message ? data.message : res.statusText); + return; + } + if (!data) { + return; + } + setGroups(data); + } + + effect(); + }, [user, setGroups, csrfToken ]); + + return ( + + + + + { groups.map((group) => { + return
+
{group.name}
+ { group.nextEvent && +
Next event on .
+ } +
; + }) } +
+
+
+ ); +} + +export default Dashboard; diff --git a/client/src/GoodTimesBar.js b/client/src/GoodTimesBar.js index 5815146..ace9550 100644 --- a/client/src/GoodTimesBar.js +++ b/client/src/GoodTimesBar.js @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState, useCallback } from 'react'; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; import Toolbar from '@mui/material/Toolbar'; @@ -17,30 +17,13 @@ import { import Gravatar from 'react-gravatar' import { GlobalContext } from "./GlobalContext.js"; +import { base } from "./Common.js"; const GoodTimesBar = () => { - const { user } = useContext(GlobalContext); + const { csrfToken, user, setUser } = useContext(GlobalContext); const [ settings, setSettings ] = useState([]); const navigate = useNavigate(); - useEffect(() => { - if (user) { - setSettings(['Profile', 'Account', 'Dashboard', 'Logout'] - .map((setting) => ( - - {setting} - - ))); - } else { - setSettings(['Login'] - .map((setting) => ( - - {setting} - - ))); - } - }, [user, setSettings]); - const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElUser, setAnchorElUser] = React.useState(null); @@ -51,6 +34,37 @@ const GoodTimesBar = () => { setAnchorElUser(event.currentTarget); }; + const handleUserMenu = useCallback(async (event, menu) => { + switch (menu) { + case 'Sign Out': { + const res = await window.fetch( + `${base}/api/v1/users/signout`, { + method: 'POST', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'CSRF-Token': csrfToken + } + }); + const data = await res.json(); + if (res.status >= 400) { + return; + } + setUser(null); + setAnchorElUser(null); + navigate('/'); + break; + } + + case 'Dashboard': + setAnchorElUser(null); + navigate('/'); + break; + } + console.log(event); + }, [csrfToken, setUser]); + const handleCloseNavMenu = () => { setAnchorElNav(null); }; @@ -59,6 +73,21 @@ const GoodTimesBar = () => { setAnchorElUser(null); }; + console.log(`goodtimes-bar - user - `, user); + + useEffect(() => { + if (user) { + setSettings([/*'Profile', 'Account',*/ 'Dashboard', 'Sign Out'] + .map((setting) => ( + handleUserMenu(e, setting)}> + {setting} + + ))); + } else { + setSettings([]); + } + }, [user, setSettings]); + return ( @@ -111,14 +140,15 @@ const GoodTimesBar = () => { > GOODTIMES + { !user && <> - + diff --git a/client/src/Group.css b/client/src/Group.css new file mode 100644 index 0000000..68dc9c0 --- /dev/null +++ b/client/src/Group.css @@ -0,0 +1,3 @@ +.Group { + text-align: center; +} diff --git a/client/src/Group.js b/client/src/Group.js index 70c7d4f..1d7a83d 100644 --- a/client/src/Group.js +++ b/client/src/Group.js @@ -1,22 +1,55 @@ -import React, { useState } from "react"; +import React, { useState, useEffect, useContext } from "react"; +import Paper from '@mui/material/Paper'; import { - useParams + useParams, + useNavigate } from "react-router-dom"; import './Group.css'; import { GlobalContext } from "./GlobalContext.js"; -import Paper from '@mui/material/Paper'; +import { base } from "./Common.js"; function Group() { - const { group } = useParams(); - const [ user, setUser ] = useState(null); + const { csrfToken, user, setUser } = useContext(GlobalContext); + const groupId = useParams().group; + const [ group, setGroup ] = useState(null); + const [ error, setError ] = useState(null); + + useEffect(() => { + if (!user || !groupId || !csrfToken) { + return; + } + + const effect = async () => { + const res = await window.fetch( + `${base}/api/v1/group/${groupId}/events`, { + method: 'POST', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'CSRF-Token': csrfToken + } + }); + const data = await res.json(); + if (res.status >= 400) { + setError(data.message ? data.message : res.statusText); + return; + } + if (!data) { + return; + } + setGroup(data); + }; + effect(); + }, [user, setGroup, groupId, csrfToken]); return ( -
+ - Group: {group} + Group: {groupId} -
+ ); } diff --git a/client/src/SignIn.js b/client/src/SignIn.js index ad8a463..27a4231 100644 --- a/client/src/SignIn.js +++ b/client/src/SignIn.js @@ -40,11 +40,7 @@ export default function SignIn() { const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); - console.log({ - email: data.get('email'), - password: data.get('password'), - }); - window.fetch(`${base}/api/v1/users/login`, { + window.fetch(`${base}/api/v1/users/signin`, { method: 'POST', cache: 'no-cache', credentials: 'same-origin', diff --git a/client/src/SignUp.js b/client/src/SignUp.js index 02f7288..ba69bb6 100644 --- a/client/src/SignUp.js +++ b/client/src/SignUp.js @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { useContext, useState } from 'react'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import CssBaseline from '@mui/material/CssBaseline'; @@ -12,7 +12,11 @@ import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import Typography from '@mui/material/Typography'; import Container from '@mui/material/Container'; import { createTheme, ThemeProvider } from '@mui/material/styles'; -import { Link as ReactLink } from "react-router-dom"; + +import { Link as ReactLink, useNavigate } from "react-router-dom"; + +import { GlobalContext } from "./GlobalContext.js"; +import { base } from "./Common.js"; function Copyright(props) { return ( @@ -30,12 +34,44 @@ function Copyright(props) { const theme = createTheme(); export default function SignUp() { + const { setUser, csrfToken } = useContext(GlobalContext); + const [error, setError] = useState(undefined); + const navigate = useNavigate(); + const handleSubmit = (event) => { event.preventDefault(); const data = new FormData(event.currentTarget); - console.log({ - email: data.get('email'), - password: data.get('password'), + window.fetch(`${base}/api/v1/users/signup`, { + method: 'POST', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + 'CSRF-Token': csrfToken + }, + body: JSON.stringify({ + email: data.get('email'), + firstName: data.get('firstName'), + familyName: data.get('lastName'), + password: data.get('password') + }) + }) + .then(async (res) => { + return [res.status, res.statusText, await res.json()]; + }) + .then(([status, statusText, data]) => { + if (status >= 400) { + setError(data.message ? data.message : statusText); + return null; + } if (!data) { + return; + } + setUser(data); + navigate("/"); + }) + .catch((error) => { + console.error(`Error: `, error); + setError(`Error authenticating.`); }); }; @@ -102,6 +138,10 @@ export default function SignUp() { /> + {error && + {error} + + }