all repos — caroster @ 62de78accc81066736a8ae9f2b99eb212d2abc2d

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

:sparkles: add A generic menu for the dashboard, and a fab to create an event
Hadrien Froger hadrien@octree.ch
Sat, 18 Jul 2020 07:46:09 +0100
commit

62de78accc81066736a8ae9f2b99eb212d2abc2d

parent

ba8b73340ac0259d8b3ed2ad1959ef337c694383

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

@@ -20,6 +20,7 @@ <BrowserRouter>

<Switch> <Route path="/e/:eventId" component={Event} /> <Route path="/" exact component={Home} /> + <Route path="/new" exact component={Home} /> <Route path="/register/success" exact component={SignUpSuccess} /> <Route path="/register" exact component={SignUp} /> <Route path="/login" exact component={SignIn} />
A app/src/containers/Dashboard/Dashboard.js

@@ -0,0 +1,57 @@

+import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import {makeStyles} from '@material-ui/core/styles'; +import {useTranslation} from 'react-i18next'; +import {EventCard} from './EventCard'; +import Typography from '@material-ui/core/Typography'; +const DashboardSection = ({children}) => ( + <Grid item xs={12}> + <Typography gutterBottom variant="h6" component="h3"> + {children} + </Typography> + </Grid> +); +const Dashboard = ({futureEvents, noDateEvents, pastEvents}) => { + const classes = useStyles(); + const {t} = useTranslation(); + const cardsForEvents = events => + events.map(event => ( + <Grid item xs={3} key={event.id}> + <EventCard event={event} /> + </Grid> + )); + + return ( + <Grid container className={classes.root} spacing={4}> + {futureEvents.length + noDateEvents.length > 0 && ( + <> + <DashboardSection> + {t('dashboard.sections.future', { + count: futureEvents.length + noDateEvents.length, + })} + </DashboardSection> + {cardsForEvents(futureEvents)} + {cardsForEvents(noDateEvents)} + </> + )} + + {pastEvents.length > 0 && ( + <> + <DashboardSection> + {t('dashboard.sections.past', {count: pastEvents.length})} + </DashboardSection> + {cardsForEvents(pastEvents)} + </> + )} + </Grid> + ); +}; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + maxWidth: '90rem', + margin: '0 auto', + }, +})); +export default Dashboard;
A app/src/containers/Dashboard/DashboardFab.js

@@ -0,0 +1,27 @@

+import React from 'react'; +import Icon from '@material-ui/core/Icon'; +import Fab from '@material-ui/core/Fab'; +import {makeStyles} from '@material-ui/core/styles'; + +export const DashboardFab = ({onClick, open}) => { + const classes = useStyles({open}); + + return ( + <div className={classes.container}> + <Fab color="secondary" aria-label="dashboard-add" onClick={onClick}> + <Icon>add</Icon> + </Fab> + </div> + ); +}; + +const useStyles = makeStyles(theme => ({ + container: ({open}) => ({ + position: 'fixed', + bottom: open ? -theme.spacing(8) : theme.spacing(3), + right: theme.spacing(3), + transition: 'all 0.3s ease', + transform: open ? 'rotate(45deg)' : '', + zIndex: theme.zIndex.speedDial, + }), +}));
A app/src/containers/Dashboard/EmptyDashboard.js

@@ -0,0 +1,29 @@

+import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import {useTranslation} from 'react-i18next'; + +export const EmptyDashboard = () => { + const {t} = useTranslation(); + return ( + <Card> + <CardContent> + <Typography gutterBottom variant="h5" component="h1"> + {t('dashboard.noEvent.title')} + </Typography> + <Typography + variant="body1" + dangerouslySetInnerHTML={{ + __html: t('dashboard.noEvent.text_html'), + }} + /> + </CardContent> + <CardActions> + <Button>{t('dashboard.noEvent.create_event')}</Button> + </CardActions> + </Card> + ); +};
A app/src/containers/Dashboard/EventCard.js

@@ -0,0 +1,32 @@

+import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import {useTranslation} from 'react-i18next'; +export const EventCard = ({event}) => { + const {t} = useTranslation(); + return ( + <Card> + <CardContent> + <Typography gutterBottom variant="h6" component="h3"> + {event.name} + </Typography> + <Typography variant="body1">{t('event.fields.starts_on')}</Typography> + <Typography variant="body2" gutterBottom> + {event.date || t('event.fields.empty')} + </Typography> + <Typography variant="body1">{t('event.fields.address')}</Typography> + <Typography variant="body2" gutterBottom> + {event.address || t('event.fields.empty')} + </Typography> + </CardContent> + <CardActions> + <Button href={`/e/${event.id}`}> + {t('dashboard.actions.see_event')} + </Button> + </CardActions> + </Card> + ); +};
A app/src/containers/Dashboard/index.js

