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 { green, lightgrey } from '../../lib/colors';
import {
addOrUpdateEventSignup,
getEvent,
getEventSignups,
removeEventSignup,
} from '../api';
import { green, lightgrey } from '../../lib/colors';
import { useMe } from '../hooks';
import { IEvent, IEventSignup } from '../types';
import UIButton from '../UI/UIButton';
@ -14,6 +15,7 @@ import UISecondaryBox from '../UI/UISecondaryBox';
import UISecondaryHeader from '../UI/UISecondaryHeader';
import useThrottle from '../useThrottle';
import EventCarpools from './EventCarpools';
import EventContext from './EventContext';
import EventDetails from './EventDetails';
import EventSignups from './EventSignups';
@ -21,8 +23,14 @@ function GroupName({ group }: { group: IEvent['group'] }) {
return <UILink href={`/groups/${group.id}`}>{group.name}</UILink>;
}
export default function Event({ event }: { event: IEvent }) {
const { name, group, formattedAddress, startTime, endTime } = event;
export default function Event({
id,
initial,
}: {
id: number;
initial?: IEvent;
}) {
const [event, setEvent] = useState<IEvent | null>(initial || null);
const [placeId, setPlaceId] = useState<string | null>(null);
const [interested, setInterested] = useState(false);
const [updating, setUpdating] = useState(false);
@ -36,6 +44,12 @@ export default function Event({ event }: { event: IEvent }) {
});
const me = useMe();
const refresh = useCallback(() => {
getEvent(id).then(setEvent);
}, [id]);
useEffect(refresh, [refresh]);
useEffect(() => {
if (signups === null) {
return;
@ -43,7 +57,7 @@ export default function Event({ event }: { event: IEvent }) {
const removeSignup = () => {
if (prev.interested) {
removeEventSignup(event.id)
removeEventSignup(id)
.then(() => {
prev.interested = false;
})
@ -56,13 +70,13 @@ export default function Event({ event }: { event: IEvent }) {
console.log('Adding or updating signup.', prev, {
interested,
placeId,
eventId: event.id,
eventId: id,
signups,
});
addOrUpdateEventSignup(event.id, placeId)
addOrUpdateEventSignup(id, placeId)
.then(() => {
prev.placeId = placeId;
prev.eventId = event.id;
prev.eventId = id;
prev.interested = true;
})
.finally(() => setUpdating(false));
@ -76,16 +90,16 @@ export default function Event({ event }: { event: IEvent }) {
} else {
addOrUpdateSignup();
}
}, [event.id, interested, placeId, signups, updating]);
}, [id, interested, placeId, signups, updating]);
useEffect(() => {
getEventSignups(event.id)
getEventSignups(id)
.then((signups) => {
for (let signup of signups) {
if (signup.user.id === me?.id) {
setInterested(true);
setPlaceId(signup.placeId);
existingSignup.current.eventId = event.id;
existingSignup.current.eventId = id;
existingSignup.current.placeId = signup.placeId;
existingSignup.current.interested = true;
}
@ -93,9 +107,16 @@ export default function Event({ event }: { event: IEvent }) {
setSignups(signups);
})
.catch(console.error);
}, [event.id, me?.id]);
}, [id, me?.id]);
if (!event) {
return <UISecondaryBox>Loading...</UISecondaryBox>;
}
const { name, group, formattedAddress, startTime, endTime } = event;
return (
<EventContext.Provider value={{ event, refresh, default: false }}>
<UISecondaryBox>
<div style={{ textAlign: 'center' }}>
<UISecondaryHeader>{name}</UISecondaryHeader>
@ -123,12 +144,17 @@ export default function Event({ event }: { event: IEvent }) {
placeId={placeId}
/>
<br />
<EventCarpools event={event} />
<EventCarpools />
{signups !== null && (
<EventSignups event={event} myPlaceId={placeId} signups={signups} />
<EventSignups
event={event}
myPlaceId={placeId}
signups={signups}
/>
)}
</>
)}
</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 { useCallback, useMemo, useState } from 'react';
import { useCallback, useContext, useMemo, useState } from 'react';
import { createCarpool } from '../api';
import { lightgrey } from '../../lib/colors';
import { useMe } from '../hooks';
import { IEvent } from '../types';
import UIButton from '../UI/UIButton';
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 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 (
<div
style={{
@ -40,14 +66,32 @@ function CarpoolRow({ carpool }: { carpool: IEvent['carpools'][0] }) {
<b>{carpool.extraDistance} miles</b>
</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>
);
}
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 [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null);
@ -59,7 +103,8 @@ export default function Carpools({ event }: { event: IEvent }) {
),
[event.carpools, me.id]
);
const alreadyInCarpool = myCarpool !== undefined;
const alreadyInCarpool =
myCarpool !== undefined || creationStatus === 'completed';
const createEmptyCarpool = useCallback(() => {
setCreationStatus('pending');
@ -107,7 +152,11 @@ export default function Carpools({ event }: { event: IEvent }) {
</>
)}
{event.carpools.map((carpool) => (
<CarpoolRow carpool={carpool} key={carpool.id} />
<CarpoolRow
carpool={carpool}
key={carpool.id}
inCarpoolAlready={alreadyInCarpool}
/>
))}
</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 (
<>
<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 (
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
{events.map((event) => (
<Event event={event} key={event.name} />
<Event id={event.id} initial={event} key={event.name} />
))}
</div>
);

View File

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