all repos — caroster @ 60d64767353163a8100c5d56dccf7eb484b5b10d

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

🎨 Link users to events, do some refacto
Tim Izzo tim@octree.ch
Tue, 21 Jul 2020 08:56:02 +0000
commit

60d64767353163a8100c5d56dccf7eb484b5b10d

parent

7f378db1a463f777b274446840456613c75fe547

M .gitlab-ci.yml.gitlab-ci.yml

@@ -113,9 +113,10 @@ Code Quality:

stage: analyze image: ciricihq/gitlab-sonar-scanner rules: - - if: '$CI_PIPELINE_SOURCE =~ "schedule"' + - if: '$CI_COMMIT_REF_NAME == "master"' + when: manual script: - >- sonar-scanner -Dsonar.host.url=$SONAR_URL - -Dsonar.projectKey=$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG + -Dsonar.projectKey=$CI_PROJECT_NAME -Dsonar.login=$SONAR_LOGIN -Dsonar.sources=${SONAR_SOURCE:-.}
M api/car/documentation/1.0.0/car.jsonapi/car/documentation/1.0.0/car.json

@@ -575,6 +575,12 @@ "type": "object"

}, "waiting_list": { "type": "object" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } } } },
M api/event/documentation/1.0.0/event.jsonapi/event/documentation/1.0.0/event.json

@@ -580,6 +580,59 @@ "type": "object"

}, "waiting_list": { "type": "object" + }, + "users": { + "type": "array", + "items": { + "required": [ + "id", + "username", + "firstName", + "lastName", + "email" + ], + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "password": { + "type": "string" + }, + "resetPasswordToken": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "blocked": { + "type": "boolean" + }, + "role": { + "type": "string" + }, + "events": { + "type": "array", + "items": { + "type": "string" + } + } + } + } } } },

@@ -612,6 +665,12 @@ "type": "object"

}, "waiting_list": { "type": "object" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } } } }
M api/event/models/event.settings.jsonapi/event/models/event.settings.json

@@ -32,6 +32,11 @@ "type": "json"

}, "waiting_list": { "type": "json" + }, + "users": { + "via": "events", + "plugin": "users-permissions", + "collection": "user" } } }
M api/settings/documentation/1.0.0/settings.jsonapi/settings/documentation/1.0.0/settings.json

@@ -320,12 +320,18 @@ "type": "string"

}, "gtm_id": { "type": "string" + }, + "about_link": { + "type": "string" } } }, "NewSettings": { "properties": { "gtm_id": { + "type": "string" + }, + "about_link": { "type": "string" } }
M api/settings/models/settings.settings.jsonapi/settings/models/settings.settings.json

@@ -12,6 +12,9 @@ "attributes": {

"gtm_id": { "type": "string", "regex": "GTM-.*" + }, + "about_link": { + "type": "string" } } }
M app/package-lock.jsonapp/package-lock.json

@@ -24,7 +24,7 @@ },

