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