1
0

Compare commits

..

No commits in common. "175d8449eafb10cb65b64da85c5f88a856439b91" and "3ba43bc8b863e2cc8e94acfbfa992c06888d9b48" have entirely different histories.

11 changed files with 734 additions and 956 deletions

1356
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,12 +4,10 @@
"private": true, "private": true,
"proxy": "http://localhost:11141", "proxy": "http://localhost:11141",
"dependencies": { "dependencies": {
"@date-io/moment": "^1.3.13",
"@emotion/react": "^11.9.0", "@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1", "@emotion/styled": "^11.8.1",
"@material-ui/core": "^4.12.3", "@material-ui/core": "^4.12.3",
"@material-ui/lab": "^4.0.0-alpha.60", "@material-ui/lab": "^4.0.0-alpha.60",
"@material-ui/pickers": "^3.3.10",
"@mui/icons-material": "^5.4.4", "@mui/icons-material": "^5.4.4",
"@mui/material": "^5.6.0", "@mui/material": "^5.6.0",
"@mui/utils": "^5.4.4", "@mui/utils": "^5.4.4",
@ -19,7 +17,7 @@
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"http-proxy-middleware": "^2.0.3", "http-proxy-middleware": "^2.0.3",
"moment": "^2.29.2", "moment": "^2.29.1",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.34",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import { import {
Routes, Routes,
Route, Route,
useLocation
} from "react-router-dom"; } from "react-router-dom";
import Container from '@mui/material/Container'; import Container from '@mui/material/Container';
@ -28,11 +27,6 @@ const App = () => {
const [ loading, setLoading ] = useState(true); const [ loading, setLoading ] = useState(true);
const [ googleApiKey, setGoogleApiKey ] = useState(undefined); const [ googleApiKey, setGoogleApiKey ] = useState(undefined);
const [ error, setError] = useState(undefined); const [ error, setError] = useState(undefined);
const location = useLocation();
useEffect(() => {
console.log(`app - path - `, location.pathname);
}, [location]);
useEffect(() => { useEffect(() => {
const effect = async () => { const effect = async () => {
@ -78,6 +72,7 @@ const App = () => {
effect(); effect();
}, [csrfToken, setUser]); }, [csrfToken, setUser]);
useEffect(() => { useEffect(() => {
if (!csrfToken || googleApiKey) { if (!csrfToken || googleApiKey) {
return; return;
@ -111,7 +106,7 @@ const App = () => {
<GlobalContext.Provider value={{ <GlobalContext.Provider value={{
user, setUser, csrfToken, googleApiKey, user, setUser, csrfToken, googleApiKey,
setError setError
}}> }}>
<GoodTimesBar /> <GoodTimesBar />
<Container className="Content"> <Container className="Content">
{ error && <Paper style={{padding: "0.5rem"}}>{error}</Paper> } { error && <Paper style={{padding: "0.5rem"}}>{error}</Paper> }
@ -128,15 +123,13 @@ const App = () => {
} }
{ user && !user.mailVerified && { user && !user.mailVerified &&
<Route path="/" element={ <Route path="/" element={
<Paper style={{padding: "0.5rem"}}> <Paper style={{padding: "0.5rem"}}>You need to verify your email via the link sent to {user.email}.</Paper>}/>
You need to verify your email via the link sent to {user.email}.
</Paper>}/>
} }
{ !user && !loading && { !user && !loading &&
<Route path="/" element={<SignIn />} /> <Route path="/" element={<SignIn />} />
} }
{ !user && loading && { !user && loading &&
<Route path="/" element={<Paper>Loading...</Paper>} /> <Route path="/" element={<div>Loading...</div>} />
} }
</Routes> </Routes>
</Container> </Container>

View File

@ -14,8 +14,9 @@ import { base } from "./Common.js";
function Dashboard() { function Dashboard() {
const navigate = useNavigate(); const navigate = useNavigate();
const { csrfToken, user, setUser, setError } = useContext(GlobalContext); const { csrfToken, user, setUser } = useContext(GlobalContext);
const [ groups, setGroups ] = useState([]); const [ groups, setGroups ] = useState([]);
const [ error, setError ] = useState(null);
useEffect(() => { useEffect(() => {
if (!user || !csrfToken) { if (!user || !csrfToken) {
@ -44,7 +45,7 @@ function Dashboard() {
} }
effect(); effect();
}, [user, setGroups, csrfToken, setError ]); }, [user, setGroups, csrfToken ]);
const upcomingEvents = groups const upcomingEvents = groups
.filter(group => group.nextEvent.date > Date.now()); .filter(group => group.nextEvent.date > Date.now());
@ -56,51 +57,41 @@ function Dashboard() {
flexDirection: 'column', flexDirection: 'column',
textAlign: 'left' textAlign: 'left'
}}> }}>
<GlobalContext.Provider value={{user, setUser}}> <GlobalContext.Provider value={{user, setUser}}>
{ upcomingEvents && upcomingEvents.length !== 0 && {upcomingEvents && upcomingEvents.length &&
<Paper style={{ <Paper style={{
flexDirection: 'column', flexDirection: 'column',
display: 'flex', display: 'flex',
width: '100%', width: '100%',
marginTop: '0.5rem' marginTop: '0.5rem'
}}> }}>
<div style={{ fontWeight: 'bold', padding: '0.5rem' }}> <div style={{ fontWeight: 'bold', padding: '0.5rem' }}>
Upcoming events Upcoming events
</div> </div>
{ upcomingEvents { upcomingEvents
.map((group) => { .map((group) => {
return <Button key={group.id} return <Button key={group.id}
onClick={() => navigate(`/${group.group}/${group.nextEvent.event}`)} onClick={() => navigate(`/${group.group}/${group.nextEvent.event}`)}
style={{ style={{
flexDirection: 'row', flexDirection: 'column',
display: 'flex', display: 'flex',
justifyContent: 'flex-start', alignItems: 'flex-start',
alignItems: 'center', padding: '0 0.5rem 0.5rem 0.5rem'
textAlign: 'left',
padding: '0 0.5rem 0.5rem 0.5rem'
}
}>
<div style={{
fontWeight: 'bold',
marginRight: '0.5rem',
maxWidth: '10rem',
minWidth: '10rem',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden'
}}>{group.name}</div>
{ group.nextEvent.date &&
<div>Next event <Moment fromNow date={group.nextEvent.date} /> on <Moment format={'MMMM Do YYYY, h: mm: ss a'} date={group.nextEvent.date}/>.</div>
} }
</Button>; }>
}) <div key={group.id} style={{
} fontWeight: 'bold'
}}>{group.name}</div>
{ group.nextEvent.date &&
<div>Next event <Moment fromNow date={group.nextEvent.date} /> on <Moment format={'MMMM Do YYYY, h: mm: ss a'} date={group.nextEvent.date}/>.</div>
}
</Button>;
})
}
</Paper> </Paper>
} }
{ groups && groups.length &&
{ groups && groups.length !== 0 &&
<Paper style={{ <Paper style={{
flexDirection: 'column', flexDirection: 'column',
display: 'flex', display: 'flex',
@ -108,25 +99,25 @@ function Dashboard() {
marginTop: '0.5rem' marginTop: '0.5rem'
}}> }}>
<div style={{ fontWeight: 'bold', padding: '0.5rem' }}> <div style={{ fontWeight: 'bold', padding: '0.5rem' }}>
Groups Groups
</div> </div>
{ groups { groups
.map((group) => { .map((group) => {
return <Button key={group.id} return <Button key={group.id}
onClick={() => navigate(`/${group.group}`)} onClick={() => navigate(`/${group.group}`)}
style={{ style={{
flexDirection: 'column', flexDirection: 'column',
display: 'flex', display: 'flex',
alignItems: 'flex-start', alignItems: 'flex-start',
padding: '0 0.5rem 0.5rem 0.5rem' padding: '0 0.5rem 0.5rem 0.5rem'
} }
}> }>
<div key={group.id} style={{ <div key={group.id} style={{
fontWeight: 'bold' fontWeight: 'bold'
}}>{group.name}</div> }}>{group.name}</div>
</Button>; </Button>;
}) })
} }
</Paper> </Paper>
} }
</GlobalContext.Provider> </GlobalContext.Provider>

View File

@ -2,11 +2,3 @@
text-align: left; text-align: left;
} }
.Event .when {
margin-bottom: 0.25rem;
}
.Event .what {
font-size: 1.25rem;
margin-bottom: 0.5rem;
}

View File

@ -1,12 +1,8 @@
import React, { useState, useEffect, useContext } from "react"; import React, { useState, useEffect, useContext } from "react";
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import MomentUtils from '@date-io/moment'
import { Calendar, MuiPickersUtilsProvider } from '@material-ui/pickers';
import Moment from 'react-moment';
import moment from 'moment';
import { import {
useParams useParams,
useNavigate
} from "react-router-dom"; } from "react-router-dom";
import './Event.css'; import './Event.css';
@ -45,84 +41,68 @@ function Event({ groupId }) {
setEvent(data[0]); setEvent(data[0]);
} }
effect(); effect();
}, [eventId, groupId, csrfToken, event, setError]); }, [eventId, csrfToken, event, setError]);
const showEvent = (event) => <div className="Event"> const showEvent = (event) => {
<div style={{display: 'flex', flexDirection: 'row'}}> const fields = Object.getOwnPropertyNames(event)
<div>Event: </div> .filter(field => event[field])
<div style={{ fontWeight: 'bold' }}>{event.name}</div> .map(field => {
</div> return <div key={field}
<div style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'row' flexDirection: 'row',
}}> alignItems: 'flex-start',
width: '100%'
<div style={{
flexDirection: 'column',
padding: '0.5rem',
display: 'flex',
alignItems: 'flex-start',
textAlign: 'center',
height: 'calc(3rem + 146px)',
minHeight: 'calc(3rem + 146px)',
width: 'calc(162px)',
minWidth: 'calc(162px)',
boxSizing: 'border-box'
}}>
<div style={{
transformOrigin: '0 0',
transform: 'scale(0.5)',
height: 'calc(2rem + 292px)',
margin: '0 calc((-50% * (1 - 0.5)) / 2) calc((-50% * (1 - 0.5)) / 2) 0'
}}>
<Calendar
variant='static'
date={moment(event.date)}
onChange={() => {}}
maxDate={moment(event.date)}
minDate={moment(event.date)}
/>
<div style={{
fontSize: '2em',
fontWeight: 'bold'
}}> }}>
<Moment value={event.date} format='LT' /> <div style={{
display: 'flex',
alignSelf: 'flex-start',
fontWeight: 'bold',
minWidth: '8rem'
}}>{field}</div>
<div style={{
display: 'flex',
alignSelf: 'flex-start',
textAlign: 'left'
}}>
{ typeof event[field] === 'string'
&& event[field].match(/^http.*/)
&& <a href={event[field]}>{field.toUpperCase()}</a> }
{ typeof event[field] === 'string'
&& !event[field].match(/^http.*/)
&& event[field] }
{ typeof event[field] === 'number' && event[field]}
</div> </div>
</div> </div>
} );
</div> return <div key={event.id} style={{
<div> display: 'flex',
<div className='what'>{event.description}</div> flexDirection: 'row',
<div className='when'> alignItems: 'center',
Next event is occurring <Moment fromNow date={event.date} /> on <Moment format={'MMMM Do YYYY, h: mm: ss a'} date={event.date} />. justifyContent: 'space-between',
</div> }}>
{ event.votingBegins === 'day-of' && <div style={{
<div>Voting for location begins the day of the event.</div> display: 'flex',
} flexDirection: 'column',
{ event.votingBegins !== 'day-of' && alignItems: 'center'
<div>Vote any time for the location.</div> }}>{fields}</div>
} </div>;
<div>Voting for location closes at {event.votingCloses}.</div> };
</div>
</div>
</div>;
if (!event) { if (!event) {
return <>No event</>; return <>No event</>;
} }
return ( return (
<div className="Event" style={{ <Paper className="Event" style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
padding: '0.5rem', padding: '0.5rem',
marginTop: '0.25rem', marginTop: '0.25rem',
marginBottom: '0.25rem' marginBottom: '0.25rem'
}}> }}>
<MuiPickersUtilsProvider utils={MomentUtils}> { showEvent(event) }
{ showEvent(event) } </Paper>
</MuiPickersUtilsProvider>
</div>
); );
} }