"dependencies": { "semver": { "version": "5.7.1", - "resolved": "https://npm-8ee.hidora.com/semver/-/semver-5.7.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } }

@@ -131,7 +131,7 @@ },

"dependencies": { "semver": { "version": "5.7.1", - "resolved": "https://npm-8ee.hidora.com/semver/-/semver-5.7.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } }

@@ -1015,7 +1015,7 @@ },

"dependencies": { "semver": { "version": "5.7.1", - "resolved": "https://npm-8ee.hidora.com/semver/-/semver-5.7.1.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } }

@@ -3509,7 +3509,7 @@ },

"dependencies": { "anymatch": { "version": "3.1.1", - "resolved": "https://npm-8ee.hidora.com/anymatch/-/anymatch-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "requires": { "normalize-path": "^3.0.0",

@@ -3518,7 +3518,7 @@ }

}, "braces": { "version": "3.0.2", - "resolved": "https://npm-8ee.hidora.com/braces/-/braces-3.0.2.tgz", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "requires": { "fill-range": "^7.0.1"

@@ -3526,7 +3526,7 @@ }

}, "fill-range": { "version": "7.0.1", - "resolved": "https://npm-8ee.hidora.com/fill-range/-/fill-range-7.0.1.tgz", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "requires": { "to-regex-range": "^5.0.1"

@@ -3534,17 +3534,17 @@ }

}, "is-number": { "version": "7.0.0", - "resolved": "https://npm-8ee.hidora.com/is-number/-/is-number-7.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "normalize-path": { "version": "3.0.0", - "resolved": "https://npm-8ee.hidora.com/normalize-path/-/normalize-path-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "to-regex-range": { "version": "5.0.1", - "resolved": "https://npm-8ee.hidora.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { "is-number": "^7.0.0"

@@ -4850,7 +4850,7 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="

}, "memory-fs": { "version": "0.5.0", - "resolved": "https://npm-8ee.hidora.com/memory-fs/-/memory-fs-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", "requires": { "errno": "^0.1.3",

@@ -4859,7 +4859,7 @@ }

}, "readable-stream": { "version": "2.3.7", - "resolved": "https://npm-8ee.hidora.com/readable-stream/-/readable-stream-2.3.7.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0",

@@ -4873,7 +4873,7 @@ }

}, "string_decoder": { "version": "1.1.1", - "resolved": "https://npm-8ee.hidora.com/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0"

@@ -6877,7 +6877,7 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="

}, "ansi-styles": { "version": "4.2.1", - "resolved": "https://npm-8ee.hidora.com/ansi-styles/-/ansi-styles-4.2.1.tgz", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { "@types/color-name": "^1.1.1",

@@ -6895,7 +6895,7 @@ }

}, "color-convert": { "version": "2.0.1", - "resolved": "https://npm-8ee.hidora.com/color-convert/-/color-convert-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { "color-name": "~1.1.4"

@@ -6903,17 +6903,17 @@ }

}, "color-name": { "version": "1.1.4", - "resolved": "https://npm-8ee.hidora.com/color-name/-/color-name-1.1.4.tgz", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "has-flag": { "version": "4.0.0", - "resolved": "https://npm-8ee.hidora.com/has-flag/-/has-flag-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "strip-ansi": { "version": "6.0.0", - "resolved": "https://npm-8ee.hidora.com/strip-ansi/-/strip-ansi-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { "ansi-regex": "^5.0.0"

@@ -6921,7 +6921,7 @@ }

}, "supports-color": { "version": "7.1.0", - "resolved": "https://npm-8ee.hidora.com/supports-color/-/supports-color-7.1.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "requires": { "has-flag": "^4.0.0"
M app/src/containers/CreateEvent/index.jsapp/src/containers/CreateEvent/index.js

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

import React, {useState, useReducer} from 'react'; -import {useStrapi} from 'strapi-react-context'; -import useProfile from '../../hooks/useProfile'; +import {useStrapi, useAuth} from 'strapi-react-context'; // Steps import Step1 from './Step1';

@@ -12,18 +11,18 @@ const eventReducer = (state, item) => ({...state, ...item});

const CreateEvent = () => { const strapi = useStrapi(); + const {authState} = useAuth(); const [step, setStep] = useState(0); const [event, addToEvent] = useReducer(eventReducer, {}); const Step = steps[step]; - const {connected, addEvent} = useProfile(); const createEvent = async eventData => { try { const result = await strapi.services.events.create({ ...event, ...eventData, + users: authState ? [authState.user?.id] : null, }); - if (connected) addEvent(result); return result; } catch (err) { console.error(err);
D app/src/containers/Dashboard/Dashboard.js

@@ -1,58 +0,0 @@

-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={12} md={3} lg={4} key={event.id}> - <EventCard event={event} /> - </Grid> - )); - - return ( - <Grid container className={classes.root} spacing={4} xs={'auto'}> - {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', - width: '100%', - margin: '0 auto', - }, -})); -export default Dashboard;
M app/src/containers/Dashboard/DashboardFab.jsapp/src/containers/Fab/index.js

@@ -1,16 +1,16 @@

import React from 'react'; import Icon from '@material-ui/core/Icon'; -import Fab from '@material-ui/core/Fab'; +import FabMui from '@material-ui/core/Fab'; import {makeStyles} from '@material-ui/core/styles'; -export const DashboardFab = ({onClick, open}) => { +const Fab = ({open, ...props}) => { const classes = useStyles({open}); return ( <div className={classes.container}> - <Fab color="secondary" aria-label="dashboard-add" onClick={onClick}> + <FabMui color="secondary" {...props}> <Icon>add</Icon> - </Fab> + </FabMui> </div> ); };

@@ -25,3 +25,5 @@ transform: open ? 'rotate(45deg)' : '',

zIndex: theme.zIndex.speedDial, }), })); + +export default Fab;
D app/src/containers/Dashboard/EmptyDashboard.js

@@ -1,38 +0,0 @@

-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'; -import {useHistory} from 'react-router-dom'; -export const EmptyDashboard = () => { - const {t} = useTranslation(); - const history = useHistory(); - const goNewEvent = history.push.bind(undefined, '/new'); - return ( - <Card> - <CardContent> - <Typography gutterBottom variant="h5" component="h1"> - {t('dashboard.noEvent.title')} - </Typography> - <Typography - variant="body1" - gutterBottom - dangerouslySetInnerHTML={{ - __html: t('dashboard.noEvent.text_html'), - }} - /> - </CardContent> - <CardActions> - <Button - onClick={() => goNewEvent()} - variant="contained" - color="primary" - > - {t('dashboard.noEvent.create_event')} - </Button> - </CardActions> - </Card> - ); -};
M app/src/containers/Dashboard/EventCard.jsapp/src/containers/DashboardEvents/EventCard.js

@@ -5,7 +5,9 @@ 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}) => { +import {Link} from 'react-router-dom'; + +const EventCard = ({event}) => { const {t} = useTranslation(); return ( <Card>

@@ -23,10 +25,12 @@ {event.address || t('event.fields.empty')}

</Typography> </CardContent> <CardActions> - <Button href={`/e/${event.id}`}> - {t('dashboard.actions.see_event')} - </Button> + <Link to={`/e/${event.id}`}> + <Button>{t('dashboard.actions.see_event')}</Button> + </Link> </CardActions> </Card> ); }; + +export default EventCard;
D app/src/containers/Dashboard/index.js

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

-import Dashboard from './Dashboard'; -export * from './EventCard'; -export * from './EmptyDashboard'; -export * from './DashboardFab'; -export default Dashboard;
A app/src/containers/DashboardEmpty/index.js

@@ -0,0 +1,50 @@

+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 Container from '@material-ui/core/Container'; +import Button from '@material-ui/core/Button'; +import {useTranslation} from 'react-i18next'; +import {useHistory} from 'react-router-dom'; +import {makeStyles} from '@material-ui/core/styles'; + +const EmptyDashboard = () => { + const {t} = useTranslation(); + const history = useHistory(); + const classes = useStyles(); + + return ( + <Container maxWidth="sm" className={classes.container}> + <Card> + <CardContent> + <Typography gutterBottom variant="h5" component="h1"> + {t('dashboard.noEvent.title')} + </Typography> + <Typography + variant="body1" + gutterBottom + dangerouslySetInnerHTML={{ + __html: t('dashboard.noEvent.text_html'), + }} + /> + </CardContent> + <CardActions> + <Button + onClick={() => history.push('/new')} + variant="contained" + color="primary" + > + {t('dashboard.noEvent.create_event')} + </Button> + </CardActions> + </Card> + </Container> + ); +}; + +const useStyles = makeStyles(theme => ({ + container: {paddingTop: theme.spacing(8)}, +})); + +export default EmptyDashboard;
A app/src/containers/DashboardEvents/Section.js

@@ -0,0 +1,13 @@

+import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; + +const Section = ({children}) => ( + <Grid item xs={12}> + <Typography gutterBottom variant="h6" component="h3"> + {children} + </Typography> + </Grid> +); + +export default Section;
A app/src/containers/DashboardEvents/index.js

@@ -0,0 +1,54 @@

+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 Section from './Section'; + +const cardsForEvents = events => + events.map(event => ( + <Grid item xs={12} md={3} lg={4} key={event.id}> + <EventCard event={event} /> + </Grid> + )); + +const DashboardEvents = ({futureEvents, noDateEvents, pastEvents}) => { + const classes = useStyles(); + const {t} = useTranslation(); + + return ( + <Grid container className={classes.root} spacing={4} xs={'auto'}> + {futureEvents.length + noDateEvents.length > 0 && ( + <> + <Section> + {t('dashboard.sections.future', { + count: futureEvents.length + noDateEvents.length, + })} + </Section> + {cardsForEvents(futureEvents)} + {cardsForEvents(noDateEvents)} + </> + )} + + {pastEvents.length > 0 && ( + <> + <Section> + {t('dashboard.sections.past', {count: pastEvents.length})} + </Section> + {cardsForEvents(pastEvents)} + </> + )} + </Grid> + ); +}; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + maxWidth: '90rem', + width: '100%', + margin: '0 auto', + }, +})); + +export default DashboardEvents;
D app/src/containers/EventFab/index.js

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

