From efd596f80ed079ef333f02c2c6ef943054b146af Mon Sep 17 00:00:00 2001 From: Michael Fatemi Date: Tue, 13 Jul 2021 14:10:11 -0400 Subject: [PATCH] make invitation list responsive, add carpool context --- .env | 6 +- src/components/Carpool/Carpool.tsx | 181 +++++++++++++-------- src/components/Carpool/CarpoolDetails.tsx | 31 ++++ src/components/Carpool/CarpoolProvider.tsx | 72 -------- src/components/Carpool/InvitationList.tsx | 73 +++++---- src/components/api.ts | 11 +- src/components/types.ts | 10 +- 7 files changed, 205 insertions(+), 179 deletions(-) create mode 100644 src/components/Carpool/CarpoolDetails.tsx delete mode 100644 src/components/Carpool/CarpoolProvider.tsx diff --git a/.env b/.env index 57a33ce..c2a4a2c 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ -REACT_APP_API_DOMAIN_=http://localhost:5000/ -REACT_APP_API_DOMAIN__=https://wheelshare-altbackend-2efyw.ondigitalocean.app/ +REACT_APP_API_DOMAIN_LOCAL=http://localhost:5000/ +REACT_APP_API_DOMAIN_DOCN=https://wheelshare-altbackend-2efyw.ondigitalocean.app/ +REACT_APP_API_DOMAIN_WWW=https://api.wheelshare.app/ + REACT_APP_API_DOMAIN=https://api.wheelshare.app/ diff --git a/src/components/Carpool/Carpool.tsx b/src/components/Carpool/Carpool.tsx index 7f09c02..c471ba4 100644 --- a/src/components/Carpool/Carpool.tsx +++ b/src/components/Carpool/Carpool.tsx @@ -1,20 +1,27 @@ -import EventIcon from '@material-ui/icons/Event'; -import LocationOnIcon from '@material-ui/icons/LocationOn'; import MailOutlineIcon from '@material-ui/icons/MailOutline'; import PersonAddIcon from '@material-ui/icons/PersonAdd'; - -import { useEffect, useState } from 'react'; +import { createContext } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; - -import { ICarpool } from '../types'; - -import UISecondaryBox from '../UI/UISecondaryBox'; -import MemberList from './MemberList'; -import InvitationList from './InvitationList'; -import UIButton from '../UI/UIButton'; +import { cancelCarpoolInvite, getCarpool, sendCarpoolInvite } from '../api'; import { lightgrey } from '../colors'; -import { getCarpool } from '../api'; +import { ICarpool } from '../types'; +import UIButton from '../UI/UIButton'; +import UISecondaryBox from '../UI/UISecondaryBox'; import useToggle from '../useToggle'; +import CarpoolDetails from './CarpoolDetails'; +import InvitationList from './InvitationList'; +import MemberList from './MemberList'; + +export const CarpoolContext = createContext({ + carpool: null! as ICarpool, + sendInvite: (user: { id: number; name: string }) => { + console.error('not implemented: sendInvite'); + }, + cancelInvite: (user: { id: number; name: string }) => { + console.error('not implemented: cancelInvite'); + }, +}); export default function Carpool() { const id = +useParams<{ id: string }>().id; @@ -26,72 +33,102 @@ export default function Carpool() { const [invitationsOpen, toggleInvitationsOpen] = useToggle(false); + const sendInvite = useCallback( + (user: { id: number; name: string }) => { + if (carpool) { + sendCarpoolInvite(id, user.id) + .then(() => { + setCarpool( + (carpool) => + carpool && { + ...carpool, + invitations: [ + ...carpool.invitations, + { isRequest: false, user }, + ], + } + ); + }) + .catch(console.error); + } else { + console.error( + 'Trying to send invite when carpool has not been loaded.' + ); + } + }, + [carpool, id] + ); + + const cancelInvite = useCallback( + (user: { id: number; name: string }) => { + cancelCarpoolInvite(id, user.id) + .then(() => { + setCarpool( + (carpool) => + carpool && { + ...carpool, + invitations: carpool.invitations.filter( + (invite) => invite.user.id !== user.id + ), + } + ); + }) + .catch(console.error); + }, + [id] + ); + + if (!carpool) { + return <>Loading...; + } + return ( - - {carpool ? ( - <> -

