all repos — caroster @ 0a157f5b51b85a50e27d205dc4db64776b5d6182

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

merge master
Hadrien Froger hadrien@octree.ch
Wed, 23 Sep 2020 07:29:36 +0100
commit

0a157f5b51b85a50e27d205dc4db64776b5d6182

parent

ee31f8ea32ae67c14b88a82de8005742b4c92cb2

M app/src/Router.jsapp/src/Router.js

@@ -12,23 +12,26 @@ import SignUp from './pages/SignUp';

import SignIn from './pages/SignIn'; import LostPassword from './pages/LostPassword.js'; import ResetPassword from './pages/ResetPassword.js'; - +import ErrorBoundary from './containers/ErrorBoundary'; const Router = () => { useGTM(); return ( <BrowserRouter> - <Switch> - <Route path="/e/:eventId" component={Event} /> - <Route path="/" exact component={Home} /> - <Route path="/new" exact component={Home} /> - <Route path="/register" exact component={SignUp} /> - <Route path="/lost-password" exact component={LostPassword} /> - <Route path="/reset-password" exact component={ResetPassword} /> - <Route path="/login" exact component={SignIn} /> - <Route path="/dashboard" exact component={Dashboard} /> - <Route path="/profile" exact component={Profile} /> - <Route component={NotFound} /> - </Switch> + <ErrorBoundary> + <Switch> + <Route path="/e/:eventId" component={Event} /> + <Route path="/" exact component={Home} /> + <Route path="/new" exact component={Home} /> + <Route path="/register" exact component={SignUp} /> + <Route path="/lost-password" exact component={LostPassword} /> + <Route path="/reset-password" exact component={ResetPassword} /> + <Route path="/login" exact component={SignIn} /> + <Route path="/dashboard" exact component={Dashboard} /> + <Route path="/profile" exact component={Profile} /> + <Route path="/error" exact component={NotFound} /> + <Route component={NotFound} /> + </Switch> + </ErrorBoundary> </BrowserRouter> ); };
M app/src/components/Logo/index.jsapp/src/components/Logo/index.js

@@ -17,16 +17,18 @@ const useStyles = makeStyles(theme => ({

layout: { display: 'flex', justifyContent: 'center', - paddingTop: '1rem', - paddingBottom: '1rem', + alignItems: 'center', + paddingTop: theme.spacing(4), + paddingBottom: theme.spacing(4), }, link: { - width: '60%', + width: '100%', }, logo: { display: 'block', - width: '100%', + width: '40%', height: 'auto', + margin: '0 auto', }, })); export default Logo;
M app/src/containers/CreateEvent/Step1.jsapp/src/containers/CreateEvent/Step1.js

@@ -1,5 +1,6 @@

import React, {useState, useEffect, useMemo} from 'react'; import {makeStyles} from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button'; import Checkbox from '@material-ui/core/Checkbox';

@@ -7,6 +8,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';

import {useTranslation} from 'react-i18next'; import useDebounce from '../../hooks/useDebounce'; import useProfile from '../../hooks/useProfile'; +import {CardActions} from '@material-ui/core'; const isValidEmail = email => // eslint-disable-next-line

@@ -15,9 +17,9 @@ email

); const Step1 = ({nextStep, event, addToEvent}) => { - const classes = useStyles(); const {t} = useTranslation(); const {connected, user} = useProfile(); + const classes = useStyles({connected}); // States const [name, setName] = useState(event.name ?? '');

@@ -58,31 +60,31 @@ id="NewEventName"

name="name" /> {!connected && ( - <TextField - label={t('event.creation.creator_email')} - fullWidth - margin="dense" - value={email} - onChange={e => setEmail(e.target.value)} - id="NewEventEmail" - name="email" - type="email" - /> - )} - {!connected && ( - <FormControlLabel - className={classes.newsletter} - label={t('event.creation.newsletter')} - control={ - <Checkbox - name="newsletter" - color="primary" - id="NewEventNewsletter" - checked={newsletter} - onChange={e => setNewsletter(e.target.checked)} - /> - } - /> + <> + <TextField + label={t('event.creation.creator_email')} + fullWidth + margin="dense" + value={email} + onChange={e => setEmail(e.target.value)} + id="NewEventEmail" + name="email" + type="email" + /> + <FormControlLabel + className={classes.newsletter} + label={t('event.creation.newsletter')} + control={ + <Checkbox + name="newsletter" + color="primary" + id="NewEventNewsletter" + checked={newsletter} + onChange={e => setNewsletter(e.target.checked)} + /> + } + /> + </> )} <Button className={classes.button}

@@ -95,6 +97,25 @@ aria-disabled={!canSubmit}

> {t('event.creation.next')} </Button> + + {!connected && ( + <div className={classes.addFromAccountSection}> + <Typography variant="body1"> + {t('event.creation.addFromAccount.title')} + </Typography> + <Typography variant="body2"> + {t('event.creation.addFromAccount.subtitle')} + </Typography> + <CardActions className={classes.actions}> + <Button variant="text" href="/register"> + {t('event.creation.addFromAccount.actions.register')} + </Button> + <Button color="primary" href="/login"> + {t('event.creation.addFromAccount.actions.login')} + </Button> + </CardActions> + </div> + )} </form> ); };

