all repos — caroster @ ce141b018b745094b2e1cec1ceec924ed9bba3f6

[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              label={t('travel.creation.date')}
194              value={date}
195              onChange={setDate}
196              autoFocus
197            />
198            <TimePicker
199              renderInput={props => (
200                <TextField
201                  {...props}
202                  variant="outlined"
203                  size="small"
204                  helperText=" "
205                  sx={halfWidthFieldSx}
206                />
207              )}
208              label={t('travel.creation.time')}
209              value={time}
210              onChange={setTime}
211              ampm={false}
212              minutesStep={5}
213            />
214          </Box>
215          <TextField
216            variant="outlined"
217            size="small"
218            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
219            label={t('travel.creation.meeting')}
220            fullWidth
221            multiline
222            maxRows={4}
223            inputProps={{maxLength: 250}}
224            helperText={`${meeting.length}/250`}
225            value={meeting}
226            onChange={e => setMeeting(e.target.value)}
227            name="meeting"
228            id="NewTravelMeeting"
229          />
230          <TextField
231            variant="outlined"
232            size="small"
233            sx={{...addSpacing(theme, 1), paddingBottom: theme.spacing(1)}}
234            label={t('travel.creation.notes')}
235            fullWidth
236            multiline
237            maxRows={4}
238            inputProps={{maxLength: 250}}
239            helperText={`${details.length}/250`}
240            value={details}
241            onChange={e => setDetails(e.target.value)}
242            name="details"
243            id="NewTravelDetails"
244          />
245        </DialogContent>
246        <DialogActions
247          sx={{
248            paddingTop: 0,
249          }}
250        >
251          <Button
252            color="primary"
253            id="NewTravelCancel"
254            onClick={() => toggle({opened: false})}
255            tabIndex={-1}
256          >
257            {t('generic.cancel')}
258          </Button>
259          <Button
260            color="primary"
261            variant="contained"
262            type="submit"
263            disabled={!canCreate}
264            aria-disabled={!canCreate}
265            id="NewTravelSubmit"
266          >
267            {t('travel.creation.submit')}
268          </Button>
269        </DialogActions>
270      </form>
271    </Dialog>
272  );
273};
274
275const Transition = forwardRef(function Transition(props, ref) {
276  return <Slide direction="up" ref={ref} {...props} />;
277});
278
279const formatDate = (date: Moment, time: Moment) => {
280  return moment(
281    `${moment(date).format('YYYY-MM-DD')} ${moment(time).format('HH:mm')}`,
282    'YYYY-MM-DD HH:mm'
283  ).toISOString();
284};
285
286const MARKS = [1, 2, 3, 4, 5, 6, 7, 8].map(value => ({
287  value,
288  label: value,
289}));
290
291const addSpacing = (theme, ratio) => ({
292  margin: `0 ${theme.spacing(3 * ratio)}`,
293  width: `calc(100% - ${theme.spacing(6 * ratio)})`,
294});
295
296export default NewTravelDialog;