Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion frontend-react/src/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import routes, { routeKeys } from '../routes';
import useT from '../utils/translation';
import logo from '../assets/logo_codeforpoznan.svg';
import {
currentUserGroupsSelector,
currentUserUsernameSelector,
isAuthenticatedSelector,
} from '../store/selectors/auth';
Expand Down Expand Up @@ -60,12 +61,14 @@ const Sidebar = ({
const classes = useStyles();
const isAuthenticated = useSelector(isAuthenticatedSelector);
const username = useSelector(currentUserUsernameSelector);
const userGroups = useSelector(currentUserGroupsSelector);
const translations = {
// keys are taken from routes.js::routes[].key
[routeKeys.HOME]: useT('Home page'),
[routeKeys.LOGIN]: useT('Log in'),
[routeKeys.DRIVE]: useT('Add new drive'),
[routeKeys.DRIVES]: useT('Drives'),
[routeKeys.PASSENGER]: useT('Confirm drive'),
};

const links = useMemo(
Expand All @@ -74,7 +77,8 @@ const Sidebar = ({
(isAuthenticated ?
visibility === ROUTES_VISIBILITY.AUTHENTICATED :
visibility === ROUTES_VISIBILITY.GUEST)
)),
))
.filter(({ groups }) => !groups || userGroups.some(role => groups.includes(role))),
[
routes,
isAuthenticated,
Expand Down
15 changes: 14 additions & 1 deletion frontend-react/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
Redirect,
Route,
} from 'react-router-dom';
import { ROUTES_VISIBILITY } from './utils/constants';
import {
ROUTES_VISIBILITY,
USER_GROUPS,
} from './utils/constants';

export const renderRoutes = (routes = []) => (
<Suspense fallback={<div>Loading...</div>}>
Expand Down Expand Up @@ -47,6 +50,7 @@ const routeKeys = {
DEFAULT: 'default',
DRIVE: 'drive',
DRIVES: 'drives',
PASSENGER: 'passenger',
};

const routes = [
Expand Down Expand Up @@ -75,6 +79,15 @@ const routes = [
key: routeKeys.DRIVE,
component: lazy(() => import('./views/Drive')),
visibility: ROUTES_VISIBILITY.AUTHENTICATED,
groups: [USER_GROUPS.DRIVER],
},
{
exact: true,
path: '/passenger',
key: routeKeys.PASSENGER,
component: lazy(() => import('./views/Passenger')),
visibility: ROUTES_VISIBILITY.AUTHENTICATED,
groups: [USER_GROUPS.PASSENGER],
},
{
path: '*',
Expand Down
15 changes: 15 additions & 0 deletions frontend-react/src/store/selectors/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,18 @@ export const currentUserUsernameSelector = createSelector(
authSelector,
auth => auth.user?.username
);

export const currentUserGroupsSelector = createSelector(
authSelector,
auth => auth.user?.groups?.map(({ name }) => name) || []
);

export const currentUserRsaPrivDSelector = createSelector(
authSelector,
auth => auth.user?.rsaPrivD
);

export const currentUserRsaModulusNSelector = createSelector(
authSelector,
auth => auth.user?.rsaModulusN
);
5 changes: 5 additions & 0 deletions frontend-react/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ export const ROUTES_VISIBILITY = {
};

export const RSA_BIT_LENGTH = 19;

export const USER_GROUPS = {
PASSENGER: 'Passenger',
DRIVER: 'Driver',
};
111 changes: 111 additions & 0 deletions frontend-react/src/views/Passenger/components/ConfirmForm/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useState } from 'react';
import {
Box,
Button,
TextField,
} from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import { useFormik } from 'formik';
import * as yup from 'yup';

import useT from '../../../../utils/translation';

const ConfirmForm = ({ onSubmit }) => {
const submit = useT('Submit');
const clear = useT('Clear');
const codeFieldLabel = useT('Driver code');
const codeFieldError = useT('Code is required');
const codeLengthError = useT('Code has to be exactly 6 digits');
const signError = useT('Problem with signature generation');
const signSuccessInfo = useT('Generated signature code');

const [
generatedSignature,
setGeneratedSignature,
] = useState('');

const validationSchema = yup.object().shape({
code: yup.string()
.required(codeFieldError)
.length(6, codeLengthError),
});

const formik = useFormik({
initialValues: {
code: '',
},
validationSchema,
onSubmit: async ({ code }, { setErrors }) => {
setGeneratedSignature('');
onSubmit(code).then(
(response) => {
setGeneratedSignature(response);
}
)
.catch(() => {
setErrors({ code: signError });
});
},
});

return (
<form
onSubmit={formik.handleSubmit}
noValidate
>
<Box
display="flex"
flexDirection="column"
>
<Box
mb={3}
mt={2}
>
<TextField
id="code"
name="code"
fullWidth
label={codeFieldLabel}
type="text"
value={formik.values.code}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
error={formik.touched.code && Boolean(formik.errors.code)}
helperText={formik.touched.code && formik.errors.code}
/>
</Box>
</Box>
{generatedSignature && (
<Box mb={2}>
<Alert severity="success">
{signSuccessInfo}
{' '}
<b>{generatedSignature}</b>
</Alert>
</Box>
)}
<Box
display="flex"
justifyContent="space-between"
>
<Button
type="submit"
variant="contained"
color="primary"
disabled={formik.isSubmitting}
>
{submit}
</Button>
<Button
type="button"
variant="contained"
onClick={formik.resetForm}
>
{clear}
</Button>
</Box>
</form>
);
};

export default ConfirmForm;
49 changes: 49 additions & 0 deletions frontend-react/src/views/Passenger/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Typography } from '@material-ui/core';
import { useSelector } from 'react-redux';

import { useCallback } from 'react';
import Page from '../../components/Page';

import {
currentUserRsaPrivDSelector,
currentUserRsaModulusNSelector,
} from '../../store/selectors/auth';

import useT from '../../utils/translation';
import { sign } from '../../utils/crypto';

import ConfirmForm from './components/ConfirmForm';

const PassengerView = () => {
const title = useT('Confirm drive');
const rsaPrivD = useSelector(currentUserRsaPrivDSelector);
const rsaModulusN = useSelector(currentUserRsaModulusNSelector);

const generateSignature = useCallback(
code => new Promise((resolve, reject) => {
try {
resolve(String(sign(code, rsaPrivD, rsaModulusN)).padStart(6, '0'));
} catch (e) {
reject(e);
}
}),
[
rsaPrivD,
rsaModulusN,
]
);

return (
<Page title={title}>
<Typography
variant="h2"
component="h2"
>
{title}
</Typography>
<ConfirmForm onSubmit={generateSignature} />
</Page>
);
};

export default PassengerView;