@@ -105,6 +126,15 @@ marginTop: theme.spacing(2),

}, newsletter: { marginTop: theme.spacing(2), + }, + addFromAccountSection: { + marginTop: theme.spacing(8), + textAlign: 'center', + }, + actions: { + marginTop: theme.spacing(1), + justifyContent: 'space-evenly', + textAlign: 'center', }, }));
M app/src/containers/CreateEvent/Step2.jsapp/src/containers/CreateEvent/Step2.js

@@ -67,7 +67,11 @@ type="submit"

id="NewEventSubmit" > {loading ? ( - <CircularProgress className={classes.loader} size={20} /> + <CircularProgress + className={classes.loader} + size={20} + color="primary" + /> ) : ( t('generic.create') )}
M app/src/containers/DashboardEvents/EventCard.jsapp/src/containers/DashboardEvents/EventCard.js

@@ -15,13 +15,13 @@ <CardContent>

<Typography gutterBottom variant="h6" component="h3"> {event.name} </Typography> - <Typography variant="outlined"> + <Typography variant="overline"> {t('event.fields.starts_on')} </Typography> <Typography variant="body2" gutterBottom> {event.date || t('event.fields.empty')} </Typography> - <Typography variant="outlined">{t('event.fields.address')}</Typography> + <Typography variant="overline">{t('event.fields.address')}</Typography> <Typography variant="body2" gutterBottom> {event.address || t('event.fields.empty')} </Typography>
A app/src/containers/ErrorBoundary/LogoutAndRedirect.js

@@ -0,0 +1,14 @@

+import React, {useEffect} from 'react'; +import {Redirect} from 'react-router-dom'; +import {useAuth} from 'strapi-react-context'; + +const LogoutAndRedirect = () => { + const {logout, token} = useAuth(); + useEffect(() => { + logout(); + }, [logout]); + if (token) return null; + return <Redirect to="/" />; +}; + +export default LogoutAndRedirect;
A app/src/containers/ErrorBoundary/index.js

@@ -0,0 +1,43 @@

+import React, {Component} from 'react'; +import LogoutAndRedirect from './LogoutAndRedirect'; +import {ApiProblem} from 'strapi-react-context'; +import {Redirect} from 'react-router-dom'; + +/** + * Error boundary to catch all the error from our code. + * If the error catched is an `unauthorized` APIProblem, will logout and redirect. Otherwise + * will redirect to an /error page. (nicer than blank page) + */ +class ErrorBoundary extends Component { + state = {error: null}; + + /** + * @param {Error} error + * @return {Object} new state after derivation + */ + static getDerivedStateFromError(error) { + if (error instanceof ApiProblem) { + return {error: error.kind}; + } + return {error: 'unknown'}; + } + + /** + * Component did catch an error, log it. + * @param {Error} error + */ + componentDidCatch(error) { + console.error('App did catch an error', {error}); + } + + /** + * @override + */ + render() { + if (this.state.error === null) return this.props.children; + + if (this.state.error === 'unauthorized') return <LogoutAndRedirect />; + return <Redirect to="error" />; + } +} +export default ErrorBoundary;
M app/src/containers/EventBar/index.jsapp/src/containers/EventBar/index.js

@@ -124,8 +124,8 @@ <IconButton

color="inherit" edge="end" id="ShareBtn" - onClick={onShare} - className={classes.iconButtons} + onClick={toggleDetails} + className={classes.shareIcon} > <Icon>share</Icon> </IconButton>

@@ -174,7 +174,7 @@ />

