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;