Technology uses a hybrid of Wi-Fi + cellular for texting and calling. This means low-cost plans with affordable, name-brand smartphones.
Friday, 28 December 2018
Wednesday, 26 December 2018
Monday, 24 December 2018
Bank react + redux + express + heroku reference
reference:
https://stackoverflow.com/questions/44689128/how-can-we-configure-the-header-of-ant-design-table-component
https://ant.design/
https://medium.com/@anneeb/redirecting-in-react-4de5e517354a
https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_buttons_animate1
https://chuanshuoge2.blogspot.com/2018/12/bank-express-ejs-mongodb.html
http://chuanshuoge2.blogspot.com/2018/12/deploy-react-and-express-to-heroku.html
https://chuanshuoge2.blogspot.com/2018/06/react-redux-schedule_24.html
https://chuanshuoge2.blogspot.com/2018/05/react-route-reactstrap-table-form-crud.html
http://chuanshuoge2.blogspot.com/2018/06/reactstrap-schedule.html
Sunday, 23 December 2018
Deploy React + Express to Heroku
You’ve got a React app, and an API server written in Express or something else. Now – how do you deploy them both to a server?
This article will cover how to keep them together. We’ll build the Express server to serve React’s static files in addition to providing an API, and then deploy it to Heroku. Heroku is easy to deploy to and free to get started with.
heroku logs
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Node.js app detected
remote:
remote: -----> Creating runtime environment
remote:
remote: NPM_CONFIG_LOGLEVEL=error
remote: NODE_ENV=production
remote: NODE_MODULES_CACHE=true
remote: NODE_VERBOSE=false
remote:
remote: -----> Installing binaries
remote: engines.node (package.json): unspecified
remote: engines.npm (package.json): unspecified (use default)
remote:
remote: Resolving node version 10.x...
remote: Downloading and installing node 10.14.2...
remote: Using default npm version: 6.4.1
remote:
remote: -----> Building dependencies
remote: Installing node modules (package.json + package-lock)
remote: added 51 packages from 39 contributors and audited 124 packages in 2.565s
remote: found 0 vulnerabilities
remote:
remote: Running heroku-postbuild
remote:
remote: > react-express-heroku@1.0.0 heroku-postbuild /tmp/build_8618b4496c28b4d8453989494ad86267
remote: > cd client && npm install && npm run build
remote:
remote: added 1725 packages from 667 contributors and audited 35703 packages in 36.53s
remote: found 0 vulnerabilities
remote:
remote:
remote: > client@0.1.0 build /tmp/build_8618b4496c28b4d8453989494ad86267/client
remote: > react-scripts build
remote:
remote: Creating an optimized production build...
remote: Compiled with warnings.
remote:
remote: ./src/index.js
remote: Line 5: 'serviceWorker' is defined but never used no-unused-vars
remote:
remote: Search for the keywords to learn more about each warning.
remote: To ignore, add // eslint-disable-next-line to the line before.
remote:
remote: File sizes after gzip:
remote:
remote: 34.7 KB build/static/js/1.4a34e45d.chunk.js
remote: 763 B build/static/js/runtime~main.229c360f.js
remote: 749 B build/static/js/main.12ccf6aa.chunk.js
remote: 613 B build/static/css/main.9a92f39c.chunk.css
remote:
remote: The project was built assuming it is hosted at the server root.
remote: You can control this with the homepage field in your package.json.
remote: For example, add this to build it for GitHub Pages:
remote:
remote: "homepage" : "http://myname.github.io/myapp",
remote:
remote: The build folder is ready to be deployed.
remote: You may serve it with a static server:
remote:
remote: npm install -g serve
remote: serve -s build
remote:
remote: Find out more about deployment here:
remote:
remote: http://bit.ly/CRA-deploy
remote:
remote:
remote: -----> Caching build
remote: - node_modules
remote:
remote: -----> Pruning devDependencies
remote: audited 124 packages in 0.853s
remote: found 0 vulnerabilities
remote:
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote: Procfile declares types -> (none)
remote: Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote: Done: 69.7M
remote: -----> Launching...
remote: Released v3
remote: https://chuanshuoge1-react-express.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
-----------------------------------------------
package.json
{"name": "react-express-heroku",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"heroku-postbuild": "cd client && npm install && npm run build"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.4",
"password-generator": "^2.2.0"
}
}
-----------------------------------------------------
client/package.json
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-scripts": "2.1.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
//for testing, ignored by heroku
"proxy": "http://localhost:5000"
}
-------------------------------------------
index.js
const express = require('express');
const path = require('path');
const generatePassword = require('password-generator');
const app = express();
// Serve static files from the React app
app.use(express.static(path.join(__dirname, 'client/build')));
// Put all API endpoints under '/api'
app.get('/api/passwords', (req, res) => {
const count = 5;
// Generate some passwords
const passwords = Array.from(Array(count).keys()).map(i =>
generatePassword(12, false)
)
// Return them as json
res.json(passwords);
console.log(`Sent ${count} passwords`);
});
// The "catchall" handler: for any request that doesn't
// match one above, send back React's index.html file.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
const port = process.env.PORT || 5000;
app.listen(port);
console.log(`Password generator listening on ${port}`);
----------------------------------------------
client/index.js
import React from 'react';import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
//serviceWorker.unregister();
---------------------------------------------
client/app.js
import React, { Component } from 'react';import './App.css';
class App extends Component {
// Initialize state
state = { passwords: [] }
// Fetch passwords after first mount
componentDidMount() {
this.getPasswords();
}
getPasswords = () => {
// Get the passwords and store them in state
fetch('/api/passwords')
.then(res => res.json())
.then(passwords => this.setState({ passwords }));
}
render() {
const { passwords } = this.state;
return (
<div className="App">
{/* Render the passwords if we have them */}
{passwords.length ? (
<div>
<h1>5 Passwords.</h1>
<ul className="passwords">
{/*
Generally it's bad to use "index" as a key.
It's ok for this example because there will always
be the same number of passwords, and they never
change positions in the array.
*/}
{passwords.map((password, index) =>
<li key={index}>
{password}
</li>
)}
</ul>
<button
className="more"
onClick={this.getPasswords}>
Get More
</button>
</div>
) : (
// Render a helpful message otherwise
<div>
<h1>No passwords :(</h1>
<button
className="more"
onClick={this.getPasswords}>
Try Again?
</button>
</div>
)}
</div>
);
}
}
export default App;
------------------------------------------
reference:
https://daveceddia.com/deploy-react-express-app-heroku/
Thursday, 20 December 2018
next.js
server.js
const express = require('express')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
server.get('/p/:id', (req, res) => {
const actualPage = '/post'
const queryParams = { id: req.params.id }
app.render(req,res,actualPage, queryParams)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})
-------------------------------------------
pages/index.js
import Layout from '../components/layout'
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'
const Index = (props) => (
<Layout>
<h1>Batman TV Shows</h1>
<ul>
{props.shows.map(({show}) => (
<li key={show.id}>
<Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
<a>{show.name}</a>
</Link>
</li>
))}
</ul>
</Layout>
)
Index.getInitialProps = async function() {
const res = await fetch('https://api.tvmaze.com/search/shows?q=batman')
const data = await res.json()
console.log(`Show data fetched. Count: ${data.length}`)
return {
shows: data
}
}
export default Index
----------------------------------------
pages/post.js
import {withRouter} from 'next/router'
import Layout from '../components/layout'
const Page = withRouter((props) => (
<Layout>
<h1>{props.show.name}</h1>
<p>{props.show.summary.replace(/<[/]?p>/g, '')}</p>
<img src={props.show.image.medium}/>
</Layout>
))
Page.getInitialProps = async function (context) {
console.log(context.query);
const { id } = context.query
const res = await fetch(`https://api.tvmaze.com/shows/${id}`)
const show = await res.json()
console.log(`Fetched show: ${show.name}`)
return { show }
}
export default Page
---------------------------------------
components/layout.js
import Header from './header'
const layoutStyle = {
margin: 20,
padding: 20,
border: '1px solid #DDD'
}
const Layout = (props) => (
<div style={layoutStyle}>
<Header />
{props.children}
</div>
)
export default Layout
-----------------------------------------
components/header
import Link from 'next/link'
const linkStyle = {
marginRight: 15
}
const Header = () => (
<div>
<Link href="/">
<a style={linkStyle}>Home</a>
</Link>
<Link href="/about">
<a style={linkStyle}>About</a>
</Link>
</div>
)
export default Header
------------------------------------------
reference:
https://nextjs.org/learn
deploy node express
https://zeit.co/examples/nodejs-express/
deploy next
https://zeit.co/examples/nextjs/
Wednesday, 19 December 2018
Tuesday, 18 December 2018
Bank express + ejs + mongodb, authentication jwt + bcrypt
project url: https://chuanshuoge1-bank.herokuapp.com/
welcome page
signup page register bob (password abc) sam (password 123)
check customers, bob, sam are registered, password in database is hashed
log in to bob's account
obtain a token from server after authentication, valid token is required for all transactions.
token is valid for 2min, sign in again to obtain another after expiration
within token valid time, deposit $100
with valid token, withdraw $5
wire transfer $50 to sam
login to sam's account, sam receives $50 from bob
check accounts on mongodb cloud
accout schema
package.json
{
"name": "express-mango-signin",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.18.0",
"bcryptjs": "^2.4.3",
"ejs": "^2.6.1",
"express": "^4.16.4",
"express-jwt": "^5.3.1",
"jsonwebtoken": "^8.4.0",
"mongoose": "^5.3.15",
"mongoose-timestamp": "^0.6.0"
},
"devDependencies": {
"nodemon": "^1.18.8"
}
}
--------------------------------------------------------------
index.js
const express = require('express');
const mongoose = require('mongoose');
const config = require('./config');
const bodyParser = require('body-parser');
const account = require('./models/account_model');
const app = express();
app.set('view engine', 'ejs');
//Body parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
//welcome page
app.get('/', (req, res) => res.render('pages/index'));
//get all users' login
app.get('/users', async (req, res, next)=>{
try{
const users = await account.find({});
let user_login =[];
users.forEach(user => {
user_login.push({name: user.name, password: user.password});
});
res.render('pages/users', {users: user_login})
}catch(err){
return next(err);
}
})
//signup page
app.get('/signup', (req, res) => res.render('pages/signup'));
//login page
app.get('/auth', (req, res) => res.render('pages/login'));
//delete a user
/%
app.delete('/users/:id', async(req, res, next)=>{
try{
const new_user = await
account.findOneAndRemove({_id: req.params.id});
res.sendStatus(204)
}catch(err){
return next(new Error('user not found'));
}
});%/
app.listen(config.PORT, ()=>{
mongoose.connect(config.MONGODB_URI, {useNewUrlParser: true});
});
const db = mongoose.connection;
db.on('error', (err)=>console.log(err));
db.once('open', ()=>{
require('./routes/signup')(app);
require('./routes/login')(app);
require('./routes/userAccount')(app);
console.log('server started on port ',config.PORT);
});
------------------------------------------------------
models/account_model.js
const mongoose = require('mongoose');
const timestamp= require('mongoose-timestamp');
const account_schema = new mongoose.Schema({
name:{
type: String,
required: true,
trim: true
},
balance:{
type: Number,
trim: true
},
password:{
type: String,
required: true
},
ledger:{
type: [String],
}
})
account_schema.plugin(timestamp);
const account = mongoose.model('account', account_schema);
module.exports = account;
----------------------------------------------------------
routes/signup.js
const bcrypt = require('bcryptjs');
const account = require('../models/account_model');
module.exports = app => {
//add a user
app.post('/signup', async(req, res, next)=>{
const {name, password} = req.body;
//check if user exist
const existing_user = await account.findOne({name: name});
//user exist return
if(existing_user){
res.render('pages/signup', {message: 'user already exists'});
return;
}
const user = new account({
name,
password,
balance: 0,
ledger: [],
});
bcrypt.genSalt(5, (err, salt) => {
bcrypt.hash(user.password, salt, async(err, hash) => {
//hash password
user.password = hash;
//save user
try{
const new_user = await user.save();
res.render('pages/signup', {message: 'user created'});
}catch(err){
//return next(err);
res.render('pages/signup', {message: err.toString()});
}
});
});
});
}
-----------------------------------------------------
routes/login.js
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const account = mongoose.model('account');
const jwt = require('jsonwebtoken');
const config = require('../config');
const authenticate = (name, password) => {
return new Promise(async (resolve, reject)=>{
try{
//get user by name
const user = await account.findOne({name: name});
//check password
bcrypt.compare(password, user.password, (err,isMatch)=>{
if(err){throw err;}
if(isMatch){
resolve(user);
}else{
//password didn't match
reject('user/password not match');
}
});
}catch(err){
//user name not found
reject('user not exist');
}
})
}
module.exports = app => {
app.post('/auth',async (req,res,next)=>{
const {name, password} = req.body;
try{
//authenticate user
const user = await authenticate(name, password);
//create jwt
const token = jwt.sign(user.toJSON(), config.JWT_SECRET, {
expiresIn: '2m'
});
const {iat, exp} = jwt.decode(token);
//token duration
const expire = exp - iat;
//respond with token
//res.send({iat, exp, token});
res.render('pages/login',
{
message: 'login successful',
user: {name: user.name, id: user._id}, //only pass id, name to front end
token,
expire,
projectURL: config.URL,
});
}catch(err){
//return next(err);
res.render('pages/login', {message: err.toString()});
}
});
}
------------------------------------------------------
routes/userAccount.js
const mongoose = require('mongoose');
const account = mongoose.model('account');
const config = require('../config');
const rjwt = require('express-jwt');
module.exports = app => {
//protected route, retrieve user account with token and id
app.post('/balance/:id', rjwt({secret: config.JWT_SECRET}), async (req,res,next)=>{
try{
//get user by id
const user = await account.findById(req.params.id);
res.send(user);
}catch(err){
return next(err);
}
});
//protected route, update user balance with token and id
app.put('/deposite/:id', rjwt({secret: config.JWT_SECRET}), async (req,res,next)=>{
const { amount} = req.body;
const {id} = req.params;
try{
//get user by id
const user_old = await account.findById(id);
//calculate balance after transaction
const new_balance = user_old.balance + parseFloat(amount);
//record transaction
const d = new Date();
const transaction = d.toISOString() + ' deposite $' + amount;
//only save last 10 transactions in ledger
const new_ledger = [...user_old.ledger].concat(transaction).splice(-10,10);
//update balance and ledger
await account.findOneAndUpdate({_id: id}, {"balance": new_balance.toFixed(2), "ledger": new_ledger});
//get user by id
const user_new = await account.findById(id);
res.send(user_new);
}catch(err){
return next(err);
}
});
//protected route, update user balance with token and id
app.put('/withdraw/:id', rjwt({secret: config.JWT_SECRET}), async (req,res,next)=>{
const { amount} = req.body;
const {id} = req.params;
try{
//get user by id
const user_old = await account.findById(id);
//calculate balance after transaction
const new_balance = user_old.balance - parseFloat(amount);
//new balance < 0 throw error, protect over withdraw on server side
if(new_balance < 0){throw 'not enough $ in account';}
//record transaction
const d = new Date();
const transaction = d.toISOString() + ' withdraw $' + amount;
//only save last 10 transactions in ledger
const new_ledger = [...user_old.ledger].concat(transaction).splice(-10,10);
//update balance and ledger
await account.findOneAndUpdate({_id: id}, {"balance": new_balance.toFixed(2), "ledger": new_ledger});
//get user by id
const user_new = await account.findById(id);
res.send(user_new);
}catch(err){
//return next(err);
res.send({error: err.toString()});
}
});
//protected route, update user balance with token and id
app.put('/wireTransfer/:id', rjwt({secret: config.JWT_SECRET}), async (req,res,next)=>{
const { amount, recipient} = req.body;
const {id} = req.params;
try{
//if recipient not found, throw error
const recipientAccount = await account.findOne({name: recipient});
if(recipientAccount===null){throw 'wire transfer recipient not found';}
//get user by id
const user_old = await account.findById(id);
// balances
const new_balance = user_old.balance - parseFloat(amount);
const new_balance_recipient = recipientAccount.balance + parseFloat(amount);
//new balance < 0 throw error, protect over withdraw on server side
if(new_balance < 0){throw 'not enough $ in account';}
//record transactions
const d = new Date();
const transactionSend = d.toISOString() + ' send $' + amount + ' to ' + recipient;
const transactionReceive = d.toISOString() + ' receive $' + amount + ' from ' + user_old.name;
//only save last 10 transactions in ledger
const new_ledgerSender = [...user_old.ledger].concat(transactionSend).splice(-10,10);
const new_ledgerReceiver = [...recipientAccount.ledger].concat(transactionReceive).splice(-10,10);
//update accounts
await account.findOneAndUpdate({_id: id}, {"balance": new_balance.toFixed(2), "ledger": new_ledgerSender});
await account.findOneAndUpdate({_id: recipientAccount._id}, {"balance": new_balance_recipient.toFixed(2), "ledger": new_ledgerReceiver});
//get user by id
const user_new = await account.findById(id);
res.send(user_new);
}catch(err){
//return next(err);
res.send({error: err.toString()});
}
});
//display user account
app.post('/userAccount/:id', (req,res,next)=>{
const {userId, name, balance, created, updated, message, ledger} = req.body;
res.render('pages/userAccount', {
id: userId,
name,
balance,
created,
updated,
message,
ledger: ledger.split(','),
});
});
}
-----------------------------------------------------------------------------------
partials/localstorage.ejs
<!-- after authentication, store user, token in localstorage -->
<% if (typeof user != 'undefined'){ %>
<div id='user' style="display: none;"><%= user.name %></div>
<div id='id' style="display: none;"><%= user.id %></div>
<% } %>
<% if (typeof token != 'undefined'){ %>
<div id='token' style="display: none;"><%= token %></div>
<% } %>
<% if (typeof expire != 'undefined'){ %>
<div id='expire' style="display: none;"><%= expire %></div>
<% } %>
<% if (typeof projectURL != 'undefined'){ %>
<div id='projectURL' style="display: none;"><%= projectURL %></div>
<% } %>
<script>
var user = document.getElementById("user").innerHTML.trim();
var id = document.getElementById("id").innerHTML.trim();
var token = document.getElementById("token").innerHTML.trim();
var expire = document.getElementById("expire").innerHTML.trim();
var projectURL = document.getElementById("projectURL").innerHTML.trim();
if(user !== null){
localStorage.setItem("user", user);
}
if(id !== null){
localStorage.setItem("id", id);
}
if(token !== null){
localStorage.setItem("token", token);
var d = new Date();
localStorage.setItem("issued", d.getTime());
}
if(expire !== null){
localStorage.setItem("expire", expire);
}
if(projectURL !== null){
localStorage.setItem("projectURL", projectURL);
}
</script>
---------------------------------------------------------------------
partials/redirecToAccount.ejs
<!-- hidden form used for redirect to user account once authenticated -->
<form style="display: none;" class="container" method="POST" id='redirectForm'>
<input type="hidden" id='userId' name='userId'>
<input type="hidden" id='userName' name='name'>
<input type="hidden" id='userBalance' name='balance'>
<input type="hidden" id='userCreated' name='created'>
<input type="hidden" id='userUpdated' name='updated'>
<input type="hidden" id='message' name='message'>
<input type="hidden" id='ledger' name='ledger'>
<button id='redirectButton' type="submit"></button>
</form>
<script>
//retrieve user account with token and id from database, then redirect to account page
function rediectToAccount(method, route, amount, recipient){
var projectURL = localStorage.getItem("projectURL");
var id = localStorage.getItem("id");
var token = localStorage.getItem("token");
var postData = {"amount": amount, "recipient": recipient};
var jwt = 'Bearer ' + token;
var headers = {
'Content-Type': 'application/json',
'Authorization': jwt,
"Access-Control-Allow-Origin": "*",
}
var actionURL = projectURL + route + '/' + id;
axios({
method: method,
url: actionURL,
data: postData,
headers: headers,
})
.then((res)=>{
const {_id, name, balance, createdAt, updatedAt, ledger, error} = res.data;
//if server didn't reply user info, throw error meessage
if(typeof _id === 'undefined'){throw error;}
var userId = document.getElementById('userId');
var userName = document.getElementById('userName');
var userBalance = document.getElementById('userBalance');
var userCreated = document.getElementById('userCreated');
var userUpdated = document.getElementById('userUpdated');
var successMessage = document.getElementById('message');
var userLedger = document.getElementById('ledger');
userId.value = _id;
userName.value = name;
userCreated.value = createdAt;
userUpdated.value = updatedAt;
successMessage.value = route.slice(1) + ' processed';
userLedger.value = ledger;
if(typeof balance === 'undefined'){
userBalance.value = 0;
}else{
userBalance.value = balance;
}
//set redirect form submit route
document.getElementById('redirectForm').action = '/userAccount/'+_id;
//submit form, redirect to account page
document.getElementById('redirectButton').click();
})
.catch((err) => {
alert(err);
//if token is invalid or over withdraw, logout, redirect to welcome page
logout();
window.location.href = projectURL;
})
}
//remove user, token from local storage when logged out
function logout(){
localStorage.removeItem("user");
localStorage.removeItem("token");
localStorage.removeItem("expire");
}
</script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
-------------------------------------------------------------------
<form style="display: none;" class="container" method="POST" id='redirectForm'>
<input type="hidden" id='userId' name='userId'>
<input type="hidden" id='userName' name='name'>
<input type="hidden" id='userBalance' name='balance'>
<input type="hidden" id='userCreated' name='created'>
<input type="hidden" id='userUpdated' name='updated'>
<input type="hidden" id='message' name='message'>
<input type="hidden" id='ledger' name='ledger'>
<button id='redirectButton' type="submit"></button>
</form>
<script>
//retrieve user account with token and id from database, then redirect to account page
function rediectToAccount(method, route, amount, recipient){
var projectURL = localStorage.getItem("projectURL");
var id = localStorage.getItem("id");
var token = localStorage.getItem("token");
var postData = {"amount": amount, "recipient": recipient};
var jwt = 'Bearer ' + token;
var headers = {
'Content-Type': 'application/json',
'Authorization': jwt,
"Access-Control-Allow-Origin": "*",
}
var actionURL = projectURL + route + '/' + id;
axios({
method: method,
url: actionURL,
data: postData,
headers: headers,
})
.then((res)=>{
const {_id, name, balance, createdAt, updatedAt, ledger, error} = res.data;
//if server didn't reply user info, throw error meessage
if(typeof _id === 'undefined'){throw error;}
var userId = document.getElementById('userId');
var userName = document.getElementById('userName');
var userBalance = document.getElementById('userBalance');
var userCreated = document.getElementById('userCreated');
var userUpdated = document.getElementById('userUpdated');
var successMessage = document.getElementById('message');
var userLedger = document.getElementById('ledger');
userId.value = _id;
userName.value = name;
userCreated.value = createdAt;
userUpdated.value = updatedAt;
successMessage.value = route.slice(1) + ' processed';
userLedger.value = ledger;
if(typeof balance === 'undefined'){
userBalance.value = 0;
}else{
userBalance.value = balance;
}
//set redirect form submit route
document.getElementById('redirectForm').action = '/userAccount/'+_id;
//submit form, redirect to account page
document.getElementById('redirectButton').click();
})
.catch((err) => {
alert(err);
//if token is invalid or over withdraw, logout, redirect to welcome page
logout();
window.location.href = projectURL;
})
}
//remove user, token from local storage when logged out
function logout(){
localStorage.removeItem("user");
localStorage.removeItem("token");
localStorage.removeItem("expire");
}
</script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
-------------------------------------------------------------------
partials/headers.ejs
<!-- retrieve user account with token from database, then redirect to account page -->
<% include ./redirectToAccount%>
<nav class="navbar sticky-top navbar-expand-sm navbar-light" style="background-color: #e3f2fd;">
<a class="navbar-brand" href="/">Bank</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/users">Customers</a>
</li>
</ul>
<ul id='menuRight' class="navbar-nav my-2 my-lg-0">
</ul>
</div>
</nav>
<script>
var user = localStorage.getItem("user");
if(user !== null){
//redirectToAccount() included in redirectToAccount.ejs
document.getElementById('menuRight').innerHTML =
"<li><a class='nav-link' onclick='toAccount()'>" + user + "</a></li>" +
"<li><a class='nav-link' href='/' onclick='logout()'>Logout</a></li>";
}else{
document.getElementById('menuRight').innerHTML =
"<li><a class='nav-link' href='/signup'>Signup</a></li>" +
"<li><a class='nav-link' href='/auth'>Login</a></li>";
}
function toAccount(){
rediectToAccount('post', '/balance');
}
</script>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
--------------------------------------------------------
views/userAccount.ejs
<!-- views/pages/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body>
<header>
<% include ../partials/header %>
</header>
<main>
<div class="jumbotron">
<button class="btn btn-warning btn-sm" data-toggle="collapse" data-target="#token">Token</button>
Valid for <span class="text-danger" id='timer'></span>S
<button class="btn btn-danger btn-sm" onclick="revoke()">Revoke</button><br><br>
<span id="token" class="collapse"></span>
<span>Customer ID: <%= id %></span><br>
<span>Customer Since: <%= created %></span><br>
<span>Last Transaction: <%= updated %></span><br><br>
<span>Balance: $<span id='balance'><%= balance %></span></span><br>
<% if (typeof message != 'undefined'){ %>
<span class="text-info">Status: <%= message %></span><br>
<% } %>
<table class="table">
<thead>
<tr>
<th scope="col">Action</th>
<th scope="col">Parameter</th>
</tr>
<tbody>
<tr>
<td> <button class="btn btn-success btn-sm" onclick="deposite()">Deposite</button></td>
<td>$<input type="number" id="depositeAmount" onchange="abs(this)" value=0></td>
</tr>
<tr>
<td><button class="btn btn-success btn-sm" onclick="withdraw()">Withdraw</button> </td>
<td>$<input type="number" id="withdrawAmount" onchange="abs(this)" value=0></td>
</tr>
<tr>
<td><button class="btn btn-success btn-sm" onclick="wireTransfer()">Wire Transfer</button> </td>
<td>
<div class="row">
<div class="col">
$<input type="number" id="transferAmount" onchange="abs(this)" value=0 >
</div>
<div class="col">
To <input type="text" id="recipient" placeholder="recipient name">
</div>
</div>
</td>
</tr>
</tbody>
</thead>
</table>
<h4>Transaction History</h4>
<% ledger.forEach(function(transaction) { %>
<span><%= transaction %></span><br>
<% }); %>
</div>
<script>
document.getElementById('token').innerHTML = localStorage.getItem('token');
//token expire timer
var duration = localStorage.getItem('expire');
var issued = localStorage.getItem('issued');
var timer = document.getElementById('timer');
var counter = setInterval(()=>{
//second elapsed since token issued
var d = new Date();
var elapsed = parseInt((d.getTime() - parseInt(issued))/1000);
var countdown = parseInt(duration) - elapsed;
timer.innerHTML = countdown;
//stop timer when token expire
if(countdown <= 0){
clearInterval(counter);
}
},1000)
//revoke token when button clicked
function revoke(){
localStorage.removeItem("token");
clearInterval(counter);
timer.innerHTML = 0;
document.getElementById('token').innerHTML = 'revoked';
}
//get absolute 2 decimal precision of amount
function abs(obj){
obj.value = Math.abs(obj.value).toFixed(2);
}
//redirect to deposite route in userAccount.js
function deposite(){
var amount = document.getElementById('depositeAmount').value;
rediectToAccount('put', '/deposite', amount);
}
//redirect to withdraw route in userAccount.js
function withdraw(){
var amount = document.getElementById('withdrawAmount').value;
//check if account has enough $
if(!checkWithdraw(amount)){return}
rediectToAccount('put', '/withdraw', amount);
}
//check if $ is enough in account for withdraw
function checkWithdraw(withdrawAmount){
var balance = document.getElementById('balance').innerHTML;
//protect over withdraw from client side
if(withdrawAmount > parseFloat(balance)){
alert('short of $ in account');
return false;
}
return true;
}
//redirect to wire transfer route in userAccount.js
function wireTransfer(){
var recipient = document.getElementById('recipient').value;
var amount = document.getElementById('transferAmount').value;
//if recipient is not entered, return
if(recipient===''){alert('recipient empty'); return}
//check if account has enough $
if(!checkWithdraw(amount)){return}
rediectToAccount('put', '/wireTransfer', amount, recipient);
}
</script>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>
---------------------------------------------------------------------------
pages/login.ejs
<!-- views/pages/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body>
<header>
<!-- authentication passed, store user, token in local storage -->
<% include ../partials/localStorage %>
<% include ../partials/header %>
</header>
<main>
<form class="container" method="POST" action="/auth">
<h4>Login</h4>
<div class="form-group">
<label for="exampleInputName">User Name</label>
<input type="text" class="form-control" name="name" id="exampleInputName" placeholder="User Name" oninput="inputChange()">
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" name="password" id="exampleInputPassword1" placeholder="Password" oninput="inputChange()">
</div>
<button type="submit" id='submit_button' disabled=true class="btn btn-primary">Login</button>
<% if (typeof message != 'undefined'){ %>
<p class="text-info"><%= message %>
<br><span id='timer'></span></p>
<% } %>
</form>
</main>
<footer>
<% include ../partials/footer %>
</footer>
<script>
function inputChange() {
var name = document.getElementById('exampleInputName').value;
var password = document.getElementById('exampleInputPassword1').value;
var submit_button = document.getElementById('submit_button');
if(name !== '' && password !== ''){
submit_button.disabled = false;
}else{
submit_button.disabled = true;
}
}
//redirect to user account if authentication is successful
var user = localStorage.getItem("user");
if(user !== null){
//redirect timer
var seconds = 0;
var timer = document.getElementById('timer');
timer.innerHTML = 'redirecting to my account ';
var counter = setInterval(()=>{
timer.innerHTML += '$';
seconds++;
if(seconds === 10){
clearInterval(counter);
//wait for 1 seconds before redirect to account
//function included in redirectToAccount.ejs in header.ejs
rediectToAccount('post', '/balance');
}
},100)
}
</script>
</body>
</html>
-------------------------------------------------------------
config.js
module.exports={
ENV:process.env.NODE_ENV || 'development',
PORT:process.env.PORT || 3000,
URL: process.env.BASE_URL || 'http://localhost:3000',
MONGODB_URI: process.env.MONGODB_URI ||
'mongodb://xxxxxx@ds131954.mlab.com:31954/bank',
JWT_SECRET: process.env.JWT_SECRET || 'abc'
}
---------------------------------------------
pages/user.ejs
<!-- views/pages/index.ejs -->
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body>
<header>
<% include ../partials/header %>
</header>
<main>
<table class="table">
<thead class="thead-light">
<tr>
<th scope="col">Name</th>
<th scope="col">Hashed Password</th>
</tr>
</thead>
<tbody>
<% users.forEach(function(user) { %>
<tr>
<td> <%= user.name %> </td>
<td> <%= user.password %></td>
</tr>
<% }); %>
</tbody>
</table>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>
-----------------------------------------------------
partials/head.ejs
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Authentication</title>
--------------------------------------------
partials/footer.ejs
<p class="text-center text-muted">© chuanshuoge 2018</p>
-------------------------------------------
.gitignore
node_modules
-----------------------------------------
reference:
Subscribe to:
Posts (Atom)