</Toolbar> {detailsOpen && ( <Container className={classes.container} maxWidth="sm"> - <EventDetails toggleDetails={toggleDetails} /> + <EventDetails toggleDetails={toggleDetails} onShare={onShare} /> </Container> )} </AppBar>
M app/src/containers/EventDetails/index.jsapp/src/containers/EventDetails/index.js

@@ -1,7 +1,8 @@

-import React from 'react'; +import React, {useRef} from 'react'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; import Link from '@material-ui/core/Link'; +import Icon from '@material-ui/core/Icon'; import {DatePicker} from '@material-ui/pickers'; import TextField from '@material-ui/core/TextField'; import {makeStyles, createMuiTheme, ThemeProvider} from '@material-ui/core';

@@ -18,11 +19,11 @@ type: 'dark',

}, }); -const EventDetails = ({toggleDetails}) => { +const EventDetails = ({toggleDetails, onShare}) => { const {t} = useTranslation(); const classes = useStyles(); const {event, isEditing, setEditingEvent, editingEvent} = useEvent(); - + const shareInput = useRef(null); if (!event) return null; const idPrefix = isEditing ? 'EditEvent' : 'Event';

@@ -113,11 +114,32 @@ )}

</Typography> )} </div> - <div className={classes.actions}> - <Button onClick={toggleDetails} variant="outlined" id={`CarFindBtn`}> - {t('event.actions.find_car')} - </Button> - </div> + + <TextField + value={window.location.href} + inputProps={{ + ref: shareInput, + }} + InputProps={{disableUnderline: true}} + onFocus={() => { + if (shareInput) shareInput.current.select(); + }} + fullWidth + readOnly + name="eventShareLink" + id="ShareLink" + /> + + <Button + variant="outlined" + startIcon={<Icon>share</Icon>} + onClick={() => { + if (shareInput) shareInput.current.select(); + onShare(); + }} + > + Share Caroster + </Button> </div> </ThemeProvider> );