{carpool.name}

-

{carpool.event.name}

-
- {/* Requests */} - - 1 request - - {/* Invitations */} - - Invite - -
- {invitationsOpen && } -
+ + + {carpool ? ( + <> +

{carpool.name}

+

{carpool.event.name}

- - {carpool.event.formattedAddress} + {/* Requests */} + + 1 request + + {/* Invitations */} + + Invite +
-
- - DAWN - DUSK -
-
- - - ) : ( -

Loading

- )} -
+ {invitationsOpen && } + + + + ) : ( +

Loading

+ )} + + ); } diff --git a/src/components/Carpool/CarpoolDetails.tsx b/src/components/Carpool/CarpoolDetails.tsx new file mode 100644 index 0000000..f1fe129 --- /dev/null +++ b/src/components/Carpool/CarpoolDetails.tsx @@ -0,0 +1,31 @@ +import EventIcon from '@material-ui/icons/Event'; +import LocationOnIcon from '@material-ui/icons/LocationOn'; + +import { ICarpool } from '../types'; + +export default function CarpoolDetails({ carpool }: { carpool: ICarpool }) { + return ( +
+
+ + {carpool.event.formattedAddress} +
+
+ + DAWN - DUSK +
+
+ ); +} diff --git a/src/components/Carpool/CarpoolProvider.tsx b/src/components/Carpool/CarpoolProvider.tsx deleted file mode 100644 index 8e11b4f..0000000 --- a/src/components/Carpool/CarpoolProvider.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { createContext, ReactNode, useMemo, useState } from 'react'; -import * as immutable from 'immutable'; -import { useEffect } from 'react'; -import { getCarpool } from '../api'; - -class Member extends immutable.Record({ - id: 0, - name: '', -}) {} - -class Invitation extends immutable.Record({ - user: new Member(), - isRequest: false, -}) {} - -class CarpoolState extends immutable.Record({ - id: 0, - name: '', - members: immutable.List(), - invitations: immutable.List(), -}) {} - -type Subscriber = (state: CarpoolState) => void; - -class CarpoolSDK { - private _state = new CarpoolState(); - get state() { - return this._state; - } - set state(state: CarpoolState) { - this._state = state; - } - private subscribers: Subscriber[] = []; - subscribe(subscriber: Subscriber) { - this.subscribers.push(subscriber); - return () => { - this.subscribers = this.subscribers.filter((s) => s !== subscriber); - }; - } -} - -export const CarpoolContext = createContext({ - sdk: new CarpoolSDK(), - carpool: new CarpoolState(), -}); - -export default function CarpoolProvider({ - id, - children, -}: { - id: number; - children: ReactNode; -}) { - const [carpool, setCarpool] = useState(new CarpoolState()); - const sdk = useMemo(() => new CarpoolSDK(), []); - - useEffect(() => { - getCarpool(id).then((carpool) => {}); - }, [id]); - - useEffect(() => { - const remove = sdk.subscribe(setCarpool); - - return () => remove(); - }, [sdk]); - - return ( - - {children} - - ); -} diff --git a/src/components/Carpool/InvitationList.tsx b/src/components/Carpool/InvitationList.tsx index 34042d1..b5d3a7e 100644 --- a/src/components/Carpool/InvitationList.tsx +++ b/src/components/Carpool/InvitationList.tsx @@ -1,19 +1,20 @@ +import CancelIcon from '@material-ui/icons/Cancel'; +import PersonAddIcon from '@material-ui/icons/PersonAdd'; import { useMemo } from 'react'; -import { useEffect, useState } from 'react'; +import { useContext, useEffect, useState } from 'react'; import { getEventSignups } from '../api'; -import { ICarpool, IEventSignup } from '../types'; +import { useMe } from '../hooks'; +import { IEventSignup } from '../types'; +import { CarpoolContext } from './Carpool'; function InvitationRow({ - carpoolId, - userId, - userName, + user, isInvited, }: { - carpoolId: number; - userId: number; - userName: string; + user: { id: number; name: string }; isInvited: boolean; }) { + const { sendInvite, cancelInvite } = useContext(CarpoolContext); return (
- {userName} + {user.name} + {isInvited ? ( + cancelInvite(user)} + style={{ cursor: 'pointer' }} + /> + ) : ( + sendInvite(user)} + style={{ cursor: 'pointer' }} + /> + )}
); } -export default function InvitationList({ carpool }: { carpool: ICarpool }) { +export default function InvitationList() { + const { carpool } = useContext(CarpoolContext); + const me = useMe()!; + const eventId = carpool.event.id; const [availableSignups, setAvailableSignups] = useState(null); useEffect(() => { - getEventSignups(eventId).then(setAvailableSignups); - }, [eventId]); + getEventSignups(eventId).then((signups) => + setAvailableSignups(signups.filter((signup) => signup.user.id !== me.id)) + ); + }, [eventId, me.id]); - const existingSignups = useMemo( + const invitedUserIDs = useMemo( () => new Set( carpool.invitations .filter((invitation) => !invitation.isRequest) .map((invitation) => invitation.user.id) ), - [carpool] + [carpool.invitations] ); const availableSignupsAlreadyInvited = useMemo( () => availableSignups ? availableSignups.filter((signup) => - existingSignups.has(signup.userId) + invitedUserIDs.has(signup.user.id) ) : null, - [availableSignups, existingSignups] + [availableSignups, invitedUserIDs] ); const availableSignupsNotInvited = useMemo( () => availableSignups ? availableSignups.filter( - (signup) => !existingSignups.has(signup.userId) + (signup) => !invitedUserIDs.has(signup.user.id) ) : null, - [availableSignups, existingSignups] + [availableSignups, invitedUserIDs] ); return ( @@ -80,24 +97,20 @@ export default function InvitationList({ carpool }: { carpool: ICarpool }) { >

Invite Somebody

{availableSignups === null && 'Loading'} + {availableSignupsAlreadyInvited?.map((signup) => ( + + ))} {availableSignupsNotInvited?.map((signup) => ( ))} - {availableSignupsAlreadyInvited?.map((signup) => ( - - ))} ); } diff --git a/src/components/api.ts b/src/components/api.ts index 0878126..0b1b643 100644 --- a/src/components/api.ts +++ b/src/components/api.ts @@ -15,13 +15,14 @@ async function post(path: string, data: any) { return await res.json(); } -async function delete$(path: string) { +async function delete$(path: string, body?: any) { const res = await fetch(base + path, { method: 'delete', headers: { Authorization: 'Bearer ' + localStorage.getItem('session_token'), 'Content-Type': 'application/json', }, + body: body ? JSON.stringify(body) : undefined, }); return await res.json(); } @@ -176,3 +177,11 @@ export async function createCarpool({ }) { return await post('/carpools/', { eventId, name }); } + +export async function sendCarpoolInvite(carpoolId: number, userId: number) { + return await post('/carpools/' + carpoolId + '/invite', { userId }); +} + +export async function cancelCarpoolInvite(carpoolId: number, userId: number) { + return await delete$('/carpools/' + carpoolId + '/invite', { userId }); +} diff --git a/src/components/types.ts b/src/components/types.ts index fc80a5f..0fa7130 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -33,7 +33,13 @@ export type ICarpool = { id: number; name: string; }[]; - invitations: IInvitation[]; + invitations: { + user: { + id: number; + name: string; + }; + isRequest: boolean; + }[]; }; /** @@ -81,7 +87,7 @@ export type IEvent = { export type IEventSignup = { eventId: number; - userId: number; + // userId: number; user: { id: number; name: string;