all repos — caroster @ efb618469130ae351c648f97b4a1d6cac23525b3

[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            helperText={
144              <Typography variant="caption">
145                <FAQLink
146                  link={t('travel.creation.phoneHelper.faq')}
147                  text={t('travel.creation.phoneHelper.why')}
148                />
149              </Typography>
150            }
151            id="NewTravelPhone"
152          />
153          <Box sx={addSpacing(theme, 1)}>
154            <Typography variant="caption">
155              {t('travel.creation.seats')}
156            </Typography>
157            <Slider
158              size="small"
159              value={seats}
160              onChange={(e, value) => setSeats(value)}
161              step={1}
162              marks={MARKS}
163              min={1}
164              max={MARKS.length}
165              valueLabelDisplay="auto"
166              id="NewTravelSeats"
167            />
168          </Box>
169          <Divider
170            sx={{
171              margin: `${theme.spacing(2)} 0`,
172            }}
173          />
174          <Typography
175            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1.5)}}
176          >
177            {t('travel.creation.travel.title')}
178          </Typography>
179          <Box sx={addSpacing(theme, 0.5)}>
180            <DatePicker
181              renderInput={props => (
182                <TextField
183                  {...props}
184                  variant="outlined"
185                  size="small"
186                  helperText=" "
187                  sx={halfWidthFieldSx}
188                />
189              )}
190              inputFormat="DD/MM/yyyy"
191              label={t('travel.creation.date')}
192              value={date}
193              onChange={setDate}
194              autoFocus
195            />
196            <TimePicker
197              renderInput={props => (
198                <TextField
199                  {...props}
200                  variant="outlined"
201                  size="small"
202                  helperText=" "
203                  sx={halfWidthFieldSx}
204                />
205              )}
206              label={t('travel.creation.time')}
207              value={time}
208              onChange={setTime}
209              ampm={false}
210              minutesStep={5}
211            />
212          </Box>
213          <TextField
214            variant="outlined"
215            size="small"
216            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
217            label={t('travel.creation.meeting')}
218            fullWidth
219            multiline
220            maxRows={4}
221            inputProps={{maxLength: 250}}
222            helperText={`${meeting.length}/250`}
223            value={meeting}
224            onChange={e => setMeeting(e.target.value)}
225            name="meeting"
226            id="NewTravelMeeting"
227          />
228          <TextField
229            variant="outlined"
230            size="small"
231            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
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        </DialogContent>
244        <DialogActions
245          sx={{
246            paddingTop: 0,
247          }}
248        >
249          <Button
250            color="primary"
251            id="NewTravelCancel"
252            onClick={() => toggle({opened: false})}
253            tabIndex={-1}
254          >
255            {t('generic.cancel')}
256          </Button>
257          <Button
258            color="primary"
259            variant="contained"
260            type="submit"
261            disabled={!canCreate}
262            aria-disabled={!canCreate}
263            id="NewTravelSubmit"
264          >
265            {t('travel.creation.submit')}
266          </Button>
267        </DialogActions>
268      </form>
269    </Dialog>
270  );
271};
272
273const Transition = forwardRef(function Transition(props, ref) {
274  return <Slide direction="up" ref={ref} {...props} />;
275});
276
277const formatDate = (date: Moment, time: Moment) => {
278  return moment(
279    `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
280    'YYYY-MM-DD HH:mm'
281  ).toISOString();
282};
283
284const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
285  value,
286  label: value,
287}));
288
289const addSpacing = (theme, ratio) => ({
290  margin: `0 ${theme.spacing(3 * ratio)}`,
291  width: `calc(100% - ${theme.spacing(6 * ratio)})`,
292});
293
294export default NewTravelDialog;