-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'; - -const EventFab = ({toggleNewCar, open}) => { - const classes = useStyles({open}); - - return ( - <div className={classes.container}> - <Fab color="secondary" aria-label="add-car" onClick={toggleNewCar}> - <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, - }), -})); - -export default EventFab;
M app/src/containers/GenericMenu/index.jsapp/src/containers/GenericMenu/index.js

@@ -6,10 +6,23 @@ 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'; +import {useTranslation} from 'react-i18next'; +import {useStrapi} from 'strapi-react-context'; + const GenericMenu = ({title, actions = []}) => { + const {t} = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); const classes = useStyles(); + const strapi = useStrapi(); + const [settings] = strapi.stores?.settings || [{}]; const validActions = useMemo(() => actions.filter(Boolean), [actions]); + + const aboutMenuItem = { + label: t('menu.about'), + onClick: () => (window.location.href = settings['about_link']), + id: 'AboutTabs', + }; + useEffect(() => { window.scrollTo(0, 0); }, []);

@@ -41,7 +54,7 @@

<GenericToolbar anchorEl={anchorEl} setAnchorEl={setAnchorEl} - actions={validActions} + actions={[...validActions, aboutMenuItem]} /> </> )}
M app/src/containers/SignIn/SignIn.test.jsapp/src/containers/SignInForm/SignIn.test.js

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

