This commit is contained in:
Sébastien GLATZ
2019-06-27 11:12:46 +02:00
9 changed files with 329 additions and 96 deletions
+3 -2
View File
@@ -23,8 +23,9 @@ export const fetchCurrentUserFailure = user => ({
user
});
export const registerUserRequest = () => ({
type: REQUEST_REGISTER
export const registerUserRequest = (payload) => ({
type: REQUEST_REGISTER,
payload
});
export const registerUserSuccess = () => ({
+133 -48
View File
@@ -1,8 +1,15 @@
import React from 'react';
import { TextField, FormGroup, FormControlLabel, Checkbox, Button, Typography, Link } from '@material-ui/core';
import React, {Fragment} from 'react';
import { connect } from "react-redux";
import Validator from "validator";
import {FormLabel, FormHelperText,InputLabel, Input,TextField, FormGroup, FormControlLabel, Checkbox, Button, Typography, Link } from '@material-ui/core';
import Visibility from '@material-ui/icons/Visibility';
import VisibilityOff from '@material-ui/icons/VisibilityOff';
import IconButton from '@material-ui/core/IconButton';
import InputAdornment from '@material-ui/core/InputAdornment';
import FormControl from '@material-ui/core/FormControl';
import { withStyles } from '@material-ui/core/styles';
import { registerUserRequest, subscribeToNewsletterRequest } from "../../actions/user"
const styles = theme => ({
container: {
@@ -20,22 +27,84 @@ const styles = theme => ({
input50: {
width: '50%',
},
checkboxError:{
color:'red'
}
});
class Register extends React.Component {
state = {
prenom:'',
nom:'',
email:'',
password:'',
checkNewsletter:false,
checkCGU:false
data: {
firstname: '',
lastname: '',
email: '',
password: '',
checkNewsletter: false,
checkCGU: false,
},
errors: {},
loading: false,
showPassword:false
}
validate = data => {
const errors = {};
if (!Validator.isEmail(data.email)) errors.email = "Email invalide";
if (!!!data.password) errors.password = "Requis";
if (!!!data.firstname) errors.firstname = "Requis";
if (!!!data.lastname) errors.lastname = "Requis";
if (!data.checkCGU) errors.checkCGU = "Requis";
return errors;
};
onRegister = e => {
e.preventDefault();
const errors = this.validate(this.state.data);
console.log('onSubmit')
console.log(errors)
this.setState({ errors: errors });
if (Object.keys(errors).length === 0) {
this.setState({ loading: true });
this.props.registerUserRequest(this.state.data)
if (this.state.data.checkNewsletter)
this.props.subscribeToNewsletterRequest({ email: this.state.data.email })
}
};
componentWillReceiveProps(nextProps) {
this.setState({
errors: nextProps.errors,
loading: nextProps.loading
})
}
onChange = e =>
this.setState({
data: { ...this.state.data, [e.target.name]: e.target.value }
});
handleCheckBoxChange = name => event => {
this.setState({data:{ ...this.state.data, [name]: event.target.checked }});
};
handleChangePasswordVisibility = prop => event => {
this.setState({ [prop]: event.target.value });
};
handleClickShowPassword = () => {
this.setState({ showPassword: !this.state.showPassword });
};
render() {
const { classes } = this.props;
const { data, errors } = this.state;
console.log('render errors');
console.log(errors);
console.log(this.state);
return (
<div className={classes.container}>
@@ -43,77 +112,86 @@ class Register extends React.Component {
<form className={classes.form}>
<FormGroup row>
<TextField
value={this.state.prenom}
value={data.firstname}
className={classes.input50}
onChange={this.onChange}
id="prenom"
name="prenom"
name="firstname"
label="Prénom"
margin="normal"
required
error={this.state.prenom === ""}
helperText={this.state.prenom === "" ? 'Ce champ est vide' : ' '}
error={!!errors.firstname}
/>
<TextField
value={this.state.nom}
value={data.lastname}
className={classes.input50}
onChange={this.onChange}
id="nom"
name="nom"
name="lastname"
label="Nom"
margin="normal"
required
error={this.state.nom === ""}
helperText={this.state.nom === "" ? 'Ce champ est vide' : ' '}
error={!!errors.lastname}
/>
</FormGroup>
<TextField
value={this.state.email}
id="email"
value={data.email}
onChange={this.onChange}
id="emailRegister"
name="email"
label="Email"
margin="normal"
type="email"
required
error={this.state.email === ""}
helperText={this.state.email === "" ? 'Ce champ est vide' : ' '}
/>
<TextField
value={this.state.password}
id="password"
name="password"
label="Mot de passe"
margin="normal"
type="password"
required
error={this.state.password === ""}
helperText={this.state.password === "" ? 'Ce champ est vide' : ' '}
error={!!errors.email}
autoComplete="username email"
/>
<FormControl>
<InputLabel htmlFor="adornment-password">Password *</InputLabel>
<Input
name="password"
id="adornment-password"
type={this.state.showPassword ? 'text' : 'password'}
value={this.state.password}
onChange={this.onChange}
endAdornment={
<InputAdornment position="end">
<IconButton aria-label="Toggle password visibility" onClick={this.handleClickShowPassword}>
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
autoComplete="current-password"
error={!!errors.password}
/>
</FormControl>
<FormControlLabel
value={this.state.checkNewsletter}
control={
<Checkbox
value="checkedI"
<Checkbox value={data.checkNewsletter}
onChange={this.handleCheckBoxChange('checkNewsletter')}
/>
}
label="Je souhaite recevoir la newsletter de Talents Tube"
/>
<FormControlLabel
value={this.state.checkCGU}
name="checkCGU"
className={!!errors.checkCGU&&classes.checkboxError}
control={
<Checkbox
value="checkedI"
<Checkbox
error={!!errors.checkCGU }
value={data.checkCGU}
onChange={this.handleCheckBoxChange('checkCGU')}
/>
}
label="Je déclare avoir pris connaissance et accepte les termes et les CGU"
label="Je déclare avoir pris connaissance et accepte les termes et les CGU*"
required
error={this.state.checkCGU === false}
helperText={this.state.checkCGU === "" ? 'Ce champ est requis!' : ' '}
/>
<Button type="submit" variant="outlined" color="primary">
<Button type="submit" variant="outlined" color="primary" onClick={this.onRegister}>
S'inscrire
</Button>
@@ -123,10 +201,10 @@ class Register extends React.Component {
>Ou</Typography>
</form>
<Button variant="outlined" color="primary">
<Button variant="outlined" color="primary" onClick={this.props.linkedInAuth}>
Connexion avec Linkedin
</Button>
<Button variant="outlined" color="primary">
<Button variant="outlined" color="primary" onClick={this.props.facebookAuth}>
Connexion avec Facebook
</Button>
<div className={classes.form}>
@@ -145,4 +223,11 @@ class Register extends React.Component {
}
}
export default withStyles(styles)(Register);
function mapStateToProps(state) {
return {
loading: !!state.user.loading,
errors: state.user.errors
};
}
export default withStyles(styles)(connect(mapStateToProps, { registerUserRequest, subscribeToNewsletterRequest })(Register));
+162 -36
View File
@@ -12,9 +12,53 @@ import Validator from "validator";
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Router from 'next/router'
import Register from '../components/organisms/Register';
import Snackbar from '@material-ui/core/Snackbar';
import SnackbarContent from '@material-ui/core/SnackbarContent';
import { requestLogin, requestOauthLogin,requestLoginFailure } from "../actions/auth";
import { makeStyles } from '@material-ui/core/styles';
import clsx from 'clsx';
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import IconButton from '@material-ui/core/IconButton';
import ErrorIcon from '@material-ui/icons/Error';
import InfoIcon from '@material-ui/icons/Info';
import CloseIcon from '@material-ui/icons/Close';
import WarningIcon from '@material-ui/icons/Warning';
import { amber, green } from '@material-ui/core/colors';
import _ from "lodash"
const variantIcon = {
success: CheckCircleIcon,
warning: WarningIcon,
error: ErrorIcon,
info: InfoIcon,
};
const useStyles1 = makeStyles(theme => ({
success: {
backgroundColor: green[600],
},
error: {
backgroundColor: theme.palette.error.dark,
},
info: {
backgroundColor: theme.palette.primary.main,
},
warning: {
backgroundColor: amber[700],
},
icon: {
fontSize: 20,
},
iconVariant: {
opacity: 0.9,
marginRight: theme.spacing(1),
},
message: {
display: 'flex',
alignItems: 'center',
},
}));
function TabContainer({ children, dir }) {
return (
<Typography component="div" dir={dir} style={{ padding: 8 * 3 }}>
@@ -55,13 +99,37 @@ class LoginPage extends React.Component {
},
errors: {},
loading: false,
tabIndex:0
tabIndex:0,
openSnack:false
};
static getInitialProps({query}) {
const { error } = query
console.log("LoginPage getInitialProps")
console.log(error)
console.log("LoginPage getInitialProps")
return { errors: {message:error}, openSnack:!!error };
}
componentWillReceiveProps(nextProps) {
console.log("LoginPage componentWillReceiveProps")
console.log(nextProps)
console.log(!!nextProps.errors)
console.log("LoginPage componentWillReceiveProps")
this.setState({
errors: nextProps.errors,
loading: nextProps.loading
loading: nextProps.loading,
openSnack: _.isEmpty(nextProps.errors)
})
console.log("LoginPage componentWillReceiveProps 2")
console.log(nextProps)
console.log("LoginPage componentWillReceiveProps 2")
}
componentDidMount()
{
}
onChange = e =>
@@ -104,43 +172,55 @@ class LoginPage extends React.Component {
Router.push('/auth/facebook')
}
handleChange= (event, newValue) => {
handleChange = (event, newValue) => {
console.log('handleChange')
this.setState({ tabIndex: newValue });
}
handleChangeIndex= (index) =>{
handleChangeIndex = (index) => {
console.log('handleChangeIndex')
this.setState({ tabIndex: index });
}
handleClose = (event, reason) => {
// if (reason === 'clickaway') {
// return;
// }
this.setState({ openSnack: false });
}
render() {
const { classes } = this.props;
const { data, errors, loading } = this.state;
const { data, errors, loading, openSnack } = this.state;
console.log("render this.state")
console.log(this.state)
console.log("this.state")
return (
<Layout>
<div className={classes.container}>
{!!errors && (
<div className="alert alert-danger">{errors.message}</div>
)}
{/* {!!errors && (
<div className="alert alert-danger">{errors && errors.message}</div>
)} */}
{loading && (
<div className="alert alert-info"><CircularProgress color="secondary" />Chargement...</div>
)}
{!loading && (
<Fragment>
<AppBar position="static" color="default">
<Tabs
value={this.state.tabIndex}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab label="Connexion" />
<Tab label="Inscription" />
</Tabs>
</AppBar>
<Fragment>
<AppBar position="static" color="default">
<Tabs
value={this.state.tabIndex}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab label="Connexion" />
<Tab label="Inscription" />
</Tabs>
</AppBar>
<SwipeableViews
axis={ 'x'}
index={this.state.tabIndex}
@@ -160,8 +240,8 @@ class LoginPage extends React.Component {
onChange={this.onChange}
autoComplete="username"
required
error={data.email === ""}
helperText={data.email === "" ? 'Ce champ est vide' : ' '}
// error={data.email === ""}
// helperText={data.email === "" ? 'Ce champ est vide' : ' '}
/>
<TextField
@@ -174,8 +254,8 @@ class LoginPage extends React.Component {
value={data.password}
onChange={this.onChange}
autoComplete="current-password"
error={data.password === ""}
helperText={data.password === "" ? 'Ce champ est vide' : ' '}
// error={data.password === ""}
// helperText={data.password === "" ? 'Ce champ est vide' : ' '}
required
/>
@@ -208,32 +288,78 @@ class LoginPage extends React.Component {
<Typography
paragraph={true}
align="center"
>Je ne possède pas de compte Talents Tube ?
<Link href={"/Register"} className={classes.link}>
>Je ne possède pas de compte Talents Tube ?
<span onClick={() => this.handleChangeIndex(1)} className={classes.link}>
S'inscrire
</Link>
</span>
</Typography>
</div>
<div className={classes.form}><Typography
paragraph={true}
align="center"
>{this.state.openSnack}
</Typography></div>
</Fragment>
</TabContainer>
<TabContainer dir={'x'}>Item Two</TabContainer>
</SwipeableViews>
</Fragment>
</TabContainer>
<TabContainer dir={'x'}>
<Register linkedInAuth={this.linkedInAuth} facebookAuth={this.facebookAuth} />
</TabContainer>
</SwipeableViews>
</Fragment>
)}
</div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
open={openSnack}
autoHideDuration={6000}
>
<MySnackbarContentWrapper
variant="error"
className={classes.margin}
message={errors.message}
onClose={this.handleClose} />
</Snackbar>
</Layout>
);
}
}
function mapStateToProps(state) {
function mapStateToProps(state,ownProps) {
return {
openSnack:!_.isEmpty(ownProps.errors),
loading: !!state.user.loading,
errors: state.user.errors
errors: state.user.errors || ownProps.errors
};
}
function MySnackbarContentWrapper(props) {
const classes = useStyles1();
const { className, message, onClose, variant, ...other } = props;
const Icon = variantIcon[variant];
return (
<SnackbarContent
className={clsx(classes[variant], className)}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<Icon className={clsx(classes.icon, classes.iconVariant)} />
{message}
</span>
}
action={[
<IconButton key="close" aria-label="Close" color="inherit" onClick={onClose}>
<CloseIcon className={classes.icon} />
</IconButton>,
]}
{...other}
/>
);
}
export default withStyles(styles)(connect(mapStateToProps, { requestLogin, requestOauthLogin, requestLoginFailure })(LoginPage));
+11
View File
@@ -0,0 +1,11 @@
import React, { Component } from 'react'
export default class VerifyEmail extends Component {
render() {
return (
<div>
Vérifiez vos email pour confirmer votre inscription.
</div>
)
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ export function user(state = {
case REQUEST_LOGIN_FAILURE:
return {
loading: false,
errors: { ...action.data.message },
errors: { ...action.message },
}
case FETCH_CURRENT_USER_REQUEST:
return {
+3 -1
View File
@@ -3,13 +3,14 @@
import { all, call, delay, put, take, takeLatest } from 'redux-saga/effects'
import es6promise from 'es6-promise'
import { requestLoginSaga, requestUserSaga, requestLogoutSaga, requestNewsletterSubscribitionSaga} from './sagas/userSaga.js'
import { requestLoginSaga, requestUserSaga, requestRegisterSaga, requestLogoutSaga, requestNewsletterSubscribitionSaga} from './sagas/userSaga.js'
import { requestAdsSaga, requestAdSaga, requestPromotedAdsSaga } from './sagas/offreSaga.js'
import {requestCompaniesSaga} from './sagas/companySaga.js'
import {
REQUEST_LOGIN,
REQUEST_LOGOUT,
REQUEST_REGISTER,
FETCH_CURRENT_USER_REQUEST,
FETCH_ADS_REQUEST,
FETCH_AD_REQUEST,
@@ -26,6 +27,7 @@ function* rootSaga() {
yield all([
takeLatest(REQUEST_LOGIN, requestLoginSaga),
takeLatest(REQUEST_LOGOUT, requestLogoutSaga),
takeLatest(REQUEST_REGISTER, requestRegisterSaga),
takeLatest(FETCH_CURRENT_USER_REQUEST, requestUserSaga),
takeLatest(SUBSCRIBE_NEWSLETTER_REQUEST, requestNewsletterSubscribitionSaga),
takeLatest(FETCH_ADS_REQUEST, requestAdsSaga),
+7 -3
View File
@@ -1,8 +1,11 @@
import { call, put } from "redux-saga/effects";
import { userLoggedIn, requestLoginFailure,userLoggedOut } from "../actions/auth";
import { fetchCurrentUserSuccess, fetchCurrentUserFailure,
subscribeToNewsletterSuccess,subscribeToNewsletterFailure } from "../actions/user";
import { userLoggedIn, requestLoginFailure, userLoggedOut } from "../actions/auth";
import {
fetchCurrentUserSuccess, fetchCurrentUserFailure,
subscribeToNewsletterSuccess, subscribeToNewsletterFailure,
registerUserSuccess, registerUserFailure
} from "../actions/user";
import setAuthorizationHeader from "../data/setAuthorizationHeader";
import api from "../data/api";
@@ -100,6 +103,7 @@ export function* requestRegisterSaga(action) {
if (res.data.success === true) {
yield put(registerUserSuccess(res));
Router.push('/VerifyEmail');
} else
throw new Error("not subscribed")
+9 -4
View File
@@ -70,12 +70,13 @@ app.prepare()
server.use(passport.initialize());
server.get('/auth/facebook',
passport.authorize('facebook', { scope: ['email', 'public_profile'], session: false }));
passport.authorize('facebook', { scope: ['email', 'public_profile'], session: false, failureRedirect: '/login'}));
server.get('/auth/facebook/callback',
passport.authorize('facebook', { failureRedirect: '/login', session: false }),
(req, res) => {
// Successful authentication, redirect home.
console.log('facebookcallback')
console.log(req.account)//don't understand why !
// console.log(req.accessToken)
@@ -91,11 +92,15 @@ app.prepare()
})
server.get('/auth/linkedin',
passport.authorize('linkedin', { scope: ['r_basicprofile', 'r_emailaddress', 'r_liteprofile'], session: false }));
passport.authorize('linkedin', { scope: ['r_basicprofile', 'r_emailaddress', 'r_liteprofile'], session: false , failureRedirect: '/login'}));
server.get('/auth/linkedin/callback',
passport.authorize('linkedin', { failureRedirect: '/login', session: false }),
function (req, res) {
function (error, req, res, next) {
if (error) {
console.log(error)
return res.redirect('/login?error='+error.message)
}
// Successful authentication, redirect home.
console.log('linkedincallback')
console.log(req.accessToken)
-1
View File
@@ -119,7 +119,6 @@ class parametres extends Component {
const topicsTemp = cloneDeep(this.state.topics)
topicsTemp.map(temp =>
temp.value = !!topics[temp.key]
)
this.setState({ topics: topicsTemp })
}