mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-21 11:20:17 -04:00
feature: creating carpools with invitees, pt.1
This commit is contained in:
parent
7de92f4773
commit
8b8ef46b3f
|
@ -1,3 +1,4 @@
|
|||
import * as immutable from 'immutable';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { green, lightgrey } from '../../lib/colors';
|
||||
import {
|
||||
|
@ -35,6 +36,7 @@ export default function Event({
|
|||
const [interested, setInterested] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [signups, setSignups] = useState<IEventSignup[] | null>(null);
|
||||
const [hasCarpool, setHasCarpool] = useState(false);
|
||||
const toggleInterested = useCallback(() => setInterested((i) => !i), []);
|
||||
const toggleInterestedThrottled = useThrottle(toggleInterested, 500);
|
||||
const existingSignup = useRef({
|
||||
|
@ -44,6 +46,18 @@ export default function Event({
|
|||
});
|
||||
const me = useMe();
|
||||
|
||||
const [tentativeInvites, setTentativeInvites] = useState(
|
||||
immutable.Set<number>()
|
||||
);
|
||||
|
||||
const addTentativeInvite = useCallback((userId: number) => {
|
||||
setTentativeInvites((t) => t.add(userId));
|
||||
}, []);
|
||||
|
||||
const removeTentativeInvite = useCallback((userId: number) => {
|
||||
setTentativeInvites((t) => t.delete(userId));
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
getEvent(id).then(setEvent);
|
||||
}, [id]);
|
||||
|
@ -116,7 +130,19 @@ export default function Event({
|
|||
const { name, group, formattedAddress, startTime, endTime } = event;
|
||||
|
||||
return (
|
||||
<EventContext.Provider value={{ event, refresh, default: false }}>
|
||||
<EventContext.Provider
|
||||
value={{
|
||||
event,
|
||||
refresh,
|
||||
default: false,
|
||||
addTentativeInvite,
|
||||
removeTentativeInvite,
|
||||
tentativeInvites,
|
||||
signups,
|
||||
hasCarpool,
|
||||
setHasCarpool,
|
||||
}}
|
||||
>
|
||||
<UISecondaryBox>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<UISecondaryHeader>{name}</UISecondaryHeader>
|
||||
|
@ -146,11 +172,7 @@ export default function Event({
|
|||
<br />
|
||||
<EventCarpools />
|
||||
{signups !== null && (
|
||||
<EventSignups
|
||||
event={event}
|
||||
myPlaceId={placeId}
|
||||
signups={signups}
|
||||
/>
|
||||
<EventSignups myPlaceId={placeId} signups={signups} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import CancelIcon from '@material-ui/icons/Cancel';
|
||||
import CheckIcon from '@material-ui/icons/Check';
|
||||
import EmojiPeopleIcon from '@material-ui/icons/EmojiPeople';
|
||||
import { useEffect } from 'react';
|
||||
import { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { lightgrey } from '../../lib/colors';
|
||||
import {
|
||||
|
@ -91,7 +92,8 @@ function CarpoolRow({
|
|||
type CreationStatus = null | 'pending' | 'completed' | 'errored';
|
||||
|
||||
export default function Carpools() {
|
||||
const { event } = useContext(EventContext);
|
||||
const { event, tentativeInvites, signups, setHasCarpool } =
|
||||
useContext(EventContext);
|
||||
const [creationStatus, setCreationStatus] = useState<CreationStatus>(null);
|
||||
const [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null);
|
||||
|
||||
|
@ -106,6 +108,10 @@ export default function Carpools() {
|
|||
const alreadyInCarpool =
|
||||
myCarpool !== undefined || creationStatus === 'completed';
|
||||
|
||||
useEffect(() => {
|
||||
setHasCarpool(alreadyInCarpool);
|
||||
}, [alreadyInCarpool, setHasCarpool]);
|
||||
|
||||
const createEmptyCarpool = useCallback(() => {
|
||||
setCreationStatus('pending');
|
||||
|
||||
|
@ -119,6 +125,55 @@ export default function Carpools() {
|
|||
});
|
||||
}, [event.id, me.name]);
|
||||
|
||||
const tentativeInviteNames = useMemo(() => {
|
||||
if (!signups) return [];
|
||||
const names = tentativeInvites.map((id) => {
|
||||
const signup = signups.find((s) => s.user.id === id);
|
||||
return signup?.user.name;
|
||||
});
|
||||
const nonNull = names.filter((n) => n != null);
|
||||
return nonNull.toArray() as string[];
|
||||
}, [tentativeInvites, signups]);
|
||||
|
||||
let createCarpoolSection;
|
||||
|
||||
if (tentativeInviteNames.length > 0) {
|
||||
const inviteeCount = tentativeInviteNames.length;
|
||||
const peoplePlural = inviteeCount > 1 ? 'People' : 'Person';
|
||||
createCarpoolSection = (
|
||||
<>
|
||||
<br />
|
||||
<b>You have invited these people to carpool with you:</b>
|
||||
{tentativeInviteNames.join(',')}
|
||||
<UIButton
|
||||
onClick={createEmptyCarpool}
|
||||
style={{ backgroundColor: lightgrey }}
|
||||
>
|
||||
{creationStatus === null
|
||||
? `Create Carpool With ${inviteeCount} ${peoplePlural}`
|
||||
: creationStatus === 'pending'
|
||||
? 'Creating...'
|
||||
: 'Errored'}
|
||||
</UIButton>
|
||||
</>
|
||||
);
|
||||
} else
|
||||
createCarpoolSection = (
|
||||
<>
|
||||
<span>Available to drive?</span>
|
||||
<UIButton
|
||||
onClick={createEmptyCarpool}
|
||||
style={{ backgroundColor: lightgrey }}
|
||||
>
|
||||
{creationStatus === null
|
||||
? 'Create Empty Carpool'
|
||||
: creationStatus === 'pending'
|
||||
? 'Creating...'
|
||||
: 'Errored'}
|
||||
</UIButton>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<h3 style={{ marginBottom: '0' }}>Carpools</h3>
|
||||
|
@ -133,19 +188,7 @@ export default function Carpools() {
|
|||
<UILink href={`/carpools/${myCarpool.id}`}>{myCarpool.name}</UILink>
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span>Available to drive?</span>
|
||||
<UIButton
|
||||
onClick={createEmptyCarpool}
|
||||
style={{ backgroundColor: lightgrey }}
|
||||
>
|
||||
{creationStatus === null
|
||||
? 'Create Empty Carpool'
|
||||
: creationStatus === 'pending'
|
||||
? 'Creating...'
|
||||
: 'Errored'}
|
||||
</UIButton>
|
||||
</>
|
||||
createCarpoolSection
|
||||
)}
|
||||
{event.carpools.map((carpool) => (
|
||||
<CarpoolRow
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { createContext } from 'react';
|
||||
import { IEvent } from '../types';
|
||||
import { IEvent, IEventSignup } from '../types';
|
||||
import * as immutable from 'immutable';
|
||||
|
||||
const EventContext = createContext({
|
||||
refresh: () => {
|
||||
|
@ -7,6 +8,18 @@ const EventContext = createContext({
|
|||
},
|
||||
event: null! as IEvent,
|
||||
default: true,
|
||||
signups: null as IEventSignup[] | null,
|
||||
addTentativeInvite: (id: number) => {
|
||||
console.error('not implemented: addTentativeInvite');
|
||||
},
|
||||
removeTentativeInvite: (id: number) => {
|
||||
console.error('not implemented: removeTentativeInvite');
|
||||
},
|
||||
tentativeInvites: immutable.Set<number>(),
|
||||
hasCarpool: false,
|
||||
setHasCarpool: (has: boolean) => {
|
||||
console.error('not implemented: setHasCarpool');
|
||||
},
|
||||
});
|
||||
|
||||
export default EventContext;
|
||||
|
|
|
@ -1,47 +1,37 @@
|
|||
import CancelIcon from '@material-ui/icons/Cancel';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
import { useMe } from '../hooks';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { PlaceDetails } from '../../lib/getPlaceDetails';
|
||||
import latlongdist, { R_miles } from '../../lib/latlongdist';
|
||||
import { IEventSignup, IEvent } from '../types';
|
||||
import { useMe } from '../hooks';
|
||||
import { IEventSignup } from '../types';
|
||||
import usePlace from '../usePlace';
|
||||
import { useMemo } from 'react';
|
||||
import EventContext from './EventContext';
|
||||
|
||||
export default function EventSignups({
|
||||
event,
|
||||
signups,
|
||||
myPlaceId,
|
||||
function EventSignup({
|
||||
signup,
|
||||
locationLatitude,
|
||||
locationLongitude,
|
||||
myPlaceDetails,
|
||||
}: {
|
||||
event: IEvent;
|
||||
signups: IEventSignup[];
|
||||
myPlaceId: string | null;
|
||||
signup: IEventSignup;
|
||||
locationLatitude: number;
|
||||
locationLongitude: number;
|
||||
myPlaceDetails: PlaceDetails | null;
|
||||
}) {
|
||||
const carpools = event.carpools;
|
||||
const placeDetails = usePlace(myPlaceId);
|
||||
const locationLongitude = event.latitude;
|
||||
const locationLatitude = event.longitude;
|
||||
const { user, latitude, longitude } = signup;
|
||||
const me = useMe();
|
||||
const {
|
||||
addTentativeInvite,
|
||||
removeTentativeInvite,
|
||||
tentativeInvites,
|
||||
hasCarpool,
|
||||
} = useContext(EventContext);
|
||||
|
||||
const signupsWithoutCarpool = useMemo(() => {
|
||||
// A list of users not in any carpool
|
||||
const members = carpools.map((c) => c.members);
|
||||
const allMembers = members.reduce((a, b) => a.concat(b), []);
|
||||
const allMembersIds = allMembers.map((m) => m.id);
|
||||
return signups.filter((s) => !allMembersIds.includes(s.user.id));
|
||||
}, [signups, carpools]);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<h3 style={{ marginBlockEnd: '0' }}>People without a carpool</h3>
|
||||
{signupsWithoutCarpool.map(({ latitude, longitude, user }) => {
|
||||
if (user.id === me?.id) {
|
||||
return null;
|
||||
}
|
||||
let extraDistance = null;
|
||||
if (
|
||||
placeDetails != null &&
|
||||
!(latitude === null || longitude === null)
|
||||
) {
|
||||
const myLatitude = placeDetails.latitude;
|
||||
const myLongitude = placeDetails.longitude;
|
||||
let extraDistance = useMemo(() => {
|
||||
if (myPlaceDetails != null && !(latitude === null || longitude === null)) {
|
||||
const myLatitude = myPlaceDetails.latitude;
|
||||
const myLongitude = myPlaceDetails.longitude;
|
||||
const meToThem = latlongdist(
|
||||
latitude,
|
||||
longitude,
|
||||
|
@ -64,7 +54,25 @@ export default function EventSignups({
|
|||
myLongitude,
|
||||
R_miles
|
||||
);
|
||||
extraDistance = totalWithThem - totalWithoutThem;
|
||||
return totalWithThem - totalWithoutThem;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [
|
||||
latitude,
|
||||
longitude,
|
||||
locationLatitude,
|
||||
locationLongitude,
|
||||
myPlaceDetails,
|
||||
]);
|
||||
|
||||
const isTentativelyInvited = useMemo(
|
||||
() => tentativeInvites.has(signup.user.id),
|
||||
[signup.user.id, tentativeInvites]
|
||||
);
|
||||
|
||||
if (user.id === me?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -84,14 +92,54 @@ export default function EventSignups({
|
|||
>
|
||||
<b>{user.name}</b>
|
||||
{extraDistance ? `: +${extraDistance.toFixed(1)} miles` : ''}
|
||||
<PersonAddIcon
|
||||
onClick={() => {
|
||||
// Invite to carpool and create carpool
|
||||
}}
|
||||
|
||||
{!hasCarpool &&
|
||||
(isTentativelyInvited ? (
|
||||
<CancelIcon
|
||||
onClick={() => removeTentativeInvite(user.id)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
) : (
|
||||
<PersonAddIcon
|
||||
onClick={() => addTentativeInvite(user.id)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EventSignups({
|
||||
signups,
|
||||
myPlaceId,
|
||||
}: {
|
||||
signups: IEventSignup[];
|
||||
myPlaceId: string | null;
|
||||
}) {
|
||||
const { event } = useContext(EventContext);
|
||||
const carpools = event.carpools;
|
||||
const myPlaceDetails = usePlace(myPlaceId);
|
||||
|
||||
const signupsWithoutCarpool = useMemo(() => {
|
||||
// A list of users not in any carpool
|
||||
const members = carpools.map((c) => c.members);
|
||||
const allMembers = members.reduce((a, b) => a.concat(b), []);
|
||||
const allMembersIds = allMembers.map((m) => m.id);
|
||||
return signups.filter((s) => !allMembersIds.includes(s.user.id));
|
||||
}, [signups, carpools]);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<h3 style={{ marginBlockEnd: '0' }}>People without a carpool</h3>
|
||||
{signupsWithoutCarpool.map((signup) => (
|
||||
<EventSignup
|
||||
key={signup.user.id}
|
||||
signup={signup}
|
||||
myPlaceDetails={myPlaceDetails}
|
||||
locationLatitude={event.latitude}
|
||||
locationLongitude={event.longitude}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user