import React from 'react'; import renderer from 'react-test-renderer'; -import SignIn from './SignIn'; +import SignIn from '.'; describe('SignIn', () => { const signIn = renderer.create(<SignIn />);
D app/src/containers/SignIn/index.js

@@ -1,3 +0,0 @@

-import SignIn from './SignIn'; - -export default SignIn;
M app/src/containers/SignUp/SignUp.jsapp/src/containers/SignUpForm/index.js

@@ -18,7 +18,7 @@ const history = useHistory();

const { location: {state: historyState = {}}, } = history; - console.log({event: historyState.event}); + const {signUp, token} = useAuth(); const [isLoading, setIsLoading] = useState(false); const [firstName, setFirstName] = useState('');
M app/src/containers/SignUp/SignUp.test.jsapp/src/containers/SignUpForm/SignUp.test.js

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

import React from 'react'; import renderer from 'react-test-renderer'; -import SignUp from './SignUp'; +import SignUp from '.'; describe('SignUp', () => { const signUpNode = renderer.create(<SignUp />);
D app/src/containers/SignUp/index.js

@@ -1,3 +0,0 @@

-import SignUp from './SignUp'; - -export default SignUp;
M app/src/containers/WaitingList/index.jsapp/src/containers/WaitingList/index.js

@@ -59,17 +59,16 @@ console.error(error);

addToast(t(i18nError)); } }, - [event] // eslint-disable-line + [event, addEvent] // eslint-disable-line ); const addPassenger = useCallback( - async passenger => { - return saveWaitingList( + async passenger => + saveWaitingList( [...(event.waiting_list || []), passenger], 'passenger.errors.cant_add_passenger' - ); - }, - [event] // eslint-disable-line + ), + [event, saveWaitingList] // eslint-disable-line ); const removePassenger = useCallback(

@@ -79,7 +78,7 @@ event.waiting_list.filter((_, i) => i !== index),

'passenger.errors.cant_remove_passenger' ); }, - [event] // eslint-disable-line + [event, saveWaitingList] // eslint-disable-line ); const selectCar = useCallback(
M app/src/hooks/useProfile.jsapp/src/hooks/useProfile.js

@@ -1,28 +1,43 @@

