all repos — caroster @ a99f9a0e6eaee5048c7d6c15cc34865a13b9c2c0

[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  toggle: () => 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 {addToEvent} = useAddToEvents();
 41  const {user} = useProfile();
 42  const classes = useStyles();
 43  const [newPassengerTravelContext, toggleNewPassengerToTravel] = useState<{
 44    travel: TravelType;
 45  } | null>(null);
 46  const [addPassengerToWaitingListContext, toggleNewPassengerToWaitingList] =
 47    useState<NewPassengerDialogContext | null>(null);
 48  const {addPassengerToTravel} = usePassengersActions();
 49  const sortedTravels = travels?.slice().sort(sortTravels);
 50
 51  const canAddSelf = useMemo(() => {
 52    if (!user) return false;
 53    const isInWaitingList = event?.waitingList?.some(
 54      passenger => passenger.user?.id === user.id
 55    );
 56    const isInTravel = event?.travels.some(travel =>
 57      travel.passengers.some(passenger => passenger.user?.id === user.id)
 58    );
 59    return !(isInWaitingList || isInTravel);
 60  }, [event, user]);
 61
 62  const addSelfToTravel = async (travel: TravelType) => {
 63    const passenger = {
 64      user: user,
 65      email: user.email,
 66      name: user.username,
 67    };
 68
 69    return addPassengerToTravel({
 70      passenger,
 71      travel,
 72      onSucceed: () => {
 73        addToEvent(event.id);
 74        addToast(t('passenger.success.added_self_to_car'));
 75      },
 76    });
 77  };
 78
 79  const slideToTravel = (travelId: string) => {
 80    const travelIndex = sortedTravels.findIndex(
 81      travel => travel.id === travelId
 82    );
 83    const slideIndex = travelIndex + 1;
 84    slider.current.slickGoTo(slideIndex);
 85  };
 86
 87  // On tour step changes : component update
 88  useEffect(() => {
 89    onTourChange(slider.current);
 90  }, [tourStep]);
 91
 92  return (
 93    <div className={classes.container}>
 94      <div className={classes.dots} id="slider-dots" />
 95      <div className={classes.slider}>
 96        <Slider ref={slider} {...sliderSettings}>
 97          <Container maxWidth="sm" className={classes.slide}>
 98            <WaitingList
 99              slideToTravel={slideToTravel}
100              canAddSelf={canAddSelf}
101              getToggleNewPassengerDialogFunction={(addSelf: boolean) => () =>
102                toggleNewPassengerToWaitingList({addSelf})}
103            />
104          </Container>
105          {sortedTravels?.map(travel => (
106            <Container key={travel.id} maxWidth="sm" className={classes.slide}>
107              <Travel
108                travel={travel}
109                {...props}
110                canAddSelf={canAddSelf}
111                getAddPassengerFunction={(addSelf: boolean) => () => {
112                  if (addSelf) {
113                    return addSelfToTravel(travel);
114                  } else {
115                    return toggleNewPassengerToTravel({travel});
116                  }
117                }}
118              />
119            </Container>
120          ))}
121          <Container maxWidth="sm" className={classes.slide}>
122            <AddTravel {...props} />
123          </Container>
124        </Slider>
125      </div>
126      {!!newPassengerTravelContext && (
127        <AddPassengerToTravel
128          open={!!newPassengerTravelContext}
129          toggle={() => toggleNewPassengerToTravel(null)}
130          travel={newPassengerTravelContext.travel}
131        />
132      )}
133      {!!addPassengerToWaitingListContext && (
134        <AddPassengerToWaitingList
135          open={!!addPassengerToWaitingListContext}
136          toggle={() => toggleNewPassengerToWaitingList(null)}
137          addSelf={addPassengerToWaitingListContext.addSelf}
138        />
139      )}
140    </div>
141  );
142};
143
144const onTourChange = slider => {
145  const {prev, step, isCreator} = useTourStore.getState();
146  const fromTo = (step1: number, step2: number) =>
147    prev === step1 && step === step2;
148
149  if (isCreator) {
150    if (fromTo(2, 3) || fromTo(4, 3)) slider?.slickGoTo(0, true);
151  } else if (fromTo(0, 1)) slider?.slickGoTo(0, true);
152};
153
154const sortTravels = (a: TravelType, b: TravelType) => {
155  if (!b) return 1;
156  const dateA = new Date(a.departure).getTime();
157  const dateB = new Date(b.departure).getTime();
158  if (dateA === dateB)
159    return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
160  else return dateA - dateB;
161};
162
163const useStyles = makeStyles(theme => ({
164  container: {
165    minHeight: '100vh',
166    paddingTop: theme.mixins.toolbar.minHeight,
167    paddingLeft: theme.spacing(6),
168    paddingRight: theme.spacing(6),
169    [theme.breakpoints.down('sm')]: {
170      paddingLeft: theme.spacing(),
171      paddingRight: theme.spacing(),
172    },
173    display: 'flex',
174    flexDirection: 'column',
175    overflowX: 'hidden',
176    overflowY: 'auto',
177  },
178  dots: {
179    height: 32,
180    overflow: 'auto',
181    '& overflow': '-moz-scrollbars-none',
182    '-ms-overflow-style': 'none',
183    '&::-webkit-scrollbar': {
184      height: '0 !important',
185    },
186    '& .slick-dots': {
187      position: 'static',
188      '& li': {
189        display: 'block',
190      },
191    },
192    '& .slick-dots li:first-child button:before, & .slick-dots li:last-child button:before':
193      {
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;