all repos — caroster @ 6d2659c5a5d3df7aeeaad568e6543fcbb943bf45

[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            helperText=" "
125            value={phone}
126            onChange={e => setPhone(e.target.value)}
127            name="phone"
128            FormHelperTextProps={{
129              component: () => (
130                <FAQLink
131                  className={classes.faqHelper}
132                  link={t('travel.creation.phoneHelper.faq')}
133                  text={t('travel.creation.phoneHelper.why')}
134                />
135              ),
136            }}
137            id="NewTravelPhone"
138          />
139          <div className={classes.slider}>
140            <Typography variant="caption">
141              {t('travel.creation.seats')}
142            </Typography>
143            <Slider
144              value={seats}
145              onChange={(e, value) => setSeats(value)}
146              step={1}
147              marks={MARKS}
148              min={1}
149              max={MARKS.length}
150              valueLabelDisplay="auto"
151              id="NewTravelSeats"
152            />
153          </div>
154          <Divider className={classes.divider} />
155          <Typography className={classes.sectionTitle}>
156            {t('travel.creation.travel.title')}
157          </Typography>
158          <Box className={classes.halfWidthWrapper}>
159            <DatePicker
160              className={classes.halfWidthField}
161              inputVariant="outlined"
162              size="small"
163              label={t('travel.creation.date')}
164              helperText=" "
165              value={date}
166              onChange={setDate}
167              format="DD/MM/YYYY"
168              cancelLabel={t('generic.cancel')}
169              autoFocus
170              id="NewTravelDateTime"
171            />
172            <TimePicker
173              className={classes.halfWidthField}
174              inputVariant="outlined"
175              size="small"
176              label={t('travel.creation.time')}
177              helperText=" "
178              value={time}
179              onChange={setTime}
180              cancelLabel={t('generic.cancel')}
181              ampm={false}
182              minutesStep={5}
183              id="NewTravelTime"
184            />
185          </Box>
186          <TextField
187            variant="outlined"
188            size="small"
189            className={classes.field}
190            label={t('travel.creation.meeting')}
191            fullWidth
192            multiline
193            rowsMax={4}
194            inputProps={{maxLength: 250}}
195            helperText={`${meeting.length}/250`}
196            value={meeting}
197            onChange={e => setMeeting(e.target.value)}
198            name="meeting"
199            id="NewTravelMeeting"
200          />
201          <TextField
202            variant="outlined"
203            size="small"
204            className={classes.field}
205            label={t('travel.creation.notes')}
206            fullWidth
207            multiline
208            rowsMax={4}
209            inputProps={{maxLength: 250}}
210            helperText={`${details.length}/250`}
211            value={details}
212            onChange={e => setDetails(e.target.value)}
213            name="details"
214            id="NewTravelDetails"
215          />
216        </DialogContent>
217        <DialogActions className={classes.actions}>
218          <Button
219            color="primary"
220            id="NewTravelCancel"
221            onClick={() => toggle({opened: false})}
222            tabIndex={-1}
223          >
224            {t('generic.cancel')}
225          </Button>
226          <Button
227            color="primary"
228            variant="contained"
229            type="submit"
230            disabled={!canCreate}
231            aria-disabled={!canCreate}
232            id="NewTravelSubmit"
233          >
234            {t('travel.creation.submit')}
235          </Button>
236        </DialogActions>
237      </form>
238    </Dialog>
239  );
240};
241
242const Transition = forwardRef(function Transition(props, ref) {
243  return <Slide direction="up" ref={ref} {...props} />;
244});
245
246const formatDate = (date: Moment, time: Moment) => {
247  return moment(
248    `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
249    'YYYY-MM-DD HH:mm'
250  ).toISOString();
251};
252
253const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
254  value,
255  label: value,
256}));
257
258const addSpacing = (theme, ratio) => ({
259  margin: `0 ${theme.spacing(3 * ratio)}px`,
260  width: `calc(100% - ${theme.spacing(6 * ratio)}px)`,
261});
262
263const useStyles = makeStyles(theme => ({
264  title: {
265    paddingBottom: 0,
266  },
267  sectionTitle: {
268    ...addSpacing(theme, 1),
269    paddingBottom: theme.spacing(1.5),
270  },
271  content: {
272    padding: `${theme.spacing(2)}px 0`,
273  },
274  faqHelper: {
275    fontSize: '12px',
276  },
277  field: {
278    ...addSpacing(theme, 1),
279    paddingBottom: theme.spacing(1),
280  },
281  halfWidthWrapper: {
282    ...addSpacing(theme, 0.5),
283  },
284  halfWidthField: {
285    margin: `0 ${theme.spacing(1.5)}px`,
286    width: `calc(50% - ${theme.spacing(3)}px)`,
287
288    '& > .MuiFormLabel-root': {
289      textOverflow: 'ellipsis',
290      whiteSpace: 'nowrap',
291      width: '100%',
292      overflow: 'hidden',
293    },
294  },
295  slider: {
296    ...addSpacing(theme, 1),
297  },
298  divider: {
299    margin: `${theme.spacing(2)}px 0`,
300  },
301  actions: {
302    paddingTop: 0,
303  },
304}));
305
306export default NewTravelDialog;