View File

@ -1,8 +0,0 @@
.DashboardMenu {
display: flex;
flex-direction: column;
}
.DashboardMenu .MenuItem {
padding: 0 0.5rem 0 0.5rem;
}

View File

@ -5,29 +5,23 @@ import Toolbar from '@mui/material/Toolbar';
import IconButton from '@mui/material/IconButton'; import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import Menu from '@mui/material/Menu'; import Menu from '@mui/material/Menu';
import MenuList from '@mui/material/MenuList';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import Container from '@mui/material/Container'; import Container from '@mui/material/Container';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import Popper from '@mui/material/Popper';
import Grow from '@mui/material/Grow';
import Paper from '@mui/material/Paper';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import { import {
useNavigate, useNavigate,
} from "react-router-dom"; } from "react-router-dom";
import Gravatar from 'react-gravatar' import Gravatar from 'react-gravatar'
import "./GoodTimesBar.css";
import { GlobalContext } from "./GlobalContext.js"; import { GlobalContext } from "./GlobalContext.js";
import { base } from "./Common.js"; import { base } from "./Common.js";
const GoodTimesBar = () => { const GoodTimesBar = () => {
const { csrfToken, user, setUser, setError } = useContext(GlobalContext); const { csrfToken, user, setUser } = useContext(GlobalContext);
const [ settings, setSettings ] = useState([]); const [ settings, setSettings ] = useState([]);
const [ menu, setMenu] = useState([]);
const navigate = useNavigate(); const navigate = useNavigate();
const [anchorElNav, setAnchorElNav] = React.useState(null); const [anchorElNav, setAnchorElNav] = React.useState(null);
@ -55,7 +49,6 @@ const GoodTimesBar = () => {
}); });
const data = await res.json(); const data = await res.json();
if (res.status >= 400) { if (res.status >= 400) {
setError(data.message ? data.message : res.statusText);
return; return;
} }
setUser(null); setUser(null);
@ -68,11 +61,9 @@ const GoodTimesBar = () => {
setAnchorElUser(null); setAnchorElUser(null);
navigate('/'); navigate('/');
break; break;
default: break;
} }
console.log(event); console.log(event);
}, [csrfToken, setUser, navigate, setError]); }, [csrfToken, setUser]);
const handleCloseNavMenu = () => { const handleCloseNavMenu = () => {
setAnchorElNav(null); setAnchorElNav(null);
@ -88,23 +79,14 @@ const GoodTimesBar = () => {
if (user) { if (user) {
setSettings([/*'Profile', 'Account',*/ 'Dashboard', 'Sign Out'] setSettings([/*'Profile', 'Account',*/ 'Dashboard', 'Sign Out']
.map((setting) => ( .map((setting) => (
<MenuItem className='MenuItem' <MenuItem key={setting} onClick={(e) => handleUserMenu(e, setting)}>
key={`menu-${setting}`}
onClick={(e) => handleUserMenu(e, setting)}>
<Typography textAlign="center">{setting}</Typography> <Typography textAlign="center">{setting}</Typography>
</MenuItem> </MenuItem>
))); )));
setMenu(['Dashboard']
.map((menu) => (
<MenuItem key={`toolbar-${menu}`} onClick={(e) => handleUserMenu(e, menu)}>
<Typography textAlign="center">{menu}</Typography>
</MenuItem>
)));
} else { } else {
setMenu([]);
setSettings([]); setSettings([]);
} }
}, [user, setSettings, handleUserMenu]); }, [user, setSettings]);
return ( return (
<AppBar position="static"> <AppBar position="static">
@ -159,8 +141,7 @@ const GoodTimesBar = () => {
GOODTIMES GOODTIMES
</Typography> </Typography>
<Box style={{ flexDirection: 'row', marginLeft: '2rem' }} sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}> <Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
{ menu }
</Box> </Box>
<Box sx={{ flexGrow: 0 }}> <Box sx={{ flexGrow: 0 }}>
@ -179,18 +160,22 @@ const GoodTimesBar = () => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Menu <Menu
id="basic-menu" sx={{ mt: '45px' }}
open={Boolean(anchorElUser)} id="menu-appbar"
anchorEl={anchorElUser} anchorEl={anchorElUser}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
keepMounted
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={Boolean(anchorElUser)}
onClose={handleCloseUserMenu} onClose={handleCloseUserMenu}
MenuListProps={{ >
'aria-labelledby': 'basic-button', {settings}
}}>
<MenuList
className="DashboardMenu"
aria-labelledby="composition-button">
{settings}
</MenuList>
</Menu> </Menu>
</>} </>}
</Box> </Box>

