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