all repos — caroster @ 2e4e192becc64475ea9245eb5fec57f72cb7088e

[Octree] Group carpool to your event https://caroster.io

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