View File

@ -106,20 +106,16 @@ function Group() {
effect(); effect();
}, [user, setGroup, groupId, csrfToken, setError]); }, [user, setGroup, groupId, csrfToken, setError]);
if (!group) {
return <></>;
}
return ( return (
<Paper className="Group" style={{ <Paper className="Group" style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
textAlign: 'left', textAlign: 'left',
}}> }}>
<div style={{ fontWeight: 'bold' }}>{group.name}</div>
<Routes> <Routes>
<Route path="/:event" element={<Event groupId={groupId}/>} /> <Route path="/:event" element={<Event groupId={groupId}/>} />
<Route path="/" element={ group && <> <Route path="/" element={ group && <>
<div style={{fontWeight: 'bold'}}>{group.name}</div>
<div>Locations</div> <div>Locations</div>
{ locations.map(location => { locations.map(location =>
<Location location={location} key={location.id}/>) } <Location location={location} key={location.id}/>) }

View File

@ -8,22 +8,6 @@ import {
import './Location.css'; import './Location.css';
import { GlobalContext } from "./GlobalContext.js"; import { GlobalContext } from "./GlobalContext.js";
import { base } from "./Common.js"; import { base } from "./Common.js";
/*
Creating a location:
* Go to Google Maps and find the location you want, selecting the
destination on the map. This updates the URL to include the longitude
and latitude.
* Copy the full URL (CTRL-L, CTRL-A, CTRL-C)
* Paste the URL here: ...
* Backend scrapes Lat and Lon from URL and uses that with the name of
the location to create a marker on the static map view
*/
const CreateLocation = () => {
return <Paper className="CreateLocation">
</Paper>;
}
function Location(props) { function Location(props) {
const propLocation = props.location; const propLocation = props.location;

View File

@ -2,9 +2,6 @@ const originalEvents = [ {
groupId: 1, groupId: 1,
name: 'Tuesday', name: 'Tuesday',
event: 'tuesday', event: 'tuesday',
votingBegins: 'day-of',
votingCloses: '3:00 PM',
description: 'Get together every Tuesday with friends for happy hour!',
date: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */ date: Date.now() + 86400 * 14 * 1000 /* 2 weeks from now */
} ]; } ];
originalEvents.forEach((item, index) => item.id = index + 1); originalEvents.forEach((item, index) => item.id = index + 1);