all repos — caroster @ 832452704d5eae9e2164e58c086cdf365e51e5e7

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