all repos — caroster @ 0168bcad4d88a6c28f52f84ff518eec66d3995b6

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

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

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