all repos — caroster @ 82a6b00ced5640055e8caff7e67636e5746cc112

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

frontend/containers/TravelColumns/index.tsx (view raw)

  1import {useEffect, useMemo, useRef, useState} from 'react';
  2import {makeStyles} from '@material-ui/core/styles';
  3import Container from '@material-ui/core/Container';
  4import Slider from 'react-slick';
  5import {useTranslation} from 'react-i18next';
  6import {
  7  Travel as TravelType,
  8  useUpdateTravelMutation,
  9} from '../../generated/graphql';
 10import useEventStore from '../../stores/useEventStore';
 11import useTourStore from '../../stores/useTourStore';
 12import useToastStore from '../../stores/useToastStore';
 13import useProfile from '../../hooks/useProfile';
 14import useAddToEvents from '../../hooks/useAddToEvents';
 15import {
 16  AddPassengerToTravel,
 17  AddPassengerToWaitingList,
 18} from '../NewPassengerDialog';
 19import WaitingList from '../WaitingList';
 20import Travel from '../Travel';
 21import AddTravel from './AddTravel';
 22import sliderSettings from './_SliderSettings';
 23import usePassengersActions from '../../hooks/usePassengersActions';
 24
 25interface NewPassengerDialogContext {
 26  addSelf: boolean;
 27}
 28
 29interface Props {
 30  toggleNewTravel: () => void;
 31}
 32
 33const TravelColumns = (props: Props) => {
 34  const event = useEventStore(s => s.event);
 35  const {travels = []} = event || {};
 36  const slider = useRef(null);
 37  const {t} = useTranslation();
 38  const tourStep = useTourStore(s => s.step);
 39  const addToast = useToastStore(s => s.addToast);
 40  const [updateTravel] = useUpdateTravelMutation();
 41  const {addToEvent} = useAddToEvents();
 42  const {user} = useProfile();
 43  const classes = useStyles();
 44  const [newPassengerTravelContext, toggleNewPassengerToTravel] = useState<{
 45    travel: TravelType;
 46  } | null>(null);
 47  const [addPassengerToWaitingListContext, toggleNewPassengerToWaitingList] =
 48    useState<NewPassengerDialogContext | null>(null);
 49  const {addPassengerToTravel} = usePassengersActions();
 50
 51  const canAddSelf = useMemo(() => {
 52    const isInWaitingList = event?.waitingList?.some(
 53      passenger => passenger.user?.id === user.id
 54    );
 55    const isInTravel = event?.travels.some(travel =>
 56      travel.passengers.some(passenger => passenger.user?.id === user.id)
 57    );
 58    return !(isInWaitingList || isInTravel);
 59  }, [event, user]);
 60
 61  const addSelfToTravel = async (travel: TravelType) => {
 62    const passenger = {
 63      user: user,
 64      email: user.email,
 65      name: user.username,
 66    };
 67
 68    return addPassengerToTravel({
 69      passenger,
 70      travel,
 71      onSucceed: () => {
 72        addToEvent(event.id);
 73        addToast(t('passenger.success.added_self_to_car'));
 74      },
 75    });
 76  };
 77
 78  // On tour step changes : component update
 79  useEffect(() => {
 80    onTourChange(slider.current);
 81  }, [tourStep]);
 82
 83  return (
 84    <div className={classes.container}>
 85      <div className={classes.dots} id="slider-dots" />
 86      <div className={classes.slider}>
 87        <Slider ref={slider} {...sliderSettings}>
 88          <Container maxWidth="sm" className={classes.slide}>
 89            <WaitingList
 90              canAddSelf={canAddSelf}
 91              getToggleNewPassengerDialogFunction={(addSelf: boolean) => () =>
 92                toggleNewPassengerToWaitingList({addSelf})}
 93            />
 94          </Container>
 95          {travels
 96            ?.slice()
 97            .sort(sortTravels)
 98            .map(travel => (
 99              <Container
100                key={travel.id}
101                maxWidth="sm"
102                className={classes.slide}
103              >
104                <Travel
105                  travel={travel}
106                  {...props}
107                  canAddSelf={canAddSelf}
108                  getAddPassengerFunction={(addSelf: boolean) => () => {
109                    if (addSelf) {
110                      return addSelfToTravel(travel);
111                    } else {
112                      return toggleNewPassengerToTravel({travel});
113                    }
114                  }}
115                />
116              </Container>
117            ))}
118          <Container maxWidth="sm" className={classes.slide}>
119            <AddTravel {...props} />
120          </Container>
121        </Slider>
122      </div>
123      {!!newPassengerTravelContext && (
124        <AddPassengerToTravel
125          open={!!newPassengerTravelContext}
126          toggle={() => toggleNewPassengerToTravel(null)}
127          travel={newPassengerTravelContext.travel}
128        />
129      )}
130      {!!addPassengerToWaitingListContext && (
131        <AddPassengerToWaitingList
132          open={!!addPassengerToWaitingListContext}
133          toggle={() => toggleNewPassengerToWaitingList(null)}
134          addSelf={addPassengerToWaitingListContext.addSelf}
135        />
136      )}
137    </div>
138  );
139};
140
141const onTourChange = slider => {
142  const {prev, step, isCreator} = useTourStore.getState();
143  const fromTo = (step1: number, step2: number) =>
144    prev === step1 && step === step2;
145
146  if (isCreator) {
147    if (fromTo(2, 3) || fromTo(4, 3)) slider?.slickGoTo(0, true);
148  } else if (fromTo(0, 1)) slider?.slickGoTo(0, true);
149};
150
151const sortTravels = (a: TravelType, b: TravelType) => {
152  if (!b) return 1;
153  const dateA = new Date(a.departure).getTime();
154  const dateB = new Date(b.departure).getTime();
155  if (dateA === dateB)
156    return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
157  else return dateA - dateB;
158};
159
160const useStyles = makeStyles(theme => ({
161  container: {
162    minHeight: '100vh',
163    paddingTop: theme.mixins.toolbar.minHeight,
164    paddingLeft: theme.spacing(6),
165    paddingRight: theme.spacing(6),
166    [theme.breakpoints.down('sm')]: {
167      paddingLeft: theme.spacing(),
168      paddingRight: theme.spacing(),
169    },
170    display: 'flex',
171    flexDirection: 'column',
172    overflowX: 'hidden',
173    overflowY: 'auto',
174  },
175  dots: {
176    height: 32,
177    overflow: 'auto',
178    '& overflow': '-moz-scrollbars-none',
179    '-ms-overflow-style': 'none',
180    '&::-webkit-scrollbar': {
181      height: '0 !important',
182    },
183    '& .slick-dots': {
184      position: 'static',
185      '& li': {
186        display: 'block',
187      },
188    },
189    '& .slick-dots li:first-child button:before, & .slick-dots li:last-child button:before':
190      {
191        color: theme.palette.primary.main,
192      },
193  },
194  slider: {
195    flexGrow: 1,
196    height: 1,
197    '& .slick-slider': {
198      height: '100%',
199      '& .slick-list': {
200        overflow: 'visible',
201      },
202      cursor: 'grab',
203    },
204  },
205  slide: {
206    padding: theme.spacing(1),
207    marginBottom: theme.spacing(12),
208    outline: 'none',
209    '& > *': {
210      cursor: 'default',
211    },
212  },
213}));
214
215export default TravelColumns;