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;