all repos — caroster @ ba1f0945c383630d88192de37465dc72ebca0328

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