diff --git a/src/components/App.tsx b/src/components/App.tsx index fca7101..686ef68 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,6 +1,9 @@ -import WheelShare from './WheelShare'; +import { lazy, Suspense, useContext } from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; -import { lazy, Suspense } from 'react'; +import AuthenticationContext from './Authentication/AuthenticationContext'; +import logout from './Authentication/logout'; +import UIButton from './UIButton'; +import WheelShare from './WheelShare'; const Authenticator = lazy(() => import('./Authentication/Authenticator')); const Group = lazy(() => import('./Group')); @@ -11,23 +14,37 @@ const ION_AUTHORIZATION_ENDPOINT = dev : 'https://ion.tjhsst.edu/oauth/authorize?response_type=code&client_id=rNa6n9YSg8ftINdyVPpUsaMuxNbHLo9dh1OsOktR&scope=read&redirect_uri=https%3A%2F%2Fwheelshare.space%2Fauth%2Fion%2Fcallback'; export default function App() { + const { isLoggedIn, user } = useContext(AuthenticationContext); return ( - <> - Login Link for Testing Oauth -
- - - - - - - - - -
- +
+ {isLoggedIn ? ( +
+ {user!.name}{' '} + + Log out + +
+ ) : ( + Log in + )} + + + + + + + + + +
); } diff --git a/src/components/Authentication/AuthenticationContext.ts b/src/components/Authentication/AuthenticationContext.ts index 5c267e9..4e0f362 100644 --- a/src/components/Authentication/AuthenticationContext.ts +++ b/src/components/Authentication/AuthenticationContext.ts @@ -1,8 +1,14 @@ import { createContext } from 'react'; +export type User = { + name: string; + id: number; + email?: string; +}; + export type AuthState = { isLoggedIn: boolean | null; - user: Carpool.User | null; + user: User | null; /** * Function that can be used to trigger an auth state refresh. diff --git a/src/components/Authentication/AuthenticationWrapper.tsx b/src/components/Authentication/AuthenticationWrapper.tsx index e0c7716..d438d1f 100644 --- a/src/components/Authentication/AuthenticationWrapper.tsx +++ b/src/components/Authentication/AuthenticationWrapper.tsx @@ -16,16 +16,21 @@ export default function AuthenticationWrapper({ }); const refreshAuthState = useCallback(() => { - if (sessionToken) { - getMe().then((user) => { - if (user) { - setAuthState({ isLoggedIn: true, user, refreshAuthState }); - } else { - setAuthState({ isLoggedIn: false, user: null, refreshAuthState }); - } - }); - } else { + const loggedOut = () => setAuthState({ isLoggedIn: false, user: null, refreshAuthState }); + + if (sessionToken) { + getMe() + .then((user) => { + if (user) { + setAuthState({ isLoggedIn: true, user, refreshAuthState }); + } else { + loggedOut(); + } + }) + .catch(loggedOut); + } else { + loggedOut(); } }, [sessionToken]); diff --git a/src/components/Authentication/Authenticator.tsx b/src/components/Authentication/Authenticator.tsx index 96e7ddf..83f9313 100644 --- a/src/components/Authentication/Authenticator.tsx +++ b/src/components/Authentication/Authenticator.tsx @@ -10,22 +10,31 @@ export default function Authenticator() { const { refreshAuthState } = useContext(AuthenticationContext); const [status, setStatus] = useState<'pending' | 'errored' | 'authenticated'>('pending'); + const [token, setToken] = useState(null); useEffect(() => { createSession(code!) .then((data) => { if (data.status === 'success') { + console.log('Success! Token:', data.token); + setToken(data.token); localStorage.setItem('session_token', data.token); - refreshAuthState && refreshAuthState(); setStatus('authenticated'); } else { + console.log('Authentication failure.'); + setToken(null); + localStorage.removeItem('session_token'); setStatus('errored'); } }) .catch(() => { setStatus('errored'); }); - }, [code, provider, refreshAuthState]); + }, [code, provider]); + + useEffect(() => { + refreshAuthState && refreshAuthState(); + }, [token, refreshAuthState]); switch (status) { case 'authenticated': diff --git a/src/components/Authentication/logout.ts b/src/components/Authentication/logout.ts new file mode 100644 index 0000000..9538883 --- /dev/null +++ b/src/components/Authentication/logout.ts @@ -0,0 +1,4 @@ +export default function logout() { + localStorage.removeItem('session_token'); + window.location.href = '/'; +} diff --git a/src/components/Event.tsx b/src/components/Event.tsx index b02014a..e3ecc69 100644 --- a/src/components/Event.tsx +++ b/src/components/Event.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { post, removeEventSignup } from './api'; +import { addEventSignup, removeEventSignup } from './api'; import { green, lightgrey } from './colors'; import latlongdist, { R_miles } from './latlongdist'; import UIButton from './UIButton'; @@ -313,15 +313,14 @@ export default function Event({ event }: { event: IEvent }) { if ( interested === true && - (prev.placeId !== placeId || prev.eventId !== event.id) + (prev.placeId !== placeId || prev.eventId !== event.id) && + placeId !== null ) { prev.placeId = placeId; prev.eventId = event.id; prev.interested = true; - post(`/events/${event.id}/signup`, { - placeId, - }).finally(() => setUpdating(false)); + addEventSignup(event.id, placeId!).finally(() => setUpdating(false)); return; } }, [event.id, interested, placeId, updating]); diff --git a/src/components/EventCreator.tsx b/src/components/EventCreator.tsx index 2b1a2f0..547f5ca 100644 --- a/src/components/EventCreator.tsx +++ b/src/components/EventCreator.tsx @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction, useCallback, useState } from 'react'; -import { post } from './api'; +import { createEvent } from './api'; import { toggleBit } from './bits'; import { green, lightgrey } from './colors'; import { IGroup } from './Group'; @@ -120,7 +120,7 @@ export default function EventCreator({ group }: { group: IGroup }) { !durationIsNegative && !creating; - const createEvent = useCallback(() => { + const onClickedCreateEvent = useCallback(() => { if (!creating) { if (startTime === null) { console.warn( @@ -129,12 +129,19 @@ export default function EventCreator({ group }: { group: IGroup }) { return; } + if (placeId === null) { + console.warn( + 'Tried tro create an event where the placeId was unspecified.' + ); + return; + } + const duration = endTime !== null ? (endTime.getTime() - startTime.getTime()) / 60 : 0; setCreating(true); - post('/events', { + createEvent({ name, startTime, duration, @@ -143,7 +150,6 @@ export default function EventCreator({ group }: { group: IGroup }) { placeId, daysOfWeek, }) - .then((response) => response.json()) .then(({ id }) => { setCreatedEventId(id); }) @@ -211,7 +217,7 @@ export default function EventCreator({ group }: { group: IGroup }) { )} {createdEventId === -1 ? ( {creating ? 'Creating event' : 'Create event'} diff --git a/src/components/GroupCreator.tsx b/src/components/GroupCreator.tsx index f967386..660935f 100644 --- a/src/components/GroupCreator.tsx +++ b/src/components/GroupCreator.tsx @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import { post } from './api'; +import { createGroup } from './api'; import UIButton from './UIButton'; import UILink from './UILink'; import UISecondaryBox from './UISecondaryBox'; @@ -11,13 +11,10 @@ export default function GroupCreator() { useState(null); const [createdGroupId, setCreatedGroupId] = useState(0); const [creating, setCreating] = useState(false); - const createGroup = useCallback(() => { + const onClickedCreateGroup = useCallback(() => { if (!creating) { setCreating(true); - post('/groups', { - name, - }) - .then((res) => res.json()) + createGroup(name) .then(({ id }) => { setCreationSuccessful(true); setCreatedGroupId(id); @@ -36,7 +33,7 @@ export default function GroupCreator() { Name {creating ? 'Creating group' : 'Create group'} diff --git a/src/components/api.ts b/src/components/api.ts index a9d04a0..b704b12 100644 --- a/src/components/api.ts +++ b/src/components/api.ts @@ -1,26 +1,32 @@ -export async function post(path: string, data: any) { +async function post(path: string, data: any) { const res = await fetch('http://localhost:5000/api' + path, { method: 'post', body: JSON.stringify(data), headers: { + Authorization: 'Bearer ' + localStorage.getItem('session_token'), 'Content-Type': 'application/json', }, }); return await res.json(); } -export async function delete$(path: string) { +async function delete$(path: string) { const res = await fetch('http://localhost:5000/api' + path, { method: 'delete', headers: { + Authorization: 'Bearer ' + localStorage.getItem('session_token'), 'Content-Type': 'application/json', }, }); return await res.json(); } -export async function get(path: string) { - const res = await fetch('http://localhost:5000/api' + path); +async function get(path: string) { + const res = await fetch('http://localhost:5000/api' + path, { + headers: { + Authorization: 'Bearer ' + localStorage.getItem('session_token'), + }, + }); return await res.json(); } @@ -41,10 +47,47 @@ export async function getPlaceDetails( return await get('/place/' + placeId); } +export async function addEventSignup(eventId: number, placeId: string) { + post(`/events/${eventId}/signup`, { + placeId, + }); +} + export async function removeEventSignup(eventId: number) { return await delete$(`/events/${eventId}/signup`); } +export async function createEvent({ + name, + startTime, + duration, + endDate, + groupId, + placeId, + daysOfWeek, +}: { + name: string; + startTime: Date; + duration: number; + endDate: Date | null; + groupId: number; + placeId: string; + daysOfWeek: number; +}) { + const { id } = await post('/events', { + name, + startTime, + duration, + endDate, + groupId, + placeId, + daysOfWeek, + }); + return { + id, + }; +} + export async function getEvents() { return await get('/events'); } @@ -65,6 +108,15 @@ export async function deleteGroup(id: number) { return await delete$('/groups/' + id); } +export async function createGroup(name: string) { + const result = await post('/groups', { + name, + }); + return { + id: result.id, + }; +} + export async function getMe() { return await get('/users/@me'); } diff --git a/src/index.js b/src/index.js index a307dc0..8b15e68 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,13 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './components/App'; import reportWebVitals from './reportWebVitals'; +import AuthenticationWrapper from './components/Authentication/AuthenticationWrapper'; ReactDOM.render( - + + + , document.getElementById('root') );