<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React + Redux - User Registration and Login Examplel</title>
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<style>
a { cursor: pointer; }
.help-block { font-size: 12px; }
</style>
<!-- transpile and run react code in browser with jspm -->
<script src="https://jspm.io/system.js"></script>
<script src="config.js"></script>
<script type="text/javascript">
System.import('./index.jsx');
</script>
</head>
<body>
<div id="app"><h2 class="text-center">Loading...</h2></div>
</body>
</html>
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './_helpers';
import { App } from './App';
// setup fake backend
import { configureFakeBackend } from './_helpers';
configureFakeBackend();
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
export * from './App.jsx';
import React from 'react';
import { Router, Route } from 'react-router';
import { connect } from 'react-redux';
import { history } from '../_helpers';
import { alertActions } from '../_actions';
import { PrivateRoute } from '../_components';
import { HomePage } from '../HomePage';
import { LoginPage } from '../LoginPage';
import { RegisterPage } from '../RegisterPage';
class App extends React.Component {
constructor(props) {
super(props);
// this line is required to work on plunker because the app preview runs on a subfolder url
history.push('/');
const { dispatch } = this.props;
history.listen((location, action) => {
// clear alert on location change
dispatch(alertActions.clear());
});
}
render() {
const basePath = '/' + location.pathname.split('/')[1];
const { alert } = this.props;
return (
<div className="jumbotron">
<div className="container">
<div className="col-sm-8 col-sm-offset-2">
{alert.message &&
<div className={`alert ${alert.type}`}>{alert.message}</div>
}
<Router history={history}>
<div>
<PrivateRoute exact path='/' component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/register" component={RegisterPage} />
</div>
</Router>
</div>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { alert } = state;
return {
alert
};
}
const connectedApp = connect(mapStateToProps)(App);
export { connectedApp as App };
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import rootReducer from '../_reducers';
const loggerMiddleware = createLogger();
export const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
);
export * from './fake-backend';
export * from './history';
export * from './store';
export * from './auth-header';
import { combineReducers } from 'redux';
import { authentication } from './authentication.reducer';
import { registration } from './registration.reducer';
import { users } from './users.reducer';
import { alert } from './alert.reducer';
const rootReducer = combineReducers({
authentication,
registration,
users,
alert
});
export default rootReducer;
import { alertConstants } from '../_constants';
export function alert(state = {}, action) {
switch (action.type) {
case alertConstants.SUCCESS:
return {
type: 'alert-success',
message: action.message
};
case alertConstants.ERROR:
return {
type: 'alert-danger',
message: action.message
};
case alertConstants.CLEAR:
return {};
default:
return state
}
}
import { userConstants } from '../_constants';
let user = JSON.parse(localStorage.getItem('user'));
const initialState = user ? { loggedIn: true, user } : {};
export function authentication(state = initialState, action) {
switch (action.type) {
case userConstants.LOGIN_REQUEST:
return {
loggingIn: true,
user: action.user
};
case userConstants.LOGIN_SUCCESS:
return {
loggedIn: true,
user: action.user
};
case userConstants.LOGIN_FAILURE:
return {};
case userConstants.LOGOUT:
return {};
default:
return state
}
}
import { userConstants } from '../_constants';
export function registration(state = {}, action) {
switch (action.type) {
case userConstants.REGISTER_REQUEST:
return { registering: true };
case userConstants.REGISTER_SUCCESS:
return {};
case userConstants.REGISTER_FAILURE:
return {};
default:
return state
}
}
import { userConstants } from '../_constants';
export function users(state = {}, action) {
switch (action.type) {
case userConstants.GETALL_REQUEST:
return {
loading: true
};
case userConstants.GETALL_SUCCESS:
return {
items: action.users
};
case userConstants.GETALL_FAILURE:
return {
error: action.error
};
case userConstants.DELETE_REQUEST:
// add 'deleting:true' property to user being deleted
return {
...state,
items: state.items.map(user =>
user.id === action.id
? { ...user, deleting: true }
: user
)
};
case userConstants.DELETE_SUCCESS:
// remove deleted user from state
return {
items: state.items.filter(user => user.id !== action.id)
};
case userConstants.DELETE_FAILURE:
// remove 'deleting:true' property and add 'deleteError:[error]' property to user
return {
...state,
items: state.items.map(user => {
if (user.id === action.id) {
// make copy of user without 'deleting:true' property
const { deleting, ...userCopy } = user;
// return copy of user with 'deleteError:[error]' property
return { ...userCopy, deleteError: action.error };
}
return user;
})
};
default:
return state
}
}
export const alertConstants = {
SUCCESS: 'ALERT_SUCCESS',
ERROR: 'ALERT_ERROR',
CLEAR: 'ALERT_CLEAR'
};
export * from './alert.constants';
export * from './user.constants';
export const userConstants = {
REGISTER_REQUEST: 'USERS_REGISTER_REQUEST',
REGISTER_SUCCESS: 'USERS_REGISTER_SUCCESS',
REGISTER_FAILURE: 'USERS_REGISTER_FAILURE',
LOGIN_REQUEST: 'USERS_LOGIN_REQUEST',
LOGIN_SUCCESS: 'USERS_LOGIN_SUCCESS',
LOGIN_FAILURE: 'USERS_LOGIN_FAILURE',
LOGOUT: 'USERS_LOGOUT',
GETALL_REQUEST: 'USERS_GETALL_REQUEST',
GETALL_SUCCESS: 'USERS_GETALL_SUCCESS',
GETALL_FAILURE: 'USERS_GETALL_FAILURE',
DELETE_REQUEST: 'USERS_DELETE_REQUEST',
DELETE_SUCCESS: 'USERS_DELETE_SUCCESS',
DELETE_FAILURE: 'USERS_DELETE_FAILURE'
};
/*global System */
'use strict';
System.config({
transpiler: "babel",
packages: {
'./': { defaultExtension: false },
'./_actions': { main: 'index.js' },
'./_components': { main: 'index.js', defaultExtension: false },
'./_constants': { main: 'index.js' },
'./_helpers': { main: 'index.js' },
'./_reducers': { main: 'index.js' },
'./_services': { main: 'index.js' },
'./App': { main: 'index.js', defaultExtension: false },
'./HomePage': { main: 'index.js', defaultExtension: false },
'./LoginPage': { main: 'index.js', defaultExtension: false },
'./RegisterPage': { main: 'index.js', defaultExtension: false }
},
map: {
'npm:react@16.0eta.5': 'npm:react@16.0.0',
'react-redux': 'npm:react-redux@5.0.2'
},
paths: {
"*": "https://npm.jspm.io/*.js"
}
});
import { alertConstants } from '../_constants';
export const alertActions = {
success,
error,
clear
};
function success(message) {
return { type: alertConstants.SUCCESS, message };
}
function error(message) {
return { type: alertConstants.ERROR, message };
}
function clear() {
return { type: alertConstants.CLEAR };
}
export * from './alert.actions';
export * from './user.actions';
import { userConstants } from '../_constants';
import { userService } from '../_services';
import { alertActions } from './';
import { history } from '../_helpers';
export const userActions = {
login,
logout,
register,
getAll,
delete: _delete
};
function login(username, password) {
return dispatch => {
dispatch(request({ username }));
userService.login(username, password)
.then(
user => {
dispatch(success(user));
history.push('/');
},
error => {
dispatch(failure(error));
dispatch(alertActions.error(error));
}
);
};
function request(user) { return { type: userConstants.LOGIN_REQUEST, user } }
function success(user) { return { type: userConstants.LOGIN_SUCCESS, user } }
function failure(error) { return { type: userConstants.LOGIN_FAILURE, error } }
}
function logout() {
userService.logout();
return { type: userConstants.LOGOUT };
}
function register(user) {
return dispatch => {
dispatch(request(user));
userService.register(user)
.then(
user => {
dispatch(success());
history.push('/login');
dispatch(alertActions.success('Registration successful'));
},
error => {
dispatch(failure(error));
dispatch(alertActions.error(error));
}
);
};
function request(user) { return { type: userConstants.REGISTER_REQUEST, user } }
function success(user) { return { type: userConstants.REGISTER_SUCCESS, user } }
function failure(error) { return { type: userConstants.REGISTER_FAILURE, error } }
}
function getAll() {
return dispatch => {
dispatch(request());
userService.getAll()
.then(
users => dispatch(success(users)),
error => dispatch(failure(error))
);
};
function request() { return { type: userConstants.GETALL_REQUEST } }
function success(users) { return { type: userConstants.GETALL_SUCCESS, users } }
function failure(error) { return { type: userConstants.GETALL_FAILURE, error } }
}
// prefixed function name with underscore because delete is a reserved word in javascript
function _delete(id) {
return dispatch => {
dispatch(request(id));
userService.delete(id)
.then(
user => {
dispatch(success(id));
},
error => {
dispatch(failure(id, error));
}
);
};
function request(id) { return { type: userConstants.DELETE_REQUEST, id } }
function success(id) { return { type: userConstants.DELETE_SUCCESS, id } }
function failure(id, error) { return { type: userConstants.DELETE_FAILURE, id, error } }
}
export * from './PrivateRoute.jsx';
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
localStorage.getItem('user')
? <Component {...props} />
: <Redirect to={{ pathname: '/login', state: { from: props.location } }} />
)} />
)
export function authHeader() {
// return authorization header with jwt token
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.token) {
return { 'Authorization': 'Bearer ' + user.token };
} else {
return {};
}
}
// array in local storage for registered users
let users = JSON.parse(localStorage.getItem('users')) || [];
export function configureFakeBackend() {
let realFetch = window.fetch;
window.fetch = function (url, opts) {
return new Promise((resolve, reject) => {
// wrap in timeout to simulate server api call
setTimeout(() => {
// authenticate
if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
// get parameters from post request
let params = JSON.parse(opts.body);
// find if any user matches login credentials
let filteredUsers = users.filter(user => {
return user.username === params.username && user.password === params.password;
});
if (filteredUsers.length) {
// if login details are valid return user details and fake jwt token
let user = filteredUsers[0];
let responseJson = {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
token: 'fake-jwt-token'
};
resolve({ ok: true, json: () => responseJson });
} else {
// else return error
reject('Username or password is incorrect');
}
return;
}
// get users
if (url.endsWith('/users') && opts.method === 'GET') {
// check for fake auth token in header and return users if valid, this security is implemented server side in a real application
if (opts.headers && opts.headers.Authorization === 'Bearer fake-jwt-token') {
resolve({ ok: true, json: () => users });
} else {
// return 401 not authorised if token is null or invalid
reject('Unauthorised');
}
return;
}
// get user by id
if (url.match(/\/users\/\d+$/) && opts.method === 'GET') {
// check for fake auth token in header and return user if valid, this security is implemented server side in a real application
if (opts.headers && opts.headers.Authorization === 'Bearer fake-jwt-token') {
// find user by id in users array
let urlParts = url.split('/');
let id = parseInt(urlParts[urlParts.length - 1]);
let matchedUsers = users.filter(user => { return user.id === id; });
let user = matchedUsers.length ? matchedUsers[0] : null;
// respond 200 OK with user
resolve({ ok: true, json: () => user});
} else {
// return 401 not authorised if token is null or invalid
reject('Unauthorised');
}
return;
}
// register user
if (url.endsWith('/users/register') && opts.method === 'POST') {
// get new user object from post body
let newUser = JSON.parse(opts.body);
// validation
let duplicateUser = users.filter(user => { return user.username === newUser.username; }).length;
if (duplicateUser) {
reject('Username "' + newUser.username + '" is already taken');
return;
}
// save new user
newUser.id = users.length ? Math.max(...users.map(user => user.id)) + 1 : 1;
users.push(newUser);
localStorage.setItem('users', JSON.stringify(users));
// respond 200 OK
resolve({ ok: true, json: () => ({}) });
return;
}
// delete user
if (url.match(/\/users\/\d+$/) && opts.method === 'DELETE') {
// check for fake auth token in header and return user if valid, this security is implemented server side in a real application
if (opts.headers && opts.headers.Authorization === 'Bearer fake-jwt-token') {
// find user by id in users array
let urlParts = url.split('/');
let id = parseInt(urlParts[urlParts.length - 1]);
for (let i = 0; i < users.length; i++) {
let user = users[i];
if (user.id === id) {
// delete user
users.splice(i, 1);
localStorage.setItem('users', JSON.stringify(users));
break;
}
}
// respond 200 OK
resolve({ ok: true, json: () => ({}) });
} else {
// return 401 not authorised if token is null or invalid
reject('Unauthorised');
}
return;
}
// pass through any requests not handled above
realFetch(url, opts).then(response => resolve(response));
}, 500);
});
}
}
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
export * from './user.service';
import { authHeader } from '../_helpers';
export const userService = {
login,
logout,
register,
getAll,
getById,
update,
delete: _delete
};
function login(username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
};
return fetch('/users/authenticate', requestOptions)
.then(response => {
if (!response.ok) {
return Promise.reject(response.statusText);
}
return response.json();
})
.then(user => {
// login successful if there's a jwt token in the response
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
function logout() {
// remove user from local storage to log user out
localStorage.removeItem('user');
}
function getAll() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch('/users', requestOptions).then(handleResponse);
}
function getById(id) {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch('/users/' + _id, requestOptions).then(handleResponse);
}
function register(user) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
};
return fetch('/users/register', requestOptions).then(handleResponse);
}
function update(user) {
const requestOptions = {
method: 'PUT',
headers: { ...authHeader(), 'Content-Type': 'application/json' },
body: JSON.stringify(user)
};
return fetch('/users/' + user.id, requestOptions).then(handleResponse);;
}
// prefixed function name with underscore because delete is a reserved word in javascript
function _delete(id) {
const requestOptions = {
method: 'DELETE',
headers: authHeader()
};
return fetch('/users/' + id, requestOptions).then(handleResponse);;
}
function handleResponse(response) {
if (!response.ok) {
return Promise.reject(response.statusText);
}
return response.json();
}
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
class HomePage extends React.Component {
componentDidMount() {
this.props.dispatch(userActions.getAll());
}
handleDeleteUser(id) {
return (e) => this.props.dispatch(userActions.delete(id));
}
render() {
const { user, users } = this.props;
return (
<div className="col-md-6 col-md-offset-3">
<h1>Hi {user.firstName}!</h1>
<p>You're logged in with React!!</p>
<h3>All registered users:</h3>
{users.loading && <em>Loading users...</em>}
{users.error && <span className="text-danger">ERROR: {users.error}</span>}
{users.items &&
<ul>
{users.items.map((user, index) =>
<li key={user.id}>
{user.firstName + ' ' + user.lastName}
{
user.deleting ? <em> - Deleting...</em>
: user.deleteError ? <span className="text-danger"> - ERROR: {user.deleteError}</span>
: <span> - <a onClick={this.handleDeleteUser(user.id)}>Delete</a></span>
}
</li>
)}
</ul>
}
<p>
<Link to="/login">Logout</Link>
</p>
</div>
);
}
}
function mapStateToProps(state) {
const { users, authentication } = state;
const { user } = authentication;
return {
user,
users
};
}
const connectedHomePage = connect(mapStateToProps)(HomePage);
export { connectedHomePage as HomePage };
export * from './HomePage.jsx';
export * from './LoginPage.jsx';
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
class LoginPage extends React.Component {
constructor(props) {
super(props);
// reset login status
this.props.dispatch(userActions.logout());
this.state = {
username: '',
password: '',
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const { username, password } = this.state;
const { dispatch } = this.props;
if (username && password) {
dispatch(userActions.login(username, password));
}
}
render() {
const { loggingIn } = this.props;
const { username, password, submitted } = this.state;
return (
<div className="col-md-6 col-md-offset-3">
<h2>Login</h2>
<form name="form" onSubmit={this.handleSubmit}>
<div className={'form-group' + (submitted && !username ? ' has-error' : '')}>
<label htmlFor="username">Username</label>
<input type="text" className="form-control" name="username" value={username} onChange={this.handleChange} />
{submitted && !username &&
<div className="help-block">Username is required</div>
}
</div>
<div className={'form-group' + (submitted && !password ? ' has-error' : '')}>
<label htmlFor="password">Password</label>
<input type="password" className="form-control" name="password" value={password} onChange={this.handleChange} />
{submitted && !password &&
<div className="help-block">Password is required</div>
}
</div>
<div className="form-group">
<button className="btn btn-primary">Login</button>
{loggingIn &&
<img src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
}
<Link to="/register" className="btn btn-link">Register</Link>
</div>
</form>
</div>
);
}
}
function mapStateToProps(state) {
const { loggingIn } = state.authentication;
return {
loggingIn
};
}
const connectedLoginPage = connect(mapStateToProps)(LoginPage);
export { connectedLoginPage as LoginPage };
export * from './RegisterPage.jsx';
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
class RegisterPage extends React.Component {
constructor(props) {
super(props);
this.state = {
user: {
firstName: '',
lastName: '',
username: '',
password: ''
},
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const { name, value } = event.target;
const { user } = this.state;
this.setState({
user: {
...user,
[name]: value
}
});
}
handleSubmit(event) {
event.preventDefault();
this.setState({ submitted: true });
const { user } = this.state;
const { dispatch } = this.props;
if (user.firstName && user.lastName && user.username && user.password) {
dispatch(userActions.register(user));
}
}
render() {
const { registering } = this.props;
const { user, submitted } = this.state;
return (
<div className="col-md-6 col-md-offset-3">
<h2>Register</h2>
<form name="form" onSubmit={this.handleSubmit}>
<div className={'form-group' + (submitted && !user.firstName ? ' has-error' : '')}>
<label htmlFor="firstName">First Name</label>
<input type="text" className="form-control" name="firstName" value={user.firstName} onChange={this.handleChange} />
{submitted && !user.firstName &&
<div className="help-block">First Name is required</div>
}
</div>
<div className={'form-group' + (submitted && !user.lastName ? ' has-error' : '')}>
<label htmlFor="lastName">Last Name</label>
<input type="text" className="form-control" name="lastName" value={user.lastName} onChange={this.handleChange} />
{submitted && !user.lastName &&
<div className="help-block">Last Name is required</div>
}
</div>
<div className={'form-group' + (submitted && !user.username ? ' has-error' : '')}>
<label htmlFor="username">Username</label>
<input type="text" className="form-control" name="username" value={user.username} onChange={this.handleChange} />
{submitted && !user.username &&
<div className="help-block">Username is required</div>
}
</div>
<div className={'form-group' + (submitted && !user.password ? ' has-error' : '')}>
<label htmlFor="password">Password</label>
<input type="password" className="form-control" name="password" value={user.password} onChange={this.handleChange} />
{submitted && !user.password &&
<div className="help-block">Password is required</div>
}
</div>
<div className="form-group">
<button className="btn btn-primary">Register</button>
{registering &&
<img src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
}
<Link to="/login" className="btn btn-link">Cancel</Link>
</div>
</form>
</div>
);
}
}
function mapStateToProps(state) {
const { registering } = state.registration;
return {
registering
};
}
const connectedRegisterPage = connect(mapStateToProps)(RegisterPage);
export { connectedRegisterPage as RegisterPage };