all repos — caroster @ a5cf0e5ec409bf5e3c76c95bccb10adaad3758e2

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

✨ Add capability to add new car to event
Tim Izzo tim@octree.ch
Wed, 01 Jul 2020 12:14:54 +0200
commit

a5cf0e5ec409bf5e3c76c95bccb10adaad3758e2

parent

0193e48a670b09ce5d60ac51a21aa0a6a8348cf3

M app/src/components/Paper/index.jsapp/src/components/Paper/index.js

@@ -2,17 +2,17 @@ import React from "react";

import PaperMUI from "@material-ui/core/Paper"; import { makeStyles } from "@material-ui/core/styles"; -const useStyles = makeStyles((theme) => ({ - root: { - padding: theme.spacing(2), - }, -})); - const Paper = ({ className, ...props }) => { const classes = useStyles(); return ( <PaperMUI classes={{ root: classes.root, parent: className }} {...props} /> ); }; + +const useStyles = makeStyles((theme) => ({ + root: { + padding: theme.spacing(2), + }, +})); export default Paper;
M app/src/containers/CarColumns/AddCar.jsapp/src/containers/CarColumns/AddCar.js

@@ -1,7 +1,26 @@

import React from "react"; +import Button from "@material-ui/core/Button"; +import Container from "@material-ui/core/Container"; +import { makeStyles } from "@material-ui/core"; +import { useTranslation } from "react-i18next"; -const AddCar = () => { - return <div>Add car</div>; +const AddCar = ({ toggleNewCar }) => { + const classes = useStyles(); + const { t } = useTranslation(); + return ( + <Container maxWidth="sm" className={classes.container}> + <Button variant="contained" onClick={toggleNewCar}> + {t("car.creation.title")} + </Button> + </Container> + ); }; + +const useStyles = makeStyles((theme) => ({ + container: { + display: "flex", + justifyContent: "center", + }, +})); export default AddCar;
M app/src/containers/CarColumns/index.jsapp/src/containers/CarColumns/index.js

@@ -1,9 +1,11 @@

-import React from "react"; +import React, { useMemo } from "react"; import Slider from "react-slick"; import Container from "@material-ui/core/Container"; import { makeStyles } from "@material-ui/core/styles"; import Car from "./Car"; import AddCar from "./AddCar"; +import { useEvent } from "../../contexts/Event"; +import { useStrapi } from "strapi-react-context"; const settings = { dots: false,

@@ -25,18 +27,27 @@ },

], }; -const CarColumns = ({ cars = [] }) => { +const CarColumns = ({ ...props }) => { const classes = useStyles(); + const strapi = useStrapi(); + const { event } = useEvent(); + + const cars = useMemo( + () => strapi.stores.cars?.filter((car) => car?.event?.id === event?.id), + [strapi.stores.cars, event] + ); + return ( <div> <Slider {...settings}> - {cars.map((car) => ( - <Container key={car.id} maxWidth="sm" className={classes.slide}> - <Car car={car} /> - </Container> - ))} + {cars && + cars.map((car) => ( + <Container key={car.id} maxWidth="sm" className={classes.slide}> + <Car car={car} {...props} /> + </Container> + ))} <Container maxWidth="sm" className={classes.slide}> - <AddCar /> + <AddCar {...props} /> </Container> </Slider> </div>
M app/src/containers/CreateEvent/Step2.jsapp/src/containers/CreateEvent/Step2.js

@@ -60,7 +60,7 @@ fullWidth

onClick={onCreate} id="NewEventSubmit" > - {t("event.creation.create")} + {t("generic.create")} </Button> </Paper> );
M app/src/containers/EventFab/index.jsapp/src/containers/EventFab/index.js

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";

import { useEvent } from "../../contexts/Event"; import { useToast } from "../../contexts/Toast"; -const EventFab = () => { +const EventFab = ({ toggleNewCar }) => { const { t } = useTranslation(); const [open, toggleOpen] = useReducer((i) => !i, false); const classes = useStyles({ open });

@@ -54,6 +54,7 @@ color="secondary"

aria-label="add-car" variant="extended" className={classes.extendedFab} + onClick={toggleNewCar} > <Icon className={classes.extendedIcon}>directions_car</Icon> Ajouter
A app/src/containers/NewCarDialog/index.js

@@ -0,0 +1,161 @@

+import React, { useState } from "react"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import Button from "@material-ui/core/Button"; +import Slide from "@material-ui/core/Slide"; +import moment from "moment"; +import { useStrapi } from "strapi-react-context"; +import { useTranslation } from "react-i18next"; +import TextField from "@material-ui/core/TextField"; +import Slider from "@material-ui/core/Slider"; +import Typography from "@material-ui/core/Typography"; +import { makeStyles } from "@material-ui/core/styles"; +import { DateTimePicker } from "@material-ui/pickers"; +import { useToast } from "../../contexts/Toast"; +import { useEvent } from "../../contexts/Event"; + +const Transition = React.forwardRef(function Transition(props, ref) { + return <Slide direction="up" ref={ref} {...props} />; +}); + +const marks = [1, 2, 3, 4, 5, 6, 7, 8].map((value) => ({ + value, + label: value, +})); + +const NewCarDialog = ({ open, toggle }) => { + const strapi = useStrapi(); + const { t } = useTranslation(); + const classes = useStyles(); + const { addToast } = useToast(); + const { event } = useEvent(); + + // States + const [name, setName] = useState(""); + const [seats, setSeats] = useState(4); + const [meeting, setMeeting] = useState(""); + const [date, setDate] = useState(moment()); + const [phone, setPhone] = useState(""); + const [details, setDetails] = useState(""); + + const canCreate = !!name && !!seats; + + const onCreate = async () => { + try { + await strapi.services.cars.create({ + name, + seats, + meeting, + departure: date.toISOString(), + phone_number: phone, + details, + event: event.id, + }); + addToast(t("car.creation.created")); + toggle(); + return true; + } catch (error) { + console.error(error); + addToast(t("car.errors.cant_create")); + return false; + } + }; + + return ( + <Dialog + open={open} + TransitionComponent={Transition} + onClose={toggle} + fullWidth + maxWidth="sm" + > + <DialogTitle>{t("car.creation.title")}</DialogTitle> + <DialogContent> + <DialogContentText> + <TextField + className={classes.textField} + label={t("car.creation.name")} + fullWidth + autoFocus + margin="dense" + value={name} + onChange={(e) => setName(e.target.value)} + id="NewCarName" + name="name" + /> + <Typography variant="caption">{t("car.creation.seats")}</Typography> + <Slider + value={seats} + onChange={(e, value) => setSeats(value)} + step={1} + marks={marks} + min={1} + max={marks.length} + valueLabelDisplay="auto" + /> + <TextField + className={classes.textField} + label={t("car.creation.meeting")} + fullWidth + margin="dense" + multiline + rows={2} + value={meeting} + onChange={(e) => setMeeting(e.target.value)} + id="NewCarMeeting" + name="meeting" + /> + <DateTimePicker + label={t("event.creation.date")} + value={date} + onChange={setDate} + className={classes.textField} + fullWidth + format="LLLL" + disablePast + id="NewCarDateTime" + name="date" + /> + <TextField + className={classes.textField} + label={t("car.creation.phone")} + fullWidth + autoFocus + margin="dense" + value={phone} + onChange={(e) => setPhone(e.target.value)} + id="NewCarPhone" + name="phone" + /> + <TextField + className={classes.textField} + label={t("car.creation.notes")} + fullWidth + margin="dense" + multiline + rows={2} + value={details} + onChange={(e) => setDetails(e.target.value)} + id="NewCarDetails" + name="details" + /> + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={toggle}>{t("generic.cancel")}</Button> + <Button variant="contained" onClick={onCreate} disabled={!canCreate}> + {t("generic.create")} + </Button> + </DialogActions> + </Dialog> + ); +}; + +const useStyles = makeStyles((theme) => ({ + textField: { marginBottom: theme.spacing(2) }, +})); + +export default NewCarDialog;
M app/src/containers/TosDialog/index.jsapp/src/containers/TosDialog/index.js

@@ -21,7 +21,7 @@ const page = strapi.stores?.pages?.find(({ type }) => type === "tos");

useEffect(() => { strapi.services.pages.find({ type: "tos" }); - }, []); + }, [strapi.services.pages]); return ( <Dialog
M app/src/contexts/Event.jsapp/src/contexts/Event.js

@@ -23,6 +23,11 @@ if (!strapi.stores.events?.find(({ id }) => eventId === id))

strapi.services.events.findOne(eventId); }, [eventId, strapi.stores.events, strapi.services.events]); + // Fetch event cars on load + useEffect(() => { + strapi.services.cars.find({ event: eventId }); + }, [eventId]); // eslint-disable-line react-hooks/exhaustive-deps + // Retrieve event data const event = useMemo( () => strapi.stores.events?.find((e) => e.id === eventId),
M app/src/locales/fr.jsonapp/src/locales/fr.json

@@ -1,5 +1,10 @@

{ - "generic": { "loading": "Chargement...", "close": "Fermer" }, + "generic": { + "loading": "Chargement...", + "close": "Fermer", + "create": "Créer", + "cancel": "Annuler" + }, "event": { "fields": { "starts_on": "Commence le",

@@ -11,7 +16,6 @@ "creator_email": "Votre e-mail",

"date": "Date de l'événement", "address": "Adresse de l'événement", "next": "Suivant", - "create": "Créer", "tos": "J'accepte les conditions générales d'utilisation" }, "actions": {

@@ -31,6 +35,19 @@ "car": {

"fields": { "meeting_point": "Lieu de rencontre", "details": "Notes" + }, + "creation": { + "title": "Ajouter une voiture", + "name": "Nom de la voiture", + "seats": "Nombre de places", + "meeting": "Lieu de rencontre", + "phone": "Numéro de téléphone", + "notes": "Infos complémentaires", + "created": "La voiture a été créée" + }, + "errors": { + "cant_create": "Impossible de créer la voiture", + "cant_update": "Impossible de modifier la voiture" } } }
M app/src/pages/Event.jsapp/src/pages/Event.js

@@ -15,6 +15,7 @@ import EventFab from "../containers/EventFab";

import { useEvent, EventProvider } from "../contexts/Event"; import CarColumns from "../containers/CarColumns"; import { useToast } from "../contexts/Toast"; +import NewCarDialog from "../containers/NewCarDialog"; const Event = () => { const { t } = useTranslation();

@@ -22,6 +23,7 @@ const { addToast } = useToast();

const [anchorEl, setAnchorEl] = useState(null); const [detailsOpen, toggleDetails] = useReducer((i) => !i, false); const classes = useStyles({ detailsOpen }); + const [openNewCar, toggleNewCar] = useReducer((i) => !i, false); const { event, isEditing,

@@ -33,7 +35,7 @@ } = useEvent();

useEffect(() => { if (!detailsOpen) setIsEditing(false); - }, [detailsOpen]); + }, [detailsOpen]); // eslint-disable-line react-hooks/exhaustive-deps const onEventSave = async (e) => { try {

@@ -94,7 +96,7 @@ ? t("event.actions.hide_details")

: t("event.actions.show_details"), onClick: toggleDetails, }, - { label: t("event.actions.add_car"), onClick: () => {} }, + { label: t("event.actions.add_car"), onClick: toggleNewCar }, { label: t("event.actions.invite"), onClick: () => {} }, ]} />

@@ -103,8 +105,9 @@ <Container className={classes.container} maxWidth="sm">

<EventDetails toggleDetails={toggleDetails} /> </Container> </AppBar> - <CarColumns cars={event.cars} /> - <EventFab /> + <CarColumns toggleNewCar={toggleNewCar} /> + <EventFab toggleNewCar={toggleNewCar} /> + <NewCarDialog open={openNewCar} toggle={toggleNewCar} /> </Layout> ); };