Make it possible to request a carpool directly from the event page

This commit is contained in:
Michael Fatemi 2021-07-14 08:21:59 -04:00
parent bafc26fb06
commit 66ec0019c1
6 changed files with 149 additions and 54 deletions

View File

@ -1,10 +1,11 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { green, lightgrey } from '../../lib/colors';
import { import {
addOrUpdateEventSignup, addOrUpdateEventSignup,
getEvent,
getEventSignups, getEventSignups,
removeEventSignup, removeEventSignup,
} from '../api'; } from '../api';
import { green, lightgrey } from '../../lib/colors';
import { useMe } from '../hooks'; import { useMe } from '../hooks';
import { IEvent, IEventSignup } from '../types'; import { IEvent, IEventSignup } from '../types';
import UIButton from '../UI/UIButton'; import UIButton from '../UI/UIButton';
@ -14,6 +15,7 @@ import UISecondaryBox from '../UI/UISecondaryBox';
import UISecondaryHeader from '../UI/UISecondaryHeader'; import UISecondaryHeader from '../UI/UISecondaryHeader';
import useThrottle from '../useThrottle'; import useThrottle from '../useThrottle';
import EventCarpools from './EventCarpools'; import EventCarpools from './EventCarpools';
import EventContext from './EventContext';
import EventDetails from './EventDetails'; import EventDetails from './EventDetails';
import EventSignups from './EventSignups'; import EventSignups from './EventSignups';
@ -21,8 +23,14 @@ function GroupName({ group }: { group: IEvent['group'] }) {
return <UILink href={`/groups/${group.id}`}>{group.name}</UILink>; return <UILink href={`/groups/${group.id}`}>{group.name}</UILink>;
} }
export default function Event({ event }: { event: IEvent }) { export default function Event({
const { name, group, formattedAddress, startTime, endTime } = event; id,
initial,
}: {
id: number;
initial?: IEvent;
}) {
const [event, setEvent] = useState<IEvent | null>(initial || null);
const [placeId, setPlaceId] = useState<string | null>(null); const [placeId, setPlaceId] = useState<string | null>(null);
const [interested, setInterested] = useState(false); const [interested, setInterested] = useState(false);
const [updating, setUpdating] = useState(false); const [updating, setUpdating] = useState(false);
@ -36,6 +44,12 @@ export default function Event({ event }: { event: IEvent }) {
}); });
const me = useMe(); const me = useMe();
const refresh = useCallback(() => {
getEvent(id).then(setEvent);
}, [id]);
useEffect(refresh, [refresh]);
useEffect(() => { useEffect(() => {
if (signups === null) { if (signups === null) {
return; return;
@ -43,7 +57,7 @@ export default function Event({ event }: { event: IEvent }) {
const removeSignup = () => { const removeSignup = () => {
if (prev.interested) { if (prev.interested) {
removeEventSignup(event.id) removeEventSignup(id)
.then(() => { .then(() => {
prev.interested = false; prev.interested = false;
}) })
@ -56,13 +70,13 @@ export default function Event({ event }: { event: IEvent }) {
console.log('Adding or updating signup.', prev, { console.log('Adding or updating signup.', prev, {
interested, interested,
placeId, placeId,
eventId: event.id, eventId: id,
signups, signups,
}); });
addOrUpdateEventSignup(event.id, placeId) addOrUpdateEventSignup(id, placeId)
.then(() => { .then(() => {
prev.placeId = placeId; prev.placeId = placeId;
prev.eventId = event.id; prev.eventId = id;
prev.interested = true; prev.interested = true;
}) })
.finally(() => setUpdating(false)); .finally(() => setUpdating(false));
@ -76,16 +90,16 @@ export default function Event({ event }: { event: IEvent }) {
} else { } else {
addOrUpdateSignup(); addOrUpdateSignup();
} }
}, [event.id, interested, placeId, signups, updating]); }, [id, interested, placeId, signups, updating]);
useEffect(() => { useEffect(() => {
getEventSignups(event.id) getEventSignups(id)
.then((signups) => { .then((signups) => {
for (let signup of signups) { for (let signup of signups) {
if (signup.user.id === me?.id) { if (signup.user.id === me?.id) {
setInterested(true); setInterested(true);
setPlaceId(signup.placeId); setPlaceId(signup.placeId);
existingSignup.current.eventId = event.id; existingSignup.current.eventId = id;
existingSignup.current.placeId = signup.placeId; existingSignup.current.placeId = signup.placeId;
existingSignup.current.interested = true; existingSignup.current.interested = true;
} }
@ -93,9 +107,16 @@ export default function Event({ event }: { event: IEvent }) {
setSignups(signups); setSignups(signups);
}) })
.catch(console.error); .catch(console.error);
}, [event.id, me?.id]); }, [id, me?.id]);
if (!event) {
return <UISecondaryBox>Loading...</UISecondaryBox>;
}
const { name, group, formattedAddress, startTime, endTime } = event;
return ( return (
<EventContext.Provider value={{ event, refresh, default: false }}>
<UISecondaryBox> <UISecondaryBox>
<div style={{ textAlign: 'center' }}> <div style={{ textAlign: 'center' }}>
<UISecondaryHeader>{name}</UISecondaryHeader> <UISecondaryHeader>{name}</UISecondaryHeader>
@ -123,12 +144,17 @@ export default function Event({ event }: { event: IEvent }) {
placeId={placeId} placeId={placeId}
/> />
<br /> <br />
<EventCarpools event={event} /> <EventCarpools />
{signups !== null && ( {signups !== null && (
<EventSignups event={event} myPlaceId={placeId} signups={signups} /> <EventSignups
event={event}
myPlaceId={placeId}
signups={signups}
/>
)} )}
</> </>
)} )}
</UISecondaryBox> </UISecondaryBox>
</EventContext.Provider>
); );
} }

View File

@ -1,15 +1,41 @@
// import CallMergeIcon from '@material-ui/icons/CallMerge'; import CancelIcon from '@material-ui/icons/Cancel';
import CheckIcon from '@material-ui/icons/Check';
import EmojiPeopleIcon from '@material-ui/icons/EmojiPeople'; import EmojiPeopleIcon from '@material-ui/icons/EmojiPeople';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useContext, useMemo, useState } from 'react';
import { createCarpool } from '../api'; import { createCarpool } from '../api';
import { lightgrey } from '../../lib/colors'; import { lightgrey } from '../../lib/colors';
import { useMe } from '../hooks'; import { useMe } from '../hooks';
import { IEvent } from '../types'; import { IEvent } from '../types';
import UIButton from '../UI/UIButton'; import UIButton from '../UI/UIButton';
import UILink from '../UI/UILink'; import UILink from '../UI/UILink';
import EventContext from './EventContext';
import {
useCancelCarpoolRequest,
useInvitationState,
useSendCarpoolRequest,
} from '../../state/Notifications/NotificationsHooks';
function CarpoolRow({ carpool }: { carpool: IEvent['carpools'][0] }) { function CarpoolRow({
carpool,
inCarpoolAlready,
}: {
carpool: IEvent['carpools'][0];
inCarpoolAlready: boolean;
}) {
const PADDING = '1rem'; const PADDING = '1rem';
const inviteState = useInvitationState(carpool.id);
const cancelCarpoolRequest = useCancelCarpoolRequest();
const sendCarpoolRequest = useSendCarpoolRequest();
const sendButton = useCallback(() => {
sendCarpoolRequest(carpool.id);
}, [sendCarpoolRequest, carpool.id]);
const cancelButton = useCallback(() => {
cancelCarpoolRequest(carpool.id);
}, [cancelCarpoolRequest, carpool.id]);
return ( return (
<div <div
style={{ style={{
@ -40,14 +66,32 @@ function CarpoolRow({ carpool }: { carpool: IEvent['carpools'][0] }) {
<b>{carpool.extraDistance} miles</b> <b>{carpool.extraDistance} miles</b>
</div> */} </div> */}
{/* </div> */} {/* </div> */}
<EmojiPeopleIcon style={{ fontSize: '2em' }} /> {!inCarpoolAlready && (
<>
{inviteState === 'none' ? (
<EmojiPeopleIcon
style={{ fontSize: '2em', cursor: 'pointer' }}
onClick={sendButton}
/>
) : inviteState === 'requested' ? (
<CancelIcon
style={{ fontSize: '2em', cursor: 'pointer' }}
onClick={cancelButton}
/>
) : (
// inviteState === 'invited
<CheckIcon style={{ fontSize: '2em', cursor: 'pointer' }} />
)}
</>
)}
</div> </div>
); );
} }
type CreationStatus = null | 'pending' | 'completed' | 'errored'; type CreationStatus = null | 'pending' | 'completed' | 'errored';
export default function Carpools({ event }: { event: IEvent }) { export default function Carpools() {
const { event } = useContext(EventContext);
const [creationStatus, setCreationStatus] = useState<CreationStatus>(null); const [creationStatus, setCreationStatus] = useState<CreationStatus>(null);
const [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null); const [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null);
@ -59,7 +103,8 @@ export default function Carpools({ event }: { event: IEvent }) {
), ),
[event.carpools, me.id] [event.carpools, me.id]
); );
const alreadyInCarpool = myCarpool !== undefined; const alreadyInCarpool =
myCarpool !== undefined || creationStatus === 'completed';
const createEmptyCarpool = useCallback(() => { const createEmptyCarpool = useCallback(() => {
setCreationStatus('pending'); setCreationStatus('pending');
@ -107,7 +152,11 @@ export default function Carpools({ event }: { event: IEvent }) {
</> </>
)} )}
{event.carpools.map((carpool) => ( {event.carpools.map((carpool) => (
<CarpoolRow carpool={carpool} key={carpool.id} /> <CarpoolRow
carpool={carpool}
key={carpool.id}
inCarpoolAlready={alreadyInCarpool}
/>
))} ))}
</div> </div>
); );

View File

@ -0,0 +1,12 @@
import { createContext } from 'react';
import { IEvent } from '../types';
const EventContext = createContext({
refresh: () => {
console.error('not implemented: refresh');
},
event: null! as IEvent,
default: true,
});
export default EventContext;

View File

@ -16,7 +16,7 @@ export default function EventPage() {
return ( return (
<> <>
<Header /> <Header />
{event ? <Event event={event} /> : <span>Loading...</span>} {event ? <Event id={id} /> : <span>Loading...</span>}
</> </>
); );
} }

View File

@ -5,7 +5,7 @@ export default function EventStream({ events }: { events: IEvent[] }) {
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}> <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
{events.map((event) => ( {events.map((event) => (
<Event event={event} key={event.name} /> <Event id={event.id} initial={event} key={event.name} />
))} ))}
</div> </div>
); );

View File

@ -2,6 +2,14 @@ import { useMemo } from 'react';
import { useContext } from 'react'; import { useContext } from 'react';
import { NotificationsContext } from './NotificationsProvider'; import { NotificationsContext } from './NotificationsProvider';
export function useSendCarpoolRequest() {
return useContext(NotificationsContext).sendCarpoolRequest;
}
export function useCancelCarpoolRequest() {
return useContext(NotificationsContext).cancelCarpoolRequest;
}
export function useInvitationState( export function useInvitationState(
carpoolId: number carpoolId: number
): 'invited' | 'requested' | 'none' { ): 'invited' | 'requested' | 'none' {