all repos — caroster @ 06074413a139ee783b1268b024601101738c0239

[Octree] Group carpool to your event https://caroster.io

app/src/containers/WaitingList/index.js (view raw)

  1import React, {useReducer, useState, useMemo, useCallback} from 'react';
  2import Typography from '@material-ui/core/Typography';
  3import IconButton from '@material-ui/core/IconButton';
  4import Icon from '@material-ui/core/Icon';
  5import Paper from '@material-ui/core/Paper';
  6import Divider from '@material-ui/core/Divider';
  7import {makeStyles} from '@material-ui/core/styles';
  8import {Trans, useTranslation} from 'react-i18next';
  9import {useStrapi} from 'strapi-react-context';
 10import {useEvent} from '../../contexts/Event';
 11import {useToast} from '../../contexts/Toast';
 12import useProfile from '../../hooks/useProfile';
 13import PassengersList from '../PassengersList';
 14import RemoveDialog from '../RemoveDialog';
 15import CarDialog from './CarDialog';
 16
 17const sortCars = (a, b) => {
 18  const dateA = new Date(a.departure).getTime();
 19  const dateB = new Date(b.departure).getTime();
 20  if (dateA === dateB) return new Date(a.createdAt) - new Date(b.createdAt);
 21  else return dateA - dateB;
 22};
 23
 24const WaitingList = ({car}) => {
 25  const classes = useStyles();
 26  const {t} = useTranslation();
 27  const strapi = useStrapi();
 28  const {event} = useEvent();
 29  const {addEvent} = useProfile();
 30  const {addToast} = useToast();
 31  const [isEditing, toggleEditing] = useReducer(i => !i, false);
 32  const [removing, setRemoving] = useState(null);
 33  const [adding, setAdding] = useState(null);
 34
 35  const cars = useMemo(
 36    () =>
 37      strapi.stores.cars
 38        ?.filter(car => car?.event?.id === event?.id)
 39        .sort(sortCars),
 40    [strapi.stores.cars, event]
 41  );
 42
 43  const availability = useMemo(() => {
 44    if (!cars) return;
 45    return cars.reduce((count, {seats, passengers = []}) => {
 46      return count + seats - passengers.length;
 47    }, 0);
 48  }, [cars]);
 49
 50  const saveWaitingList = useCallback(
 51    async (waitingList, i18nError) => {
 52      try {
 53        await strapi.services.events.update(event.id, {
 54          waiting_list: waitingList,
 55        });
 56        addEvent(event);
 57      } catch (error) {
 58        console.error(error);
 59        addToast(t(i18nError));
 60      }
 61    },
 62    [event, addEvent] // eslint-disable-line
 63  );
 64
 65  const addPassenger = useCallback(
 66    async passenger =>
 67      saveWaitingList(
 68        [...(event.waiting_list || []), passenger],
 69        'passenger.errors.cant_add_passenger'
 70      ),
 71    [event, saveWaitingList] // eslint-disable-line
 72  );
 73
 74  const removePassenger = useCallback(
 75    async index => {
 76      return saveWaitingList(
 77        event.waiting_list.filter((_, i) => i !== index),
 78        'passenger.errors.cant_remove_passenger'
 79      );
 80    },
 81    [event, saveWaitingList] // eslint-disable-line
 82  );
 83
 84  const selectCar = useCallback(
 85    async car => {
 86      try {
 87        await strapi.services.cars.update(car.id, {
 88          passengers: [...(car.passengers || []), event.waiting_list[adding]],
 89        });
 90        await strapi.services.events.update(event.id, {
 91          waiting_list: event.waiting_list.filter((_, i) => i !== adding),
 92        });
 93      } catch (error) {
 94        console.error(error);
 95        addToast(t('passenger.errors.cant_select_car'));
 96      }
 97      setAdding(null);
 98    },
 99    [event, adding] // eslint-disable-line
100  );
101
102  const onPress = useCallback(
103    index => {
104      if (isEditing) setRemoving(index);
105      else setAdding(index);
106    },
107    [isEditing]
108  );
109
110  return (
111    <>
112      <Paper className={classes.root}>
113        <div className={classes.header}>
114          <IconButton
115            size="small"
116            color="primary"
117            className={classes.editBtn}
118            disabled={!event.waiting_list || !event.waiting_list.length}
119            onClick={toggleEditing}
120          >
121            {isEditing ? <Icon>check</Icon> : <Icon>edit</Icon>}
122          </IconButton>
123          <Typography variant="h5">{t('passenger.title')}</Typography>
124          <Typography variant="overline">
125            {t('passenger.availability.seats', {count: availability})}
126          </Typography>
127        </div>
128        <Divider />
129        <PassengersList
130          passengers={event.waiting_list}
131          addPassenger={addPassenger}
132          onPress={onPress}
133          icon={isEditing ? 'close' : 'chevron_right'}
134          disabled={!isEditing && availability <= 0}
135        />
136      </Paper>
137      <RemoveDialog
138        text={
139          <Trans
140            i18nKey="passenger.actions.remove_alert"
141            values={{
142              name: event.waiting_list ? event.waiting_list[removing] : null,
143            }}
144            components={{italic: <i />, bold: <strong />}}
145          />
146        }
147        open={removing !== null}
148        onClose={() => setRemoving(null)}
149        onRemove={() => removePassenger(removing)}
150      />
151      <CarDialog
152        cars={cars}
153        open={adding !== null}
154        onClose={() => setAdding(null)}
155        onSelect={selectCar}
156      />
157    </>
158  );
159};
160
161const useStyles = makeStyles(theme => ({
162  root: {
163    position: 'relative',
164  },
165  header: {
166    padding: theme.spacing(2),
167  },
168  editBtn: {
169    position: 'absolute',
170    top: 0,
171    right: 0,
172    margin: theme.spacing(1),
173    zIndex: theme.zIndex.speedDial,
174  },
175}));
176
177export default WaitingList;