frontend/containers/AddTravelButton/NewTravelDialog.tsx (view raw)
1import {useState, forwardRef, useMemo} from 'react';
2import Dialog from '@mui/material/Dialog';
3import DialogActions from '@mui/material/DialogActions';
4import DialogContent from '@mui/material/DialogContent';
5import DialogTitle from '@mui/material/DialogTitle';
6import Button from '@mui/material/Button';
7import Slide from '@mui/material/Slide';
8import TextField from '@mui/material/TextField';
9import Slider from '@mui/material/Slider';
10import Typography from '@mui/material/Typography';
11import moment from 'moment';
12import {Box, Divider, Stack} from '@mui/material';
13import {DatePicker} from '@mui/x-date-pickers/DatePicker';
14import {TimePicker} from '@mui/x-date-pickers/TimePicker';
15import {useTranslation} from 'next-i18next';
16import PhoneInput from '../../components/PhoneInput';
17import PlaceInput from '../PlaceInput';
18import useEventStore from '../../stores/useEventStore';
19import useActions from './useActions';
20import FAQLink from './FAQLink';
21import {useSession} from 'next-auth/react';
22
23interface Props {
24 open: boolean;
25 toggle: () => void;
26}
27
28const NewTravelDialog = ({open, toggle}: Props) => {
29 const {t} = useTranslation();
30 const event = useEventStore(s => s.event);
31 const {createTravel} = useActions({event});
32 const session = useSession();
33 const profile = session?.data?.profile;
34 const dateMoment = useMemo(
35 () => (event?.date ? moment(event.date) : null),
36 [event?.date]
37 );
38
39 // States
40 const [firstname, setFirstname] = useState(profile?.firstName || '');
41 const [lastname, setLastname] = useState(profile?.lastName || '');
42 const [seats, setSeats] = useState(4);
43 const [meeting, setMeeting] = useState('');
44 const [meeting_latitude, setMeetingLatitude] = useState(null);
45 const [meeting_longitude, setMeetingLongitude] = useState(null);
46 const [date, setDate] = useState(dateMoment);
47 const [time, setTime] = useState(dateMoment);
48 const [phone, setPhone] = useState('');
49 const [phoneCountry, setPhoneCountry] = useState('');
50 const [phoneError, setPhoneError] = useState(false);
51 const [details, setDetails] = useState('');
52
53 const canCreate =
54 !!firstname?.trim() &&
55 !!lastname?.trim() &&
56 !!seats &&
57 !phoneError &&
58 phone;
59
60 const clearState = () => {
61 setSeats(4);
62 setMeeting('');
63 setMeetingLatitude(null);
64 setMeetingLongitude(null);
65 setDate(moment());
66 setDetails('');
67 };
68
69 const onCreate = async e => {
70 if (e.preventDefault) e.preventDefault();
71
72 const travel = {
73 firstname,
74 lastname,
75 meeting,
76 meeting_latitude,
77 meeting_longitude,
78 details,
79 seats,
80 phone_number: phone,
81 phoneCountry: phoneCountry,
82 departureDate: date ? moment(date).format('YYYY-MM-DD') : '',
83 departureTime: time ? moment(time).format('HH:mm') : '',
84 event: event.id,
85 };
86
87 await createTravel(travel);
88 toggle();
89
90 clearState();
91 };
92
93 return (
94 <Dialog
95 fullWidth
96 maxWidth="xs"
97 open={open}
98 onClose={() => {
99 toggle();
100 clearState();
101 }}
102 TransitionComponent={Transition}
103 >
104 <form onSubmit={onCreate}>
105 <DialogTitle sx={{paddingBottom: 0}}>
106 {t('travel.creation.title')}
107 </DialogTitle>
108 <DialogContent sx={{px: 0}}>
109 <Stack px={3} py={2} spacing={2}>
110 <Typography>{t('travel.creation.car.title')}</Typography>
111 <Box display="flex" gap={2}>
112 <TextField
113 fullWidth
114 required
115 variant="outlined"
116 size="small"
117 label={t`profile.firstName`}
118 value={firstname}
119 onChange={e => setFirstname(e.target.value)}
120 name="firstname"
121 id="NewTravelFirstname"
122 error={!firstname?.trim()}
123 />
124 <TextField
125 fullWidth
126 required
127 variant="outlined"
128 size="small"
129 label={t`profile.lastName`}
130 value={lastname}
131 onChange={e => setLastname(e.target.value)}
132 name="lastname"
133 id="NewTravelLastname"
134 error={!lastname?.trim()}
135 />
136 </Box>
137 <PhoneInput
138 required
139 value={phone}
140 onChange={({phone, country, error}) => {
141 setPhone(phone);
142 setPhoneCountry(country);
143 setPhoneError(error);
144 }}
145 label={t('travel.creation.phone')}
146 name="phone"
147 variant="outlined"
148 size="small"
149 helperText={
150 <Typography variant="caption">
151 <FAQLink
152 link={t('travel.creation.phoneHelper.faq')}
153 text={t('travel.creation.phoneHelper.why')}
154 />
155 </Typography>
156 }
157 id="NewTravelPhone"
158 />
159 <Box>
160 <Typography variant="caption">
161 {t('travel.creation.seats')}
162 </Typography>
163 <Slider
164 size="small"
165 value={seats}
166 onChange={(e, value) => setSeats(value as number)}
167 step={1}
168 marks={MARKS}
169 min={1}
170 max={MARKS.length}
171 valueLabelDisplay="auto"
172 id="NewTravelSeats"
173 />
174 </Box>
175 </Stack>
176 <Divider />
177 <Stack px={3} pt={2} spacing={2}>
178 <Typography>{t('travel.creation.travel.title')}</Typography>
179 <Box display="flex" gap={2}>
180 <DatePicker
181 slotProps={{
182 textField: {
183 variant: 'outlined',
184 size: 'small',
185 helperText: !date
186 ? t('travel.creation.travel.dateHelper')
187 : '',
188 error: !date,
189 FormHelperTextProps: {sx: {color: 'warning.main'}},
190 },
191 }}
192 format="DD/MM/YYYY"
193 label={t('travel.creation.date')}
194 value={date}
195 onChange={setDate}
196 autoFocus
197 />
198 <TimePicker
199 slotProps={{
200 textField: {
201 variant: 'outlined',
202 size: 'small',
203 helperText: '',
204 },
205 }}
206 label={t('travel.creation.time')}
207 value={time}
208 onChange={setTime}
209 ampm={false}
210 minutesStep={5}
211 />
212 </Box>
213 <PlaceInput
214 label={t(
215 event?.isReturnEvent ? 'travel.destination' : 'travel.meeting'
216 )}
217 textFieldProps={{
218 variant: 'outlined',
219 }}
220 place={meeting}
221 latitude={meeting_latitude}
222 longitude={meeting_longitude}
223 onSelect={({place, latitude, longitude}) => {
224 setMeeting(place);
225 setMeetingLatitude(latitude);
226 setMeetingLongitude(longitude);
227 }}
228 />
229 <TextField
230 variant="outlined"
231 size="small"
232 label={t('travel.creation.notes')}
233 fullWidth
234 multiline
235 maxRows={4}
236 inputProps={{maxLength: 250}}
237 helperText={`${details.length}/250`}
238 value={details}
239 onChange={e => setDetails(e.target.value)}
240 name="details"
241 id="NewTravelDetails"
242 />
243 </Stack>
244 </DialogContent>
245 <DialogActions
246 sx={{
247 paddingTop: 0,
248 }}
249 >
250 <Button
251 color="primary"
252 id="NewTravelCancel"
253 onClick={toggle}
254 tabIndex={-1}
255 >
256 {t('generic.cancel')}
257 </Button>
258 <Button
259 color="primary"
260 variant="contained"
261 type="submit"
262 disabled={!canCreate}
263 aria-disabled={!canCreate}
264 id="NewTravelSubmit"
265 >
266 {t('travel.creation.submit')}
267 </Button>
268 </DialogActions>
269 </form>
270 </Dialog>
271 );
272};
273
274const Transition = forwardRef(function Transition(props, ref) {
275 return <Slide direction="up" ref={ref} {...props} />;
276});
277
278const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
279 value,
280 label: value,
281}));
282
283export default NewTravelDialog;