all repos — caroster @ v0.9.0

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