all repos — caroster @ e22ee9c064d006eb9bd3af3cc9709ce4d28df633

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

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

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