all repos — caroster @ fda8dfd8f0c5653fd865054042ea1423dbb07d12

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