-import {useMemo, useCallback} from 'react'; -import {useAuth} from 'strapi-react-context'; +import {useState, useEffect} from 'react'; +import {useStrapi, useAuth} from 'strapi-react-context'; export default () => { - const {token, authState, updateProfile} = useAuth(); + const strapi = useStrapi(); + const {token, authState} = useAuth(); + const [profile, setProfile] = useState(); - const connected = useMemo(() => !!token, [token]); + useEffect(() => { + const getProfile = async () => { + try { + const fetchedProfile = await strapi.services.users.findOne('me'); + setProfile(fetchedProfile); + } catch (error) { + console.error(error); + setProfile(null); + } + }; + if (authState) getProfile(); + else setProfile(null); + }, [authState, strapi.services.users]); - const user = useMemo(() => authState?.user, [authState]); - - const addEvent = useCallback( - async event => { - if (!!token) { - const {user} = authState; - const {events} = user; - updateProfile({ - ...user, - events: !!events - ? [...events.filter(e => e !== event.id), event.id] - : [event.id], + const addEvent = async event => { + try { + if (!profile) + throw new Error(`Can't add event to logged user: profile empty`); + if (!profile?.events?.some(({id}) => id === event.id)) + await strapi.services.users.update('me', { + events: [...profile.events, event.id], }); - } - }, - [token, authState] // eslint-disable-line - ); + } catch (error) { + console.error(error); + } + }; - return {connected, user, addEvent}; + return { + profile, + addEvent, + connected: !!token, + user: authState?.user, + isReady: typeof profile !== 'undefined', + }; };
M app/src/layouts/Centered.jsapp/src/layouts/Centered.js

@@ -3,25 +3,25 @@ import Container from '@material-ui/core/Container';

import DefaultLayout from './Default'; import {makeStyles} from '@material-ui/core/styles'; -const useStyles = makeStyles(theme => ({ - layout: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - minHeight: '100vh', - }, -})); - -const CenteredLayout = ({children}) => { +const CenteredLayout = ({children, ...props}) => { const classes = useStyles(); return ( - <DefaultLayout> + <DefaultLayout {...props}> <div className={classes.layout}> <Container maxWidth="sm">{children}</Container> </div> </DefaultLayout> ); }; + +const useStyles = makeStyles(theme => ({ + layout: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + minHeight: '100vh', + }, +})); export default CenteredLayout;
M app/src/layouts/Default.jsapp/src/layouts/Default.js

@@ -1,7 +1,20 @@

import React from 'react'; +import GenericMenu from '../containers/GenericMenu'; -const DefaultLayout = ({children, className = undefined}) => { - return <div className={className}>{children}</div>; +const DefaultLayout = ({ + children, + className, + menuTitle = 'Caroster', + menuActions, +}) => { + return ( + <> + {(menuTitle || menuActions) && ( + <GenericMenu title={menuTitle} actions={menuActions} /> + )} + <div className={className}>{children}</div> + </> + ); }; export default DefaultLayout;
M app/src/locales/fr.jsonapp/src/locales/fr.json

@@ -20,7 +20,7 @@ "cancel": "Annuler",

"remove": "Supprimer", "save": "Enregistrer", "confirm": "Confirmer", - "errors":{ + "errors": { "unknown": "Une erreur inconnue c'est produite", "rejected": "Quelque chose c'est mal passé", "bad_data": "Il manque quelque chose",

@@ -67,7 +67,7 @@ },

"add_to_my_events": { "cancel": "Annuler", "login": "Se connecter", - "register" : "S'inscrire", + "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." }

@@ -110,21 +110,21 @@ "title": "Évènements",

"actions": { "see_event": "Voir" }, - "sections":{ + "sections": { "future": "Évènement à venir", "future_plural": "Évènements à venir", - "past" : "Évènement passé", + "past": "Évènement passé", "past_plural": "Évènements passés" - } , + }, "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" } }, - "profile":{ + "profile": { "title": "Mon profil" - } , + }, "passenger": { "title": "Liste d'attente", "availability": {

@@ -144,14 +144,14 @@ "cant_remove_passenger": "Impossible de retirer le passager",

"cant_select_car": "Impossible de sélectionner la voiture" } }, - "signup":{ + "signup": { "email": "Email", "firstName": "Prénom", "lastName": "Nom", "password": "Mot de passe", "submit": "Créer son compte", "login": "Déjà un compte ? Se connecter", - "errors":{ + "errors": { "email_taken": "Email déjà pris" }, "success": {
M app/src/pages/Dashboard.jsapp/src/pages/Dashboard.js

@@ -1,143 +1,88 @@

-import React, {useEffect, useState, useCallback, useMemo} from 'react'; -import {useStrapi, useAuth} from 'strapi-react-context'; -import LayoutCentered from '../layouts/Centered'; +import React, {useMemo} from 'react'; +import {useAuth} from 'strapi-react-context'; import LayoutDefault from '../layouts/Default'; import moment from 'moment'; import Loading from './Loading'; -import DashboardWithCard, { - EmptyDashboard, - DashboardFab, -} from '../containers/Dashboard'; +import DashboardEvents from '../containers/DashboardEvents'; +import DashboardEmpty from '../containers/DashboardEmpty'; +import Fab from '../containers/Fab'; import {useTranslation} from 'react-i18next'; -import GenericMenu from '../containers/GenericMenu'; import {makeStyles} from '@material-ui/core/styles'; import {useHistory} from 'react-router-dom'; - -const Menu = () => { - const history = useHistory(); - const {t} = useTranslation(); - const goProfile = history.push.bind(undefined, '/profile'); - const goNewEvent = history.push.bind(undefined, '/new'); - const goAbout = () => (window.location.href = t('meta.about_href')); +import useProfile from '../hooks/useProfile'; - return ( - <GenericMenu - title={t('dashboard.title')} - actions={[ - { - label: t('menu.new_event'), - onClick: goNewEvent, - id: 'AddEventTabs', - }, - { - label: t('menu.profile'), - onClick: goProfile, - id: 'ProfileTabs', - }, - { - label: t('menu.about'), - onClick: goAbout, - id: 'AboutTabs', - }, - ]} - /> - ); -}; const sortDesc = ({date: dateA}, {date: dateB}) => dateB.localeCompare(dateA); const Dashboard = () => { - const [myEvents, setMyEvents] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const strapi = useStrapi(); - const {authState, token} = useAuth(); + const {authState} = useAuth(); + const {profile, isReady} = useProfile(); const history = useHistory(); + const {t} = useTranslation(); const classes = useStyles(); - const goNewEvent = history.push.bind(undefined, '/new'); + const {events = []} = profile || {}; + const pastEvents = useMemo( () => - myEvents - .filter(({date}) => { - return date && moment(date).isBefore(moment(), 'day'); - }) + events + .filter(({date}) => date && moment(date).isBefore(moment(), 'day')) .sort(sortDesc), - [myEvents] - ); - const noDateEvents = useMemo( - () => - myEvents.filter(({date}) => { - return !date; - }), - [myEvents] + [events] ); + const futureEvents = useMemo( () => - myEvents - .filter(({date}) => { - return date && moment(date).isSameOrAfter(moment(), 'day'); - }) + events + .filter(({date}) => date && moment(date).isSameOrAfter(moment(), 'day')) .sort(sortDesc), - [myEvents] - ); - const fetchEvents = useCallback( - async query => { - const myEvents = await strapi.services.events.find(query); - setMyEvents(myEvents); - }, - [strapi.services.events] + [events] ); - useEffect(() => { - if (!token) return; - const { - user: {events = []}, - } = authState; - if (events.length > 0) { - setIsLoading(true); - fetchEvents( - events - .reduce((acc, eventId) => { - return acc + `id_in=${eventId}&`; - }, '') - .substring(-1) - ).then(() => setIsLoading(false)); - } else { - setIsLoading(false); - } - }, [authState, token, fetchEvents]); + const noDateEvents = useMemo(() => events.filter(({date}) => !date), [ + events, + ]); - if (isLoading) return <Loading />; - - if (!token || !myEvents) return <div>Not connected</div>; - - if (!isLoading && myEvents.length === 0) { + if (!authState || !isReady) return ( - <> - <Menu /> - <LayoutCentered> - <EmptyDashboard /> - <DashboardFab onClick={() => goNewEvent()} /> - </LayoutCentered> - </> + <LayoutDefault menuTitle={t('dashboard.title')}> + <Loading /> + </LayoutDefault> ); - } + + const menuActions = [ + { + label: t('menu.new_event'), + onClick: () => history.push('/new'), + id: 'AddEventTabs', + }, + { + label: t('menu.profile'), + onClick: () => history.push('/profile'), + id: 'ProfileTabs', + }, + ]; return ( - <> - <Menu /> - <LayoutDefault className={classes.root}> - <DashboardWithCard + <LayoutDefault + className={classes.root} + menuActions={menuActions} + menuTitle={t('dashboard.title')} + > + {!events || events.length === 0 ? ( + <DashboardEmpty /> + ) : ( + <DashboardEvents pastEvents={pastEvents} futureEvents={futureEvents} noDateEvents={noDateEvents} /> - <DashboardFab onClick={() => goNewEvent()} /> - </LayoutDefault> - </> + )} + <Fab onClick={() => history.push('/new')} aria-label="add-event" /> + </LayoutDefault> ); }; const useStyles = makeStyles(theme => ({ root: { - marginTop: '50px', + marginTop: theme.mixins.toolbar.minHeight, }, })); export default Dashboard;
M app/src/pages/Event.jsapp/src/pages/Event.js

@@ -15,7 +15,7 @@ import Layout from '../layouts/Default';

import Loading from './Loading'; import EventMenu from '../containers/EventMenu'; import EventDetails from '../containers/EventDetails'; -import EventFab from '../containers/EventFab'; +import Fab from '../containers/Fab'; import CarColumns from '../containers/CarColumns'; import NewCarDialog from '../containers/NewCarDialog'; import AddToMyEventDialog from '../containers/AddToMyEventDialog';

@@ -209,7 +209,7 @@ </Container>

)} </AppBar> <CarColumns toggleNewCar={toggleNewCar} /> - <EventFab toggleNewCar={toggleNewCar} open={openNewCar} /> + <Fab onClick={toggleNewCar} open={openNewCar} aria-label="add-car" /> <NewCarDialog open={openNewCar} toggle={toggleNewCar} /> <AddToMyEventDialog open={isAddToMyEvent}
M app/src/pages/Home.jsapp/src/pages/Home.js

@@ -6,62 +6,47 @@ import CreateEvent from '../containers/CreateEvent';

import {useAuth} from 'strapi-react-context'; import {useHistory} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; -import GenericMenu from '../containers/GenericMenu'; -const Menu = () => { + +const Home = () => { const history = useHistory(); const {t} = useTranslation(); const {token} = useAuth(); - const goProfile = history.push.bind(undefined, '/profile'); - const goDashboard = history.push.bind(undefined, '/dashboard'); - const goLogin = history.push.bind(undefined, '/login'); - const goRegister = history.push.bind(undefined, '/register'); - const goAbout = () => (window.location.href = t('meta.about_href')); + + const noUserMenuActions = [ + { + label: t('menu.login'), + onClick: () => history.push('/login'), + id: 'LoginTabs', + }, + { + label: t('menu.register'), + onClick: () => history.push('/register'), + id: 'RegisterTabs', + }, + ]; + + const loggedMenuActions = [ + { + label: t('menu.dashboard'), + onClick: () => history.push('/dashboard'), + id: 'SeeDashboardTabs', + }, + { + label: t('menu.profile'), + onClick: () => history.push('/profile'), + id: 'ProfileTabs', + }, + ]; - return ( - <GenericMenu - title={t('event.creation.title')} - actions={[ - !!token && { - label: t('menu.dashboard'), - onClick: goDashboard, - id: 'SeeDashboardTabs', - }, - !!token && { - label: t('menu.profile'), - onClick: goProfile, - id: 'ProfileTabs', - }, - !token && { - label: t('menu.login'), - onClick: goLogin, - id: 'LoginTabs', - }, - !token && { - label: t('menu.register'), - onClick: goRegister, - id: 'RegisterTabs', - }, - { - label: t('menu.about'), - onClick: goAbout, - id: 'AboutTabs', - }, - ]} - /> - ); -}; + const menuActions = token ? loggedMenuActions : noUserMenuActions; -const Home = () => { return ( - <> - <Menu /> - <Layout> - <Paper> - <Logo /> - <CreateEvent /> - </Paper> - </Layout> - </> + <Layout menuTitle={t('event.creation.title')} menuActions={menuActions}> + <Paper> + <Logo /> + <CreateEvent /> + </Paper> + </Layout> ); };
M app/src/pages/Profile.jsapp/src/pages/Profile.js

@@ -2,53 +2,35 @@ import React from 'react';

import {useAuth} from 'strapi-react-context'; import Layout from '../layouts/Centered'; import {useTranslation} from 'react-i18next'; -import GenericMenu from '../containers/GenericMenu'; - import {useHistory} from 'react-router-dom'; -const Menu = () => { +const Profile = () => { const history = useHistory(); const {t} = useTranslation(); const {logout} = useAuth(); - const goDashboard = history.push.bind(undefined, '/dashboard'); - const goNewEvent = history.push.bind(undefined, '/new'); - const goAbout = () => (window.location.href = t('meta.about_href')); - return ( - <GenericMenu - title={t('profile.title')} - actions={[ - { - label: t('menu.new_event'), - onClick: goNewEvent, - id: 'AddEventTabs', - }, - { - label: t('menu.dashboard'), - onClick: goDashboard, - id: 'DashboardTabs', - }, - { - label: t('menu.logout'), - onClick: logout, - id: 'LogoutTabs', - }, - { - label: t('menu.about'), - onClick: goAbout, - id: 'AboutTabs', - }, - ]} - /> - ); -}; + const menuActions = [ + { + label: t('menu.new_event'), + onClick: () => history.push('/new'), + id: 'AddEventTabs', + }, + { + label: t('menu.dashboard'), + onClick: () => history.push('/dashboard'), + id: 'DashboardTabs', + }, + { + label: t('menu.logout'), + onClick: logout, + id: 'LogoutTabs', + }, + ]; -const Profile = () => { return ( - <> - <Menu /> - <Layout>Profile – NOT IMPLEMENTED</Layout> - </> + <Layout menuTitle={t('profile.title')} menuActions={menuActions}> + Profile – NOT IMPLEMENTED + </Layout> ); };
M app/src/pages/SignIn.jsapp/src/pages/SignIn.js

@@ -3,14 +3,14 @@ import Layout from '../layouts/Centered';

import Card from '@material-ui/core/Card'; import CardMedia from '@material-ui/core/CardMedia'; import Logo from '../components/Logo'; -import SignInContainer from '../containers/SignIn'; +import SignInForm from '../containers/SignInForm'; const SignIn = () => { return ( <Layout> <Card> <CardMedia component={Logo} /> - <SignInContainer /> + <SignInForm /> </Card> </Layout> );
M app/src/pages/SignUp.jsapp/src/pages/SignUp.js

@@ -3,14 +3,14 @@ import Layout from '../layouts/Centered';

import Card from '@material-ui/core/Card'; import CardMedia from '@material-ui/core/CardMedia'; import Logo from '../components/Logo'; -import Su from '../containers/SignUp'; +import SignUpForm from '../containers/SignUpForm'; const SignUp = () => { return ( <Layout> <Card> <CardMedia component={Logo} /> - <Su /> + <SignUpForm /> </Card> </Layout> );
M app/src/pages/SignUpSuccess.jsapp/src/pages/SignUpSuccess.js

@@ -10,12 +10,13 @@ import CardActions from '@material-ui/core/CardActions';

import Typography from '@material-ui/core/Typography'; import {useAuth} from 'strapi-react-context'; import {Redirect} from 'react-router-dom'; + const SignUpSuccess = () => { const {t} = useTranslation(); const {token} = useAuth(); - if (!token) { - return <Redirect to="/" />; - } + + if (!token) return <Redirect to="/" />; + return ( <Layout> <Card>
M config/permissions.jsonconfig/permissions.json

@@ -22,6 +22,38 @@ "actions": ["find"]

} ] } + ], + "authenticated": [ + { + "type": "application", + "controllers": [ + { + "name": "car", + "actions": ["create", "delete", "find", "findone", "update"] + }, + { + "name": "event", + "actions": ["create", "findone", "update"] + }, + { + "name": "page", + "actions": ["find", "findone"] + }, + { + "name": "settings", + "actions": ["find"] + } + ] + }, + { + "type": "users-permissions", + "controllers": [ + { + "name": "user", + "actions": ["updateme"] + } + ] + } ] } }
M extensions/documentation/documentation/1.0.0/full_documentation.jsonextensions/documentation/documentation/1.0.0/full_documentation.json

@@ -14,7 +14,7 @@ "license": {

"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "x-generation-date": "07/08/2020 12:13:00 PM" + "x-generation-date": "21.07.2020 08:40:46" }, "x-strapi-config": { "path": "/documentation",

@@ -1950,6 +1950,12 @@ "type": "object"

}, "waiting_list": { "type": "object" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } } } },

@@ -2056,6 +2062,59 @@ "type": "object"

}, "waiting_list": { "type": "object" + }, + "users": { + "type": "array", + "items": { + "required": [ + "id", + "username", + "firstName", + "lastName", + "email" + ], + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "password": { + "type": "string" + }, + "resetPasswordToken": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "blocked": { + "type": "boolean" + }, + "role": { + "type": "string" + }, + "events": { + "type": "array", + "items": { + "type": "string" + } + } + } + } } } },

