This commit is contained in:
Joshua Hsueh 2021-04-10 20:40:52 -04:00
commit 0cb37c41d1
7 changed files with 242 additions and 239 deletions

2
.gitignore vendored
View File

@ -1,7 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies # dependencies
/node_modules node_modules
/.pnp /.pnp
.pnp.js .pnp.js

View File

@ -7,7 +7,7 @@ export default function AuthenticationWrapper({
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const sessionId = localStorage.getItem('session_id'); const sessionToken = localStorage.getItem('session_token');
// Prevent race conditions // Prevent race conditions
const [authState, setAuthState] = useState<AuthState>({ const [authState, setAuthState] = useState<AuthState>({
isLoggedIn: null, isLoggedIn: null,
@ -16,7 +16,7 @@ export default function AuthenticationWrapper({
}); });
const refreshAuthState = useCallback(() => { const refreshAuthState = useCallback(() => {
if (sessionId) { if (sessionToken) {
getMe().then((user) => { getMe().then((user) => {
if (user) { if (user) {
setAuthState({ isLoggedIn: true, user, refreshAuthState }); setAuthState({ isLoggedIn: true, user, refreshAuthState });
@ -27,7 +27,7 @@ export default function AuthenticationWrapper({
} else { } else {
setAuthState({ isLoggedIn: false, user: null, refreshAuthState }); setAuthState({ isLoggedIn: false, user: null, refreshAuthState });
} }
}, [sessionId]); }, [sessionToken]);
useEffect(() => { useEffect(() => {
refreshAuthState(); refreshAuthState();

View File

@ -1,11 +1,13 @@
import { useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
import { Redirect, useLocation, useParams } from 'react-router-dom'; import { Redirect, useLocation, useParams } from 'react-router-dom';
import { API_ENDPOINT } from '../api/api'; import { API_ENDPOINT } from '../api/api';
import AuthenticationContext from './AuthenticationContext';
export default function Authenticator() { export default function Authenticator() {
const { provider } = useParams<{ provider: string }>(); const { provider } = useParams<{ provider: string }>();
const query = new URLSearchParams(useLocation().search); const query = new URLSearchParams(useLocation().search);
const code = query.get('code'); const code = query.get('code');
const { refreshAuthState } = useContext(AuthenticationContext);
const [status, setStatus] = useState<'pending' | 'errored' | 'authenticated'>( const [status, setStatus] = useState<'pending' | 'errored' | 'authenticated'>(
'pending' 'pending'
); );
@ -25,6 +27,7 @@ export default function Authenticator() {
response.json().then((json) => { response.json().then((json) => {
if (json.status === 'success') { if (json.status === 'success') {
localStorage.setItem('session_token', json.token); localStorage.setItem('session_token', json.token);
refreshAuthState && refreshAuthState();
setStatus('authenticated'); setStatus('authenticated');
} else { } else {
setStatus('errored'); setStatus('errored');
@ -34,7 +37,7 @@ export default function Authenticator() {
.catch(() => { .catch(() => {
setStatus('errored'); setStatus('errored');
}); });
}, [code, provider]); }, [code, provider, refreshAuthState]);
switch (status) { switch (status) {
case 'authenticated': case 'authenticated':

View File

@ -1,91 +1,72 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { API_ENDPOINT } from '../api/api'; import Typography from '@material-ui/core/Typography';
import { makeAPIGetCall } from '../api/utils';
const maybePluralize = (count: number, noun: string, suffix = 's') => const maybePluralize = (count: number, noun: string, suffix = 's') =>
`${count} ${noun}${count !== 1 ? suffix : ''}`; `${count} ${noun}${count !== 1 ? suffix : ''}`;
const Group = () => { const SAMPLE_POOLS: Carpool.Pool[] = [
{
id: '1234',
title: 'TJ Carpool',
description: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participant_ids: [],
comments: [
{
author_id: 'joshua_hsueh',
body: 'What is the covid vaccination status of all the participants?',
id: 'comment_0',
},
],
driver_id: 'michael',
create_time: '0',
update_time: '0',
group_id: 'test_group',
status: 'pending',
direction: 'dropoff',
author_id: 'michael',
type: 'offer',
},
];
export default function Group() {
// eslint-disable-next-line // eslint-disable-next-line
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [pools, setPools] = useState([ const [group, setGroup] = useState<Carpool.Group>();
{ const [pools, setPools] = useState<Carpool.Pool[]>(SAMPLE_POOLS);
id: 1,
pool_title: 'TJ Carpool',
pool_text: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participants: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
{
id: 2,
pool_title: 'TJ Carpool',
pool_text: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participants: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
{
id: 3,
pool_title: 'TJ Carpool',
pool_text: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participants: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
{
id: 4,
pool_title: 'TJ Carpool',
pool_text: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participants: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
]);
useEffect(() => { useEffect(() => {
console.log(process.env); makeAPIGetCall('/group', { id }).then((res) => setGroup(res.data.data));
fetch(`${API_ENDPOINT}/my_pools`) }, [id]);
.then((response) => response.json())
.then((json) => { if (!group) {
if (json) { return <h1 style={{ textAlign: 'center' }}>Loading</h1>;
setPools(json.data);
} }
});
}, []);
return ( return (
<div className="bg-dark" style={{ minHeight: '100vh' }}> <div
<h1 style={{
className="d-flex justify-content-center p-4" width: '100%',
style={{ backgroundColor: '#F1EAE8', fontFamily: 'Impact' }} display: 'flex',
flexDirection: 'column',
padding: '1rem',
}}
> >
<Typography variant="h1" align="center">
Group {group.id}
</Typography>
<Typography variant="h3" align="center">
Pools Pools
</h1> </Typography>
<a <a className="btn btn-large btn-success" href="/create_pool">
className="btn btn-large btn-success"
href="/create_pool"
style={{ fontFamily: 'Courier New' }}
>
Create Pool Create Pool
</a> </a>
<div className="container" style={{ fontFamily: 'Courier New' }}> <div className="container">
<br></br> <br></br>
{pools.map((pool, index) => { {pools.map((pool, index) => {
let background; let background;
@ -100,10 +81,10 @@ const Group = () => {
style={{ backgroundColor: background }} style={{ backgroundColor: background }}
> >
<a href={'/Pool/' + pool.id} className="card-title"> <a href={'/Pool/' + pool.id} className="card-title">
{pool.pool_title} {pool.title}
</a> </a>
<p className="text-left"> <p className="text-left">
Capacity: {pool.participants.length} / {pool.capacity} Capacity: {pool.participant_ids.length} / {pool.capacity}
</p> </p>
<p className="text-left">Start Time: {pool.start_time}</p> <p className="text-left">Start Time: {pool.start_time}</p>
<p className="text-left">End Time: {pool.end_time}</p> <p className="text-left">End Time: {pool.end_time}</p>
@ -116,6 +97,4 @@ const Group = () => {
</div> </div>
</div> </div>
); );
}; }
export default Group;

View File

@ -22,6 +22,7 @@ const useStyles = makeStyles({
}); });
const navLinks = [ const navLinks = [
{ title: `Profile`, path: `/profile` }, { title: `Profile`, path: `/profile` },
{ title: `Create Pool`, path: `/create_pool` },
// { title: `Groups`, path: `/groups` }, // { title: `Groups`, path: `/groups` },
// { title: `MyGroups`, path: `/mygroups` }, // { title: `MyGroups`, path: `/mygroups` },
]; ];

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef, useContext } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card'; import Card from '@material-ui/core/Card';
@ -6,10 +6,10 @@ import Textarea from '@material-ui/core/TextareaAutosize';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Comment from './Comment'; import Comment from './Comment';
import { makeAPIPostCall } from '../api/utils'; import { makeAPIPostCall } from '../api/utils';
import AuthenticationContext from './AuthenticationContext';
export default function Pool({ registered = false }: { registered?: boolean }) { // eslint-disable-next-line
const id = useParams<{ id: string }>().id; const SAMPLE_POOL = {
const [pool, setPool] = useState<Carpool.Pool>({
id: '123', id: '123',
title: 'TJ Carpool', title: 'TJ Carpool',
description: 'Carpool from TJ track to homes', description: 'Carpool from TJ track to homes',
@ -32,7 +32,12 @@ export default function Pool({ registered = false }: { registered?: boolean }) {
direction: 'dropoff', direction: 'dropoff',
author_id: 'michael', author_id: 'michael',
type: 'offer', type: 'offer',
}); };
export default function Pool() {
const id = useParams<{ id: string }>().id;
const [pool, setPool] = useState<Carpool.Pool>();
const { user } = useContext(AuthenticationContext);
const commentTextareaRef = useRef<HTMLTextAreaElement>(null); const commentTextareaRef = useRef<HTMLTextAreaElement>(null);
const [commentStatus, setCommentStatus] = useState< const [commentStatus, setCommentStatus] = useState<
@ -69,6 +74,20 @@ export default function Pool({ registered = false }: { registered?: boolean }) {
[] []
); );
const onRegister = useCallback(() => {
if (user) {
let userID = user.id;
makeAPIPostCall('/join_pool', { id }).then(() => {
if (pool) {
setPool({
...pool,
participant_ids: [...pool.participant_ids, userID],
});
}
});
}
}, [user, id, pool]);
useEffect(() => { useEffect(() => {
fetch(`${process.env.REACT_APP_API_ENDPOINT}/pool/${id}`) fetch(`${process.env.REACT_APP_API_ENDPOINT}/pool/${id}`)
.then((response) => response.json()) .then((response) => response.json())
@ -81,6 +100,8 @@ export default function Pool({ registered = false }: { registered?: boolean }) {
return ( return (
<Card style={{ margin: '3rem auto', padding: '1rem 1rem', width: '50%' }}> <Card style={{ margin: '3rem auto', padding: '1rem 1rem', width: '50%' }}>
{pool && (
<>
<Typography variant="h2" align="center"> <Typography variant="h2" align="center">
{pool.title} {pool.title}
</Typography> </Typography>
@ -94,13 +115,18 @@ export default function Pool({ registered = false }: { registered?: boolean }) {
<b>End Time</b>: {pool.end_time} <b>End Time</b>: {pool.end_time}
</Typography> </Typography>
<Typography variant="body1">{pool.description}</Typography> <Typography variant="body1">{pool.description}</Typography>
{user && (
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
style={{ marginTop: '0.5rem' }} style={{ marginTop: '0.5rem' }}
onClick={onRegister}
> >
{registered ? 'Unregister' : 'Register'} {pool.participant_ids.includes(user.id)
? 'Unregister'
: 'Register'}
</Button> </Button>
)}
<hr /> <hr />
<Textarea <Textarea
cols={80} cols={80}
@ -125,6 +151,8 @@ export default function Pool({ registered = false }: { registered?: boolean }) {
<Comment comment={comment} key={comment.id} /> <Comment comment={comment} key={comment.id} />
))} ))}
</div> </div>
</>
)}
</Card> </Card>
); );
} }

View File

@ -1,103 +1,95 @@
import { CenterFocusStrong } from '@material-ui/icons'; import Button from '@material-ui/core/Button';
import React, { useState, useEffect } from 'react'; import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import { useContext, useEffect, useState } from 'react';
import { makeAPIGetCall } from '../api/utils';
import AuthenticationContext from './AuthenticationContext';
const maybePluralize = (count: number, noun: string, suffix = 's') => const useStyles = makeStyles({
`${count} ${noun}${count !== 1 ? suffix : ''}`; root: {
maxWidth: 345,
},
media: {
height: 140,
},
});
const Profile = () => { const Profile = () => {
const [state, setState] = useState({ const { user } = useContext(AuthenticationContext);
user: { // const [groups, setGroups] = useState<Carpool.Group[]>([]);
username: 'HyperionLegion', const [pools, setPools] = useState<Carpool.Pool[]>([]);
}, const classes = useStyles();
pools: [
{
title: 'TJ Carpool',
description: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
id: 1,
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participant_ids: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
{
title: 'TJ Carpool',
description: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
id: 2,
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participant_ids: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
{
title: 'TJ Carpool',
description: 'Carpool from TJ track to homes',
start_time: '4/10/2021 3:00 PM',
id: 3,
end_time: '4/10/2021 4:00 PM',
capacity: 2,
participant_ids: [],
comments: [
'What is the covid vaccination status of all the participants?',
],
},
],
groups: [],
});
const callAPI = () => {
fetch(`${process.env.REACT_APP_API_ENDPOINT}/profile/`)
.then((response) => response.json())
.then((data) => {
if (data !== undefined) {
setState(data);
}
});
};
useEffect(() => { useEffect(() => {
callAPI(); makeAPIGetCall('/my_pools').then((res) => {
if (res.data.data) setPools(res.data.data);
});
}, []); }, []);
if (!user) {
return <h1>Please Sign In</h1>;
}
return ( return (
<div className="bg-dark" style={{ minHeight: '100vh' }}> <div
className=""
style={{ minHeight: '100vh', backgroundColor: '#F1EAE8' }}
>
<h1 <h1
className="d-flex justify-content-center p-4" className="d-flex justify-content-center p-4"
style={{ backgroundColor: '#F1EAE8', fontFamily: 'Courier New' }} style={{ backgroundColor: '#F1EAE8' }}
> >
Profile Profile
</h1> </h1>
<div className="container" style={{ fontFamily: 'Courier New', alignSelf: 'center' }}> <div className="container">
<h2 style={{color: '#FFFFFF'}}><u>{state.user.username}'s Pools</u></h2> <h2>
<u>{user.username}'s Pools</u>
</h2>
<div className=""> <div className="">
{state.pools.map((pool, index) => { {pools.map((pool) => {
let background;
if (index % 2 === 0) {
background = '#F1EAE8';
} else {
background = '#FFFFFF';
}
return ( return (
<div <Card
className="card card-body text-left" className={classes.root + 'd-inline-flex'}
style={{ backgroundColor: background }} style={{ margin: '0.5rem' }}
> >
<a href={'/Pool/' + pool.id} className="card-title"> <CardActionArea href={'/pool/' + pool.id}>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{pool.title} {pool.title}
</a> </Typography>
<p className="text-left"> <Typography
Capacity: {pool.participant_ids.length} / {pool.capacity} variant="body2"
</p> color="textSecondary"
<p className="text-left">Start Time: {pool.start_time}</p> component="p"
<p className="text-left">End Time: {pool.end_time}</p> >
<p className="" style={{color: '#9E6105'}}> {pool.description}
{maybePluralize(pool.comments.length, 'comment')} </Typography>
</p> </CardContent>
</div> </CardActionArea>
<CardActions>
<Button
size="small"
color="primary"
onClick={() => {
let link: string = 'localhost:3000/pool/' + pool.id;
navigator.clipboard.writeText(link);
}}
>
Share
</Button>
<Button
href={'/pool/' + pool.id}
size="small"
color="primary"
>
Learn More
</Button>
</CardActions>
</Card>
); );
})} })}
</div> </div>