@@ -0,0 +1,5 @@

+import Dashboard from './Dashboard'; +export * from './EventCard'; +export * from './EmptyDashboard'; +export * from './DashboardFab'; +export default Dashboard;
A app/src/containers/GenericMenu/Toolbar.js

@@ -0,0 +1,40 @@

+import React from 'react'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; + +const Toolbar = ({anchorEl, setAnchorEl, actions = []}) => { + if (actions.length === 0) return null; + return ( + <Menu + anchorEl={anchorEl} + anchorOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + keepMounted + transformOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + open={!!anchorEl} + onClose={() => setAnchorEl(null)} + > + {actions && + actions.map(({onClick, id, label, ...menuItemProps}, idx) => ( + <MenuItem + onClick={() => { + if (!onClick) return; + onClick(); + setAnchorEl(null); + }} + key={idx} + id={id || `MenuItem${idx}`} + {...menuItemProps} + > + {label} + </MenuItem> + ))} + </Menu> + ); +}; +export default Toolbar;
A app/src/containers/GenericMenu/index.js

@@ -0,0 +1,75 @@

+import React, {useState, useEffect, useMemo} from 'react'; +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Icon from '@material-ui/core/Icon'; +import {makeStyles} from '@material-ui/core/styles'; +import GenericToolbar from './Toolbar'; +const GenericMenu = ({title, actions = []}) => { + const [anchorEl, setAnchorEl] = useState(null); + const classes = useStyles(); + const validActions = useMemo(() => actions.filter(Boolean), [actions]); + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + return ( + <AppBar + position="static" + color="primary" + className={classes.appbar} + id="Menu" + > + <Toolbar> + <div className={classes.name}> + <Typography variant="h6" noWrap id="MenuHeaderTitle"> + {title} + </Typography> + </div> + {validActions.length > 0 && ( + <> + <IconButton + color="inherit" + edge="end" + id="MenuMoreInfo" + onClick={e => setAnchorEl(e.currentTarget)} + > + <Icon>more_vert</Icon> + </IconButton> + + <GenericToolbar + anchorEl={anchorEl} + setAnchorEl={setAnchorEl} + actions={validActions} + /> + </> + )} + </Toolbar> + </AppBar> + ); +}; + +const useStyles = makeStyles(theme => ({ + container: { + padding: theme.spacing(2), + }, + appbar: { + overflow: 'hidden', + height: theme.mixins.toolbar.minHeight, + transition: 'height 0.3s ease', + zIndex: theme.zIndex.appBar, + position: 'fixed', + top: 0, + }, + name: { + flexGrow: 1, + display: 'flex', + alignItems: 'center', + }, + shareIcon: { + marginRight: theme.spacing(0), + }, +})); + +export default GenericMenu;
M app/src/locales/fr.jsonapp/src/locales/fr.json

@@ -1,6 +1,7 @@