@@ -2088,6 +2147,12 @@ "type": "object"

}, "waiting_list": { "type": "object" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } } } },

@@ -2143,12 +2208,18 @@ "type": "string"

}, "gtm_id": { "type": "string" + }, + "about_link": { + "type": "string" } } }, "NewSettings": { "properties": { "gtm_id": { + "type": "string" + }, + "about_link": { "type": "string" } }
D extensions/users-permissions/config/policies/setMe.js

@@ -1,6 +0,0 @@

-// Set authenticated user ID as queried ID -module.exports = async (ctx, next) => { - ctx.params.id = ctx.state.user.id; - console.log('USER ID', ctx.state.user.id); - await next(); -};
M extensions/users-permissions/config/routes.jsonextensions/users-permissions/config/routes.json

@@ -3,16 +3,15 @@ "routes": [

{ "method": "PUT", "path": "/users/me", - "handler": "CarosterUser.update", - + "handler": "User.updateMe", "config": { - "policies": ["plugins::users-permissions.setme"], + "policies": [], "prefix": "", - "description": "Update an existing user", + "description": "Update authenticated user", "tag": { "plugin": "users-permissions", "name": "User", - "actionType": "update" + "actionType": "updateMe" } } }
D extensions/users-permissions/controllers/CarosterUser.js

@@ -1,111 +0,0 @@

-const _ = require('lodash'); - -module.exports = { - /** - * Update a record. - * - * @return {Object} - */ - update: async ctx => { - console.log('I AM HEREE'); - const advancedConfigs = await strapi - .store({ - environment: '', - type: 'plugin', - name: 'users-permissions', - key: 'advanced', - }) - .get(); - - const {id} = ctx.params; - const { - email, - username, - password, - firstName, - lastName, - events = [], - } = ctx.request.body; - - const user = await strapi.plugins['users-permissions'].services.user.fetch({ - id, - }); - - if (_.has(ctx.request.body, 'email') && !email) { - return ctx.badRequest('email.notNull'); - } - - if (_.has(ctx.request.body, 'firstName') && !firstName) { - return ctx.badRequest('firstName.notNull'); - } - - if (_.has(ctx.request.body, 'lastName') && !lastName) { - return ctx.badRequest('lastName.notNull'); - } - - if (_.has(ctx.request.body, 'username') && !username) { - return ctx.badRequest('username.notNull'); - } - - if ( - _.has(ctx.request.body, 'password') && - !password && - user.provider === 'local' - ) { - return ctx.badRequest('password.notNull'); - } - - if (_.has(ctx.request.body, 'username')) { - const userWithSameUsername = await strapi - .query('user', 'users-permissions') - .findOne({username}); - - if (userWithSameUsername && userWithSameUsername.id != id) { - return ctx.badRequest( - null, - formatError({ - id: 'Auth.form.error.username.taken', - message: 'username.alreadyTaken.', - field: ['username'], - }) - ); - } - } - - if (_.has(ctx.request.body, 'email') && advancedConfigs.unique_email) { - const userWithSameEmail = await strapi - .query('user', 'users-permissions') - .findOne({email}); - - if (userWithSameEmail && userWithSameEmail.id != id) { - return ctx.badRequest( - null, - formatError({ - id: 'Auth.form.error.email.taken', - message: 'Email already taken', - field: ['email'], - }) - ); - } - } - - let updateData = { - ...ctx.request.body, - }; - - if (_.has(ctx.request.body, 'password') && password === user.password) { - delete updateData.password; - } - - if (!_.has(ctx.request.body, 'events')) { - updateData.events = []; - } - - const data = await strapi.plugins['users-permissions'].services.user.edit( - {id}, - updateData - ); - - ctx.send({...data}); - }, -};
A extensions/users-permissions/controllers/User.js

@@ -0,0 +1,63 @@

+const {removeUndefined, sanitizeEntity} = require('strapi-utils'); + +module.exports = { + /** + * Update authenticated user. + * + * @return {Object} + */ + updateMe: async ctx => { + const user = ctx.state.user; + + if (!user) { + return ctx.badRequest(null, [ + {messages: [{id: 'No authorization header was found'}]}, + ]); + } + + const { + username, + email, + password, + firstname, + lastname, + events, + } = ctx.request.body; + + const data = await strapi.plugins['users-permissions'].services.user.edit( + {id: user.id}, + removeUndefined({ + username, + email, + password, + firstname, + lastname, + events, + }) + ); + + ctx.send(data); + }, + + /** + * Retrieve authenticated user. + * @return {Object} + */ + async me(ctx) { + const {id} = ctx.state.user; + + const user = await strapi.plugins['users-permissions'].services.user.fetch({ + id, + }); + + if (!user) { + return ctx.badRequest(null, [ + {messages: [{id: 'No authorization header was found'}]}, + ]); + } + const data = sanitizeEntity(user, { + model: strapi.query('user', 'users-permissions').model, + }); + ctx.send(data); + }, +};
M extensions/users-permissions/models/User.settings.jsonextensions/users-permissions/models/User.settings.json

@@ -67,7 +67,9 @@ "plugin": "users-permissions",

"configurable": false }, "events": { - "type": "json" + "collection": "event", + "via": "users", + "dominant": true } } }