Logins and logouts (sessions) are working

This commit is contained in:
2019-05-08 11:35:57 +10:00
parent 0d4bb2f57f
commit fbb37ad2d9
15 changed files with 477 additions and 74 deletions
+113 -45
View File
@@ -1424,29 +1424,48 @@
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
}, },
"body-parser": { "body-parser": {
"version": "1.18.3", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": { "requires": {
"bytes": "3.0.0", "bytes": "3.1.0",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "~1.1.2", "depd": "~1.1.2",
"http-errors": "~1.6.3", "http-errors": "1.7.2",
"iconv-lite": "0.4.23", "iconv-lite": "0.4.24",
"on-finished": "~2.3.0", "on-finished": "~2.3.0",
"qs": "6.5.2", "qs": "6.7.0",
"raw-body": "2.3.3", "raw-body": "2.4.0",
"type-is": "~1.6.16" "type-is": "~1.6.17"
}, },
"dependencies": { "dependencies": {
"iconv-lite": { "http-errors": {
"version": "0.4.23", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
} }
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
} }
} }
}, },
@@ -1669,9 +1688,9 @@
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
}, },
"bytes": { "bytes": {
"version": "3.0.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
}, },
"cacache": { "cacache": {
"version": "11.3.2", "version": "11.3.2",
@@ -2563,10 +2582,51 @@
"vary": "~1.1.2" "vary": "~1.1.2"
}, },
"dependencies": { "dependencies": {
"body-parser": {
"version": "1.18.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
"requires": {
"bytes": "3.0.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "~1.6.3",
"iconv-lite": "0.4.23",
"on-finished": "~2.3.0",
"qs": "6.5.2",
"raw-body": "2.3.3",
"type-is": "~1.6.16"
}
},
"bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
},
"iconv-lite": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"path-to-regexp": { "path-to-regexp": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"raw-body": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
"requires": {
"bytes": "3.0.0",
"http-errors": "1.6.3",
"iconv-lite": "0.4.23",
"unpipe": "1.0.0"
}
} }
} }
}, },
@@ -3079,13 +3139,11 @@
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -3098,18 +3156,15 @@
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -3212,8 +3267,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -3223,7 +3277,6 @@
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -3236,7 +3289,6 @@
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -3336,8 +3388,7 @@
}, },
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -3347,7 +3398,6 @@
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -3453,7 +3503,6 @@
"string-width": { "string-width": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -5318,23 +5367,37 @@
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
}, },
"raw-body": { "raw-body": {
"version": "2.3.3", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": { "requires": {
"bytes": "3.0.0", "bytes": "3.1.0",
"http-errors": "1.6.3", "http-errors": "1.7.2",
"iconv-lite": "0.4.23", "iconv-lite": "0.4.24",
"unpipe": "1.0.0" "unpipe": "1.0.0"
}, },
"dependencies": { "dependencies": {
"iconv-lite": { "http-errors": {
"version": "0.4.23", "version": "1.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": { "requires": {
"safer-buffer": ">= 2.1.2 < 3" "depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
} }
},
"setprototypeof": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
} }
} }
}, },
@@ -6311,6 +6374,11 @@
"repeat-string": "^1.6.1" "repeat-string": "^1.6.1"
} }
}, },
"toidentifier": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
"trim-right": { "trim-right": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+1
View File
@@ -19,6 +19,7 @@
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.5",
"bcrypt": "^3.0.6", "bcrypt": "^3.0.6",
"body-parser": "^1.19.0",
"dotenv": "^8.0.0", "dotenv": "^8.0.0",
"express": "^4.16.4", "express": "^4.16.4",
"forever": "^1.0.0", "forever": "^1.0.0",
+72 -1
View File
@@ -127,7 +127,78 @@ function verify(connection) {
} }
} }
function login(connection) {
return (req, res) => {
//formidable handles forms
let form = formidable.IncomingForm();
//parse form
form.parse(req, (err, fields) => {
if (err) throw err;
//validate email, username and password
if (!validateEmail(fields.email) || fields.password.length < 8) {
res.write('<p>Invalid login data</p>');
res.end();
return;
}
//find this email's information
let query = 'SELECT id, username, salt, hash FROM accounts WHERE email = ?;';
connection.query(query, [fields.email], (err, results) => {
if (err) throw err;
//found this email?
if (results.length === 0) {
res.status(400).write('Incorrect email or password');
res.end();
return;
}
//gen a new hash from the salt and password
bcrypt.hash(fields.password, results[0].salt, (err, newHash) => {
if (err) throw err;
//compare the passwords
if (results[0].hash !== newHash) {
res.status(400).write('Incorrect email or password');
res.end();
return;
}
//create the new session
let rand = Math.floor(Math.random() * 100000);
let query = 'INSERT INTO sessions (accountId, token) VALUES (?, ?);';
connection.query(query, [results[0].id, rand], (err) => {
if (err) throw err;
//send json containing the account info
res.status(200).json({
id: results[0].id,
email: fields.email,
username: results[0].username,
token: rand
});
});
});
});
});
}
}
function logout(connection) {
return (req, res) => {
let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?) AND token = ?;';
connection.query(query, [req.body.email, req.body.token], (err) => {
if (err) throw err;
});
}
}
module.exports = { module.exports = {
signup: signup, signup: signup,
verify: verify verify: verify,
login: login,
logout: logout
}; };
+5
View File
@@ -5,8 +5,11 @@ require('dotenv').config();
let express = require('express'); let express = require('express');
let app = express(); let app = express();
let http = require('http').Server(app); let http = require('http').Server(app);
let bodyParser = require('body-parser');
let path = require('path'); let path = require('path');
app.use(bodyParser.json());
//database //database
let { connectToDatabase } = require('./database.js'); let { connectToDatabase } = require('./database.js');
let connection = connectToDatabase(); //uses .env let connection = connectToDatabase(); //uses .env
@@ -15,6 +18,8 @@ let connection = connectToDatabase(); //uses .env
let accounts = require('./accounts.js'); let accounts = require('./accounts.js');
app.post('/signup', accounts.signup(connection)); app.post('/signup', accounts.signup(connection));
app.get('/verify', accounts.verify(connection)); app.get('/verify', accounts.verify(connection));
app.post('/login', accounts.login(connection));
app.post('/logout', accounts.logout(connection));
//static directories //static directories
app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) ); app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) );
+12 -2
View File
@@ -1,4 +1,4 @@
CREATE TABLE signups ( CREATE TABLE IF NOT EXISTS signups (
email VARCHAR(320) UNIQUE, email VARCHAR(320) UNIQUE,
username VARCHAR(100) UNIQUE, username VARCHAR(100) UNIQUE,
salt VARCHAR(50), salt VARCHAR(50),
@@ -7,7 +7,7 @@ CREATE TABLE signups (
verify INTEGER DEFAULT 0 verify INTEGER DEFAULT 0
); );
CREATE TABLE accounts ( CREATE TABLE IF NOT EXISTS accounts (
id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE, id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE,
td TIMESTAMP DEFAULT CURRENT_TIMESTAMP(), td TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
@@ -17,3 +17,13 @@ CREATE TABLE accounts (
hash VARCHAR(100) hash VARCHAR(100)
); );
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE,
td TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
accountId INTEGER UNSIGNED,
token INTEGER DEFAULT 0,
CONSTRAINT FOREIGN KEY fk_accountId(accountId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE
);
+2 -1
View File
@@ -1,3 +1,4 @@
DROP TABLE signups; DROP TABLE signups;
DROP TABLE accounts; DROP TABLE accounts;
DROP TABLE profiles; DROP TABLE sessions;
#DROP TABLE profiles;
+18
View File
@@ -0,0 +1,18 @@
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export function login(id, email, username, token) {
return {
type: LOGIN,
id: id,
email: email,
username: username,
token: token
};
}
export function logout() {
return {
type: LOGOUT
};
}
-7
View File
@@ -1,7 +0,0 @@
export const ACTION_NAME = 'ACTION_NAME';
export function actionName() {
return {
type: ACTION_NAME
};
}
+27 -2
View File
@@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
//panels //panels
import Signup from '../panels/signup.jsx'; import Signup from '../panels/signup.jsx';
import Login from '../panels/login.jsx';
import Logout from '../panels/logout.jsx';
class Home extends React.Component { class Home extends React.Component {
constructor(props) { constructor(props) {
@@ -13,10 +15,33 @@ class Home extends React.Component {
} }
render() { render() {
//well this is goofy
let SidePanel;
if (this.props.id) {
SidePanel = () => {
return (
<div>
<p>You are logged in.</p>
<Logout />
</div>
);
};
} else {
SidePanel = () => {
return (
<div>
<Signup />
<Login />
</div>
);
};
}
return ( return (
<div className='page'> <div className='page'>
<p>This is the home page.</p> <p>This is the home page.</p>
<Signup /> <SidePanel />
</div> </div>
); );
} }
@@ -24,7 +49,7 @@ class Home extends React.Component {
function mapStoreToProps(store) { function mapStoreToProps(store) {
return { return {
// id: store.account.id
} }
} }
+131
View File
@@ -0,0 +1,131 @@
import React from 'react';
import { connect } from 'react-redux';
import { login } from '../../actions/accounts.js';
import { validateEmail } from '../../../common/utilities.js';
class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
warning: ''
};
}
render() {
let warningStyle = {
display: this.state.warning.length > 0 ? 'flex' : 'none'
};
return (
<div className='panel'>
<h1>Login</h1>
<div className='warning' style={warningStyle}>
<p>{this.state.warning}</p>
</div>
<form action='/login' method='post' onSubmit={(e) => this.submit(e)}>
<div>
<label>Email:</label>
<input type='text' name='email' value={this.state.email} onChange={this.updateEmail.bind(this)} />
</div>
<div>
<label>Password:</label>
<input type='password' name='password' value={this.state.password} onChange={this.updatePassword.bind(this)} />
</div>
<button type='submit'>Login</button>
</form>
</div>
);
}
submit(e) {
e.preventDefault();
if (!this.validateInput()) {
return;
}
//build the XHR
let form = e.target;
let formData = new FormData(form);
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
let json = JSON.parse(xhr.responseText);
this.props.login(json.id, json.email, json.username, json.token);
}
else if (xhr.status === 400) {
this.setWarning(xhr.responseText);
}
}
};
//send the XHR
xhr.open('POST', form.action, true);
xhr.send(formData);
}
validateInput(e) {
if (!validateEmail(this.state.email)) {
this.setWarning('Invalid Email');
return false;
}
if (this.state.password.length < 8) {
this.setWarning('Minimum password length is 8 characters');
return false;
}
return true;
}
setWarning(s) {
this.setState({
warning: s
});
}
clearInput() {
this.setState({
email: '',
password: '',
warning: ''
});
}
updateEmail(evt) {
this.setState({
email: evt.target.value
});
}
updatePassword(evt) {
this.setState({
password: evt.target.value
});
}
}
function mapStoreToProps(store) {
return {
//
}
}
function mapDispatchToProps(dispatch) {
return {
login: (id, email, username, token) => { dispatch(login(id, email, username, token)) }
}
}
Login = connect(mapStoreToProps, mapDispatchToProps)(Login);
export default Login;
+47
View File
@@ -0,0 +1,47 @@
import React from 'react';
import { connect } from 'react-redux';
import { logout } from '../../actions/accounts.js';
class Logout extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<button type='submit' onClick={(e) => this.submit(e)}>Logout</button>
);
}
submit(e) {
e.preventDefault();
//build the XHR
let xhr = new XMLHttpRequest();
xhr.open('POST', '/logout', true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify({
email: this.props.email,
token: this.props.token
}));
this.props.logout();
}
}
function mapStoreToProps(store) {
return {
email: store.account.email,
token: store.account.token
}
}
function mapDispatchToProps(dispatch) {
return {
logout: () => { dispatch(logout()) }
}
}
Logout = connect(mapStoreToProps, mapDispatchToProps)(Logout);
export default Logout;
+12 -2
View File
@@ -7,17 +7,27 @@ import thunk from 'redux-thunk';
import DevTools from './dev_tools.jsx'; import DevTools from './dev_tools.jsx';
import App from './components/app.jsx'; import App from './components/app.jsx';
import reducer from './reducers/reducer.jsx'; import reducer from './reducers/reducer.js';
//persistence
let ITEM_NAME = 'account.kingdombattles';
let account = localStorage.getItem(ITEM_NAME);
account = account ? JSON.parse(account) : {};
var store = createStore( var store = createStore(
reducer, reducer,
{}, //initial state { account: account }, //initial state
compose( compose(
applyMiddleware(thunk), applyMiddleware(thunk),
DevTools.instrument() DevTools.instrument()
) )
); );
//persistence
store.subscribe(() => {
localStorage.setItem(ITEM_NAME, JSON.stringify(store.getState().account));
});
//start the process //start the process
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
+29
View File
@@ -0,0 +1,29 @@
import { LOGIN, LOGOUT } from "../actions/accounts.js";
const initialStore = {
id: 0,
email: '',
username: '',
token: 0
};
export function accountReducer(store = initialStore, action) {
switch(action.type) {
case LOGIN:
let newStore = JSON.parse(JSON.stringify(initialStore));
newStore.id = action.id;
newStore.email = action.email;
newStore.username = action.username;
newStore.token = action.token;
return newStore;
case LOGOUT:
return initialStore;
default:
return store;
}
}
+8
View File
@@ -0,0 +1,8 @@
import { combineReducers } from 'redux';
import { accountReducer } from './accounts.js';
//compile all reducers together
export default combineReducers({
account: accountReducer
});
-14
View File
@@ -1,14 +0,0 @@
import { ACTION_NAME } from "../actions/actions.jsx";
const initialState = {};
export default function reducer(state = initialState, action) {
switch(action.type) {
case ACTION_NAME:
//DO NOTHING
return state;
default:
return state;
}
}