all repos — caroster @ 2e1d246adedb2ed739c18489e6fff9f30f661508

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

frontend/containers/NewTravelDialog/index.tsx (view raw)

  1import {useState, forwardRef, useMemo, useEffect} 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 {Box, Divider} from '@mui/material';
 12import {useTheme} from '@mui/material/styles';
 13import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 14import {TimePicker} from '@mui/x-date-pickers/TimePicker';
 15import moment, {Moment} from 'moment';
 16import {useTranslation} from 'react-i18next';
 17import useEventStore from '../../stores/useEventStore';
 18import useActions from './useActions';
 19import FAQLink from './FAQLink';
 20import {Vehicle} from '../../generated/graphql';
 21
 22interface Props {
 23  context: {
 24    vehicle: Vehicle;
 25    opened: boolean;
 26  };
 27  toggle: ({opened: boolean}) => void;
 28}
 29
 30const NewTravelDialog = ({context, toggle}: Props) => {
 31  const {t} = useTranslation();
 32  const theme = useTheme();
 33
 34  const event = useEventStore(s => s.event);
 35  const {createTravel} = useActions({event});
 36
 37  const dateMoment = useMemo(
 38    () => (event?.date ? moment(event.date) : null),
 39    [event?.date]
 40  );
 41
 42  // States
 43  const [name, setName] = useState('');
 44  const [seats, setSeats] = useState(4);
 45  const [meeting, setMeeting] = useState('');
 46  const [date, setDate] = useState(dateMoment);
 47  const [time, setTime] = useState(dateMoment);
 48  const [phone, setPhone] = useState('');
 49  const [details, setDetails] = useState('');
 50  const canCreate = !!name && !!seats;
 51
 52  const clearState = () => {
 53    setName('');
 54    setSeats(4);
 55    setMeeting('');
 56    setDate(moment());
 57    setPhone('');
 58    setDetails('');
 59  };
 60
 61  useEffect(() => {
 62    if (context.vehicle) {
 63      setName(context.vehicle.name);
 64      setSeats(context.vehicle.seats);
 65      setPhone(context.vehicle.phone_number);
 66    }
 67  }, [context.vehicle]);
 68
 69  const onCreate = async e => {
 70    if (e.preventDefault) e.preventDefault();
 71
 72    const travel = {
 73      meeting,
 74      details,
 75      seats,
 76      vehicleName: name,
 77      phone_number: phone,
 78      departure: formatDate(date, time),
 79      event: event.id,
 80    };
 81    const createVehicle = !context.vehicle;
 82
 83    await createTravel(travel, createVehicle);
 84    toggle({opened: false});
 85
 86    clearState();
 87  };
 88
 89  const halfWidthFieldSx = {
 90    margin: `0 ${theme.spacing(1.5)}`,
 91    width: `calc(50% - ${theme.spacing(3)})`,
 92
 93    '& > .MuiFormLabel-root': {
 94      textOverflow: 'ellipsis',
 95      whiteSpace: 'nowrap',
 96      width: '100%',
 97      overflow: 'hidden',
 98    },
 99  };
100
101  return (
102    <Dialog
103      fullWidth
104      maxWidth="xs"
105      open={context?.opened}
106      onClose={() => {
107        toggle({opened: false});
108        clearState();
109      }}
110      TransitionComponent={Transition}
111    >
112      <form onSubmit={onCreate}>
113        <DialogTitle sx={{paddingBottom: 0}}>
114          {t('travel.creation.title')}
115        </DialogTitle>
116        <DialogContent sx={{padding: `${theme.spacing(2)} 0`}}>
117          <Typography
118            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1.5)}}
119          >
120            {t('travel.creation.car.title')}
121          </Typography>
122          <TextField
123            variant="outlined"
124            size="small"
125            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
126            label={t('travel.creation.name')}
127            fullWidth
128            value={name}
129            onChange={e => setName(e.target.value)}
130            name="name"
131            id="NewTravelName"
132          />
133          <TextField
134            variant="outlined"
135            size="small"
136            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
137            label={t('travel.creation.phone')}
138            fullWidth
139            inputProps={{type: 'tel'}}
140            value={phone}
141            onChange={e => setPhone(e.target.value)}
142            name="phone"
143            FormHelperTextProps={{
144              component: () => (
145                <Typography variant="caption">
146                  <FAQLink
147                    sx={{textDecoration: 'none'}}
148                    link={t('travel.creation.phoneHelper.faq')}
149                    text={t('travel.creation.phoneHelper.why')}
150                  />
151                </Typography>
152              ),
153            }}
154            id="NewTravelPhone"
155          />
156          <Box sx={addSpacing(theme, 1)}>
157            <Typography variant="caption">
158              {t('travel.creation.seats')}
159            </Typography>
160            <Slider
161              size="small"
162              value={seats}
163              onChange={(e, value) => setSeats(value)}
164              step={1}
165              marks={MARKS}
166              min={1}
167              max={MARKS.length}
168              valueLabelDisplay="auto"
169              id="NewTravelSeats"
170            />
171          </Box>
172          <Divider
173            sx={{
174              margin: `${theme.spacing(2)} 0`,
175            }}
176          />
177          <Typography
178            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1.5)}}
179          >
180            {t('travel.creation.travel.title')}
181          </Typography>
182          <Box sx={addSpacing(theme, 0.5)}>
183            <DatePicker
184              renderInput={props => (
185                <TextField
186                  {...props}
187                  variant="outlined"
188                  size="small"
189                  helperText=" "
190                  sx={halfWidthFieldSx}
191                />
192              )}
193              inputFormat="DD/MM/yyyy"
194              label={t('travel.creation.date')}
195              value={date}
196              onChange={setDate}
197              autoFocus
198            />
199            <TimePicker
200              renderInput={props => (
201                <TextField
202                  {...props}
203                  variant="outlined"
204                  size="small"
205                  helperText=" "
206                  sx={halfWidthFieldSx}
207                />
208              )}
209              label={t('travel.creation.time')}
210              value={time}
211              onChange={setTime}
212              ampm={false}
213              minutesStep={5}
214            />
215          </Box>
216          <TextField
217            variant="outlined"
218            size="small"
219            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
220            label={t('travel.creation.meeting')}
221            fullWidth
222            multiline
223            maxRows={4}
224            inputProps={{maxLength: 250}}
225            helperText={`${meeting.length}/250`}
226            value={meeting}
227            onChange={e => setMeeting(e.target.value)}
228            name="meeting"
229            id="NewTravelMeeting"
230          />
231          <TextField
232            variant="outlined"
233            size="small"
234            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
235            label={t('travel.creation.notes')}
236            fullWidth
237            multiline
238            maxRows={4}
239            inputProps={{maxLength: 250}}
240            helperText={`${details.length}/250`}
241            value={details}
242            onChange={e => setDetails(e.target.value)}
243            name="details"
244            id="NewTravelDetails"
245          />
246        </DialogContent>
247        <DialogActions
248          sx={{
249            paddingTop: 0,
250          }}
251        >
252          <Button
253            color="primary"
254            id="NewTravelCancel"
255            onClick={() => toggle({opened: false})}
256            tabIndex={-1}
257          >
258            {t('generic.cancel')}
259          </Button>
260          <Button
261            color="primary"
262            variant="contained"
263            type="submit"
264            disabled={!canCreate}
265            aria-disabled={!canCreate}
266            id="NewTravelSubmit"
267          >
268            {t('travel.creation.submit')}
269          </Button>
270        </DialogActions>
271      </form>
272    </Dialog>
273  );
274};
275
276const Transition = forwardRef(function Transition(props, ref) {
277  return <Slide direction="up" ref={ref} {...props} />;
278});
279
280const formatDate = (date: Moment, time: Moment) => {
281  return moment(
282    `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
283    'YYYY-MM-DD HH:mm'
284  ).toISOString();
285};
286
287const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
288  value,
289  label: value,
290}));
291
292const addSpacing = (theme, ratio) => ({
293  margin: `0 ${theme.spacing(3 * ratio)}`,
294  width: `calc(100% - ${theme.spacing(6 * ratio)})`,
295});
296
297export default NewTravelDialog;