@@ -129,11 +151,6 @@ marginBottom: theme.spacing(12),

}, section: { marginBottom: theme.spacing(2), - }, - actions: { - display: 'flex', - justifyContent: 'center', - marginTop: theme.spacing(4), }, map: { marginTop: theme.spacing(4),
M app/src/containers/GenericMenu/index.jsapp/src/containers/GenericMenu/index.js

@@ -9,9 +9,11 @@ import {makeStyles} from '@material-ui/core/styles';

import GenericToolbar from './Toolbar'; import {useTranslation} from 'react-i18next'; import {useStrapi, useAuth} from 'strapi-react-context'; +import {useHistory} from 'react-router-dom'; -const GenericMenu = ({title, actions = []}) => { +const GenericMenu = ({title, actions = [], goBack = false}) => { const {t} = useTranslation(); + const history = useHistory(); const [anchorEl, setAnchorEl] = useState(null); const classes = useStyles(); const strapi = useStrapi();

@@ -42,6 +44,17 @@ className={classes.appbar}

id="Menu" > <Toolbar> + {goBack && ( + <IconButton + edge="start" + className={classes.goBack} + onClick={() => + history.length > 2 ? history.goBack() : history.push('/dashboard') + } + > + <Icon>arrow_back</Icon> + </IconButton> + )} <div className={classes.name}> <Typography variant="h6" noWrap id="MenuHeaderTitle"> {title}
M app/src/containers/Profile/EditPassword.jsapp/src/containers/Profile/EditPassword.js

@@ -1,13 +1,11 @@

import React from 'react'; import Card from '@material-ui/core/Card'; -import CardHeader from '@material-ui/core/CardHeader'; +import {makeStyles} from '@material-ui/core/styles'; import CardContent from '@material-ui/core/CardContent'; import CardActions from '@material-ui/core/CardActions'; import Button from '@material-ui/core/Button'; import {useTranslation} from 'react-i18next'; import TextField from '@material-ui/core/TextField'; -import IconButton from '@material-ui/core/IconButton'; -import Icon from '@material-ui/core/Icon'; const EditPassword = ({ oldPassword,

@@ -19,7 +17,7 @@ save,

cancel, }) => { const {t} = useTranslation(); - + const classes = useStyles(); return ( <form onSubmit={evt => {

@@ -29,20 +27,6 @@ save();

}} > <Card> - <CardHeader - title={t('profile.actions.change_password')} - action={ - <IconButton - color="inherit" - id="ChangePasswordAction" - type="submit" - title={t('profile.actions.save')} - disabled={oldPassword.length < 4 || newPassword.length < 4} - > - <Icon>done</Icon> - </IconButton> - } - /> <CardContent> <TextField label={t('profile.current_password')}

@@ -68,7 +52,7 @@ id="ProfileNewPassword"

name="new_password" /> </CardContent> - <CardActions> + <CardActions className={classes.actions}> <Button type="button" onClick={cancel}> {t('profile.actions.cancel')} </Button>

@@ -86,4 +70,9 @@ </form>

); }; +const useStyles = makeStyles(theme => ({ + actions: { + justifyContent: 'flex-end', + }, +})); export default EditPassword;
M app/src/containers/Profile/index.jsapp/src/containers/Profile/index.js

@@ -1,9 +1,6 @@

import React, {useState} from 'react'; import Card from '@material-ui/core/Card'; -import CardHeader from '@material-ui/core/CardHeader'; import CardContent from '@material-ui/core/CardContent'; -import IconButton from '@material-ui/core/IconButton'; -import Icon from '@material-ui/core/Icon'; import CardActions from '@material-ui/core/CardActions'; import Button from '@material-ui/core/Button'; import {useTranslation} from 'react-i18next';

@@ -78,23 +75,6 @@ setIsEditing(!isEditing);

}} > <Card> - <CardHeader - action={ - <IconButton - color="inherit" - id="EditProfileAction" - type="submit" - title={ - isEditing - ? t('profile.actions.save') - : t('profile.actions.edit') - } - > - <Icon>{isEditing ? 'done' : 'edit'}</Icon> - </IconButton> - } - title={t('profile.title')} - /> <CardContent> <ProfileField name="firstName"

@@ -128,22 +108,41 @@ isEditing={isEditing}

/> </CardContent> <CardActions className={classes.actions}> - {isEditing && ( - <Button - type="button" - color="primary" - onClick={evt => { - if (evt.preventDefault) evt.preventDefault(); - setIsEditingPassword(true); - }} - > - {t('profile.actions.change_password')} - </Button> - )} {!isEditing && ( - <Button type="button" color="default" onClick={() => logout()}> - {t('profile.actions.logout')} - </Button> + <> + <Button type="button" onClick={() => logout()}> + {t('profile.actions.logout')} + </Button> + <Button + type="button" + color="primary" + onClick={() => setIsEditing(true)} + variant="contained" + > + {t('profile.actions.edit')} + </Button> + </> + )} + {isEditing && ( + <> + <Button + type="button" + onClick={evt => { + if (evt.preventDefault) evt.preventDefault(); + setIsEditingPassword(true); + }} + > + {t('profile.actions.change_password')} + </Button> + <Button + type="submit" + color="primary" + onClick={() => setIsEditing(false)} + variant="contained" + > + {t('profile.actions.save')} + </Button> + </> )} </CardActions> </Card>

@@ -154,6 +153,7 @@

const useStyles = makeStyles(theme => ({ actions: { marginTop: theme.spacing(2), + justifyContent: 'flex-end', }, })); export default Profile;
M app/src/containers/SignInForm/index.jsapp/src/containers/SignInForm/index.js

@@ -110,7 +110,7 @@ </Typography>

</RouterLink> </CardContent> <CardActions className={classes.actions} align="center"> - <Button id="SignInRegister" href="/register" size="small"> + <Button size="small" id="SignInRegister" href="/register"> {t('signin.register')} </Button> <Button

@@ -137,7 +137,6 @@ const useStyles = makeStyles(theme => ({

content: { display: 'flex', flexDirection: 'column', - alignItems: 'center', }, loader: { marginLeft: '14px',
M app/src/layouts/Default.jsapp/src/layouts/Default.js

@@ -9,6 +9,7 @@ menuTitle = 'Caroster',

menuActions, pageTitle = undefined, displayMenu = true, + goBack = false, }) => { return ( <>

@@ -16,7 +17,7 @@ <Helmet>

<title>{pageTitle || menuTitle}</title> </Helmet> {displayMenu && (menuTitle || menuActions) && ( - <GenericMenu title={menuTitle} actions={menuActions} /> + <GenericMenu title={menuTitle} actions={menuActions} goBack={goBack} /> )} <div className={className}>{children}</div> </>
M app/src/locales/fr.jsonapp/src/locales/fr.json

@@ -2,10 +2,10 @@ {

"menu": { "logout": "Se déconnecter", "about": "À propos de Caroster", - "dashboard": "Mes évènements", + "dashboard": "Tableau de bord", "login": "Se connecter", - "register": "S'inscrire", - "new_event": "Créer un évènement", + "register": "Créer un compte", + "new_event": "Créer un caroster", "profile": "Profil" }, "generic": {

@@ -44,9 +44,17 @@ "address": "Adresse de l'événement",

"next": "Suivant", "newsletter": "Me tenir informé des évolutions de Caroster par e-mail", "actions": { - "dashboard": "Mes évènements", + "dashboard": "$t(menu.dashboard)", "see_profile": "Profil", "about": "À propos de Caroster" + }, + "addFromAccount": { + "title": "Voulez-vous ajouter ce caroster à vos évènements ?", + "subtitle": "Créez-le depuis votre compte", + "actions": { + "register": "$t(menu.register)", + "login": "$t(menu.login)" + } } }, "actions": {

@@ -62,10 +70,10 @@ "cant_create": "Impossible de créer l'événement",

"cant_update": "Impossible de modifier l'événement" }, "add_to_my_events": { - "login": "Se connecter", - "register": "S'inscrire", - "title": "Ajouter {{eventName}} à sa liste d'évènement", - "text_html": "Pour ajouter <strong>{{eventName}}</strong>, il est nécessaire de se connecter ou de créer un compte." + "login": "$t(menu.login)", + "register": "$t(menu.register)", + "title": "Vous devez être connecté", + "text_html": "Pour ajouter <strong>{{eventName}}</strong> à vos carosters vous devez être connecté ou créer un compte." } }, "car": {

@@ -101,27 +109,27 @@ "cant_remove_passenger": "Impossible de supprimer le passager"

} }, "dashboard": { - "title": "Évènements", + "title": "$t(menu.dashboard)", "actions": { "see_event": "Voir plus", "add_event": "Créer un caroster" }, "sections": { - "future": "Évènement à venir", - "future_plural": "Évènements à venir", - "past": "Évènement passé", - "past_plural": "Évènements passés", - "noDate": "Évènement sans date", - "noDate_plural": "Évènements sans date" + "future": "Caroster à venir", + "future_plural": "Carosters à venir", + "past": "Caroster passé", + "past_plural": "Carosters passés", + "noDate": "Caroster sans date", + "noDate_plural": "Carosters sans date" }, "noEvent": { "title": "Bienvenue sur Caroster", - "text_html": "Ici, vous y verrez <strong>les évènements auxquels vous participer</strong>, pour commencer créer un évènement !", - "create_event": "Créer un évènement" + "text_html": "Ici, vous y verrez <strong>les carosters auxquels vous participer</strong>, pour commencer créer un Caroster !", + "create_event": "$t(menu.new_event)" } }, "profile": { - "title": "Mon profil", + "title": "Profil", "firstName": "Prénom", "lastName": "Nom", "email": "Email",

@@ -132,7 +140,7 @@ "updated": "Profil mis à jour",

"actions": { "save": "Enregistrer", "edit": "Editer", - "change_password": "Mot de passe oublié ?", + "change_password": "Changer son mot de passe", "logout": "Se déconnecter", "cancel": "Annuler", "save_new_password": "Mettre à jour"

@@ -167,7 +175,7 @@ "firstName": "Prénom",

"lastName": "Nom", "password": "Mot de passe", "submit": "Créer son compte", - "login": "Se connecter", + "login": "$t(menu.login)", "errors": { "email_taken": "Email déjà pris" },

@@ -182,8 +190,8 @@ "signin": {

"title": "Connexion", "email": "Email", "password": "Mot de passe", - "login": "Se connecter", - "register": "S'inscrire", + "login": "$t(menu.login)", + "register": "$t(menu.register)", "errors": "Vérifier votre email et mot de passe", "withGoogle": "Utiliser un compte Google" },
M app/src/pages/Profile.jsapp/src/pages/Profile.js

@@ -29,7 +29,7 @@

if (!profile) return <Loading />; return ( - <Layout menuTitle={t('profile.title')} menuActions={menuActions}> + <Layout menuTitle={t('profile.title')} menuActions={menuActions} goBack> <Profile profile={profile} updateProfile={updateProfile}