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