{ "meta": { - "title": "Caroster - Covoiturage de groupe - {{title}}" + "title": "Caroster - Covoiturage de groupe - {{title}}", + "about_href": "https://caroster.io/about" }, "generic": { "loading": "Chargement...",

@@ -93,9 +94,19 @@ "cant_remove_passenger": "Impossible de supprimer le passager"

} }, "dashboard": { + "title": "Évènements", "actions": { - "see_event": "Voir" + "see_event": "Voir", + "see_profile": "Profil", + "add_event" : "Ajouter un évènement", + "about": "À propos de Caroster" }, + "sections":{ + "future": "Évènement à venir", + "future_plural": "Évènements à venir", + "past" : "Évènement passé", + "past_plural": "Évènements passés" + } , "noEvent": { "title": "Tableau de bord Caroster", "text_html": "Ceci est votre tableau de bord, vous y verrez <strong>les évènements auxquels vous participer</strong>",
M app/src/pages/Dashboard.jsapp/src/pages/Dashboard.js

@@ -1,22 +1,24 @@

import React, {useEffect, useState, useCallback, useMemo} from 'react'; import {useStrapi, useAuth} from 'strapi-react-context'; -import Layout from '../layouts/Centered'; -import Card from '@material-ui/core/Card'; -import CardActions from '@material-ui/core/CardActions'; -import CardContent from '@material-ui/core/CardContent'; -import Grid from '@material-ui/core/Grid'; -import {makeStyles} from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; -import Button from '@material-ui/core/Button'; -import {useTranslation} from 'react-i18next'; +import LayoutCentered from '../layouts/Centered'; +import LayoutDefault from '../layouts/Default'; import moment from 'moment'; +import Loading from './Loading'; +import DashboardWithCard, { + EmptyDashboard, + DashboardFab, +} from '../containers/Dashboard'; +import {useTranslation} from 'react-i18next'; +import GenericMenu from '../containers/GenericMenu'; + +import {useHistory} from 'react-router-dom'; const Dashboard = () => { - const classes = useStyles(); - const {t} = useTranslation(); const [myEvents, setMyEvents] = useState([]); const [isLoading, setIsLoading] = useState(true); + const {t} = useTranslation(); const strapi = useStrapi(); const {authState, token} = useAuth(); + const history = useHistory(); const sortDesc = ({date: dateA}, {date: dateB}) => dateB.localeCompare(dateA); const pastEvents = useMemo( () =>

@@ -50,6 +52,7 @@ setMyEvents(myEvents);

}, [strapi.services.events] ); + useEffect(() => { if (!token) return; setIsLoading(true);

@@ -65,78 +68,54 @@ .substring(-1)

).then(() => setIsLoading(false)); }, [authState, token, fetchEvents]); + if (isLoading) return <Loading />; + if (!token || !myEvents) return <div>Not connected</div>; if (!isLoading && myEvents.length === 0) { return ( - <Layout> - <Card> - <CardContent> - <Typography gutterBottom variant="h5" component="h1"> - {t('dashboard.noEvent.title')} - </Typography> - <Typography - variant="body1" - dangerouslySetInnerHTML={{ - __html: t('dashboard.noEvent.text_html'), - }} - /> - </CardContent> - <CardActions> - <Button>{t('dashboard.actions.create_event')}</Button> - </CardActions> - </Card> - </Layout> + <LayoutCentered> + <EmptyDashboard /> + </LayoutCentered> ); } + const goProfile = history.push.bind(undefined, '/profile'); + const goNewEvent = history.push.bind(undefined, '/new'); + const goAbout = () => (window.location.href = t('meta.about_href')); + return ( - <Layout> - <Grid container className={classes.root} spacing={2}> - <Grid item xs={6}> - <Grid container justify="center" spacing={4}> - {[...futureEvents, ...noDateEvents, ...pastEvents].map(event => ( - <Grid key={event.id} item> - <Card className={classes.card}> - <CardContent> - <Typography gutterBottom variant="h6" component="h3"> - {event.name} - </Typography> - <Typography variant="body1"> - {t('event.fields.starts_on')} - </Typography> + <> + <GenericMenu + title={t('dashboard.title')} + actions={[ + { + label: t('dashboard.actions.add_event'), + onClick: goNewEvent, + id: 'AddEventTabs', + }, + { + label: t('dashboard.actions.see_profile'), + onClick: goProfile, + id: 'ProfileTabs', + }, - <Typography variant="body2" gutterBottom> - {event.date || t('event.fields.empty')} - </Typography> - - <Typography variant="body1"> - {t('event.fields.address')} - </Typography> - <Typography variant="body2" gutterBottom> - {event.address || t('event.fields.empty')} - </Typography> - </CardContent> - <CardActions> - <Button href={`/e/${event.id}`}> - {t('dashboard.actions.see_event')} - </Button> - </CardActions> - </Card> - </Grid> - ))} - </Grid> - </Grid> - </Grid> - </Layout> + { + label: t('dashboard.actions.about'), + onClick: goAbout, + id: 'AboutTabs', + }, + ]} + /> + <LayoutDefault> + <DashboardWithCard + pastEvents={pastEvents} + futureEvents={futureEvents} + noDateEvents={noDateEvents} + /> + <DashboardFab onClick={() => goNewEvent()} /> + </LayoutDefault> + </> ); }; -const useStyles = makeStyles(theme => ({ - root: { - flexGrow: 1, - }, - card: { - minWidth: '300px', - }, -})); export default Dashboard;
M app/src/pages/Event.jsapp/src/pages/Event.js

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

import {useEvent, EventProvider} from '../contexts/Event'; import {useToast} from '../contexts/Toast'; import Layout from '../layouts/Default'; -import Loading from '../pages/Loading'; +import Loading from './Loading'; import EventMenu from '../containers/EventMenu'; import EventDetails from '../containers/EventDetails'; import EventFab from '../containers/EventFab';