Apostila React Redux
Apostila React Redux
Apostila React Redux
Leonardo Leitão
https://www.cod3r.com.br
1
1. Introdução
1.1. Visão Geral do Curso
2
2. Webpack
2.1. Visão Geral e Instalação
Abra o terminal e dentro da pasta Desktop execute o seguinte comando:
npm init -y
node_modules
*.log
3
Listagem 5 - Criar arquivo webpack.config.js
exercicios_webpack/webpack.config.js
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Exercícios de Webpack</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
4
Listagem 7 - Alterar arquivo index.js
exercicios_webpack/ex/index.js (na linha 1)
console.log('Webpack')
por:
function info(text) {
console.log(`INFO: ${text}`)
}
module.exports = { info }
5
Listagem 11 - Sobrescrever arquivo index.js
exercicios_webpack/ex/index.js
console.log('Sou carregado?')
Para abrir o terminal no Visual Studio Code use o atalho ctrl + '
./node_modules/.bin/webpack
require('./duvidacruel')
./node_modules/.bin/webpack
6
2.7. Usando EcmaScript 2015 sem Babel
class Pessoa {
constructor(nome) {
this.nome = nome
}
toString() {
return `Pessoa: ${this.nome}`
}
}
Para abrir o terminal no Visual studio Code use o atalho ctrl + '
./node_modules/.bin/webpack
7
Listagem 19 - Alterar arquivo pessoa.js
exercicios_webpack/ex/pessoa.js
toString() {
return `Pessoa: ${this.nome}`
}
}
8
Listagem 21 - Alterar arquivo webpack.config.js
exercicios_webpack/webpack.config.js (aprox. linha 12)
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
module: {
loaders: [{
test: /.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015']
}
}]
}
}
9
Listagem 24 - Sobrescrever arquivo index.js
exercicios_webpack/ex/index.js
const produto = {
nome: 'Caneta Bic Preta',
preco: 1.90,
desconto: 0.05
}
function clone(objeto) {
return { ...objeto }
}
console.log(produto, novoProduto)
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
module: {
loaders: [{
test: /.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015'],
plugins: ['transform-object-rest-spread']
}
}]
}
}
10
Abra o terminal e dentro da pasta do projeto exercicios_webpack execute o
seguinte comando:
import 'react'
console.log('Funcionou!')
11
Listagem 31 - Alterar arquivo webpack.config.js
exercicios_webpack/webpack.config.js (aprox. linha 19)
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
module: {
loaders: [{
test: /.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['transform-object-rest-spread']
}
}]
}
}
12
2.11. Adicionando o Loader para CSS
body{
background-color: #253B6E;
}
import './estilo.css'
import 'react'
console.log('Funcionou!')
13
Listagem 36 - Alterar arquivo webpack.config.js
exercicios_webpack/webpack.config.js (aprox. linha 2, 14 e 26)
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
plugins: [
new ExtractTextPlugin('app.css')
],
module: {
loaders: [{
test: /.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['transform-object-rest-spread']
}
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
}]
}
}
14
Listagem 37 - Alterar arquivo index.html
exercicios_webpack/public/index.html (aprox. linha 6)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Exercícios de Webpack</title>
<link rel='stylesheet' href='app.css'>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
Para abrir o terminal no Visual studio Code use o atalho ctrl + '
./node_modules/.bin/webpack
15
3. React
3.1. Configurando o Projeto
Abra o terminal e dentro da pasta Desktop execute o seguinte comando:
npm init -y
node_modules
*.log
16
Listagem 45 - Criar arquivo webpack.config.js
exercicios_react/webpack.config.js
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}]
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Exercícios de React</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
17
3.4. Olá React
module.exports = {
entry: './ex/index.jsx',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}]
}
}
18
Listagem 49 - Alterar arquivo package.json
exercicios_react/package.json (aprox. linha 7)
por:
function() {
return <h1>Primeiro Componente!</h1>
}
19
Listagem 52 - Alterar arquivo index.jsx
exercicios_react/ex/index.jsx (aprox. linha 3 e 5)
20
Listagem 55 - Alterar arquivo webpack.config.js
exercicios_react/webpack.config.js (aprox. linha 13)
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
resolve: {
extensions: ['', '.js', '.jsx']
},
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}]
}
}
21
Listagem 57 - Alterar arquivo component.jsx
exercicios_react/ex/component.jsx (aprox. linha 3)
22
3.8. Exportando mais de um Componente
ReactDOM.render(
<div>
<Primeiro />
<Segundo />
</div>
, document.getElementById('app'))
23
Listagem 64 - Alterar arquivo component.jsx
exercicios_react/ex/component.jsx (aprox. linha 3)
ReactDOM.render(
<div>
<Primeiro />
<Segundo />
</div>
, document.getElementById('app'))
24
Listagem 67 - Criar arquivo silvaFamily.jsx
exercicios_react/ex/silvaFamily.jsx
ReactDOM.render(
<SilvaFamily />
, document.getElementById('app'))
25
Listagem 70 - Alterar arquivo index.jsx
exercicios_react/ex/index.jsx (aprox. linha 3 e 7)
ReactDOM.render(
<Family lastName='Silva'>
<Member name='Guilherme' />
</Family>
, document.getElementById('app'))
26
Listagem 73 - Alterar arquivo webpack.config.js
exercicios_react/webpack.config.js (aprox. linha 22)
module.exports = {
entry: './ex/index.js',
output: {
path: __dirname + '/public',
filename: './bundle.js'
},
devServer: {
port: 8080,
contentBase: './public'
},
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['transform-object-rest-spread']
}
}]
}
}
27
Listagem 75 - Alterar arquivo index.jsx
exercicios_react/ex/index.jsx (aprox. linha 9)
ReactDOM.render(
<Family lastName='Silva'>
<Member name='Guilherme' />
<Member name='Rafael' />
<Member name='Julia' />
</Family>
, document.getElementById('app'))
28
Listagem 77 - Criar arquivo reactUtils.js
exercicios_react/utils/reactUtils.js
export { childrenWithProps }
29
Listagem 80 - Alterar arquivo index.jsx
exercicios_react/ex/index.jsx (aprox. linha 3 e 6)
ReactDOM.render(
<ClassComponent value='Componente Classe' />
, document.getElementById('app'))
ReactDOM.render(
<ClassComponent value={10} />
, document.getElementById('app'))
30
Listagem 82 - Alterar arquivo classComponent.jsx
exercicios_react/ex/classComponent.jsx (aprox. linha 4)
sum(delta){
this.setState({ value: this.state.value + delta })
}
render() {
return(
<div>
<h1>{this.props.label}</h1>
<h2>{this.state.value}</h2>
<button => this.sum(-1)}>Dec</button>
<button => this.sum(1)}>Inc</button>
</div>
)
}
}
ReactDOM.render(
<ClassComponent label='Contador' initialValue={10} />
, document.getElementById('app'))
31
Listagem 84 - Criar arquivo field.jsx
exercicios_react/ex/field.jsx
handleChange(event) {
this.setState({ value: event.target.value })
}
render(){
return (
<div>
<label>{this.state.value}</label><br />
<input value={this.state.value} />
</div>
)
}
}
ReactDOM.render(
<Field initialValue='Teste' />
, document.getElementById('app'))
32
Listagem 86 - Alterar arquivo field.jsx
exercicios_react/ex/field.jsx (aprox. linha 8)
handleChange(event) {
this.setState({ value: event.target.value })
}
render(){
return (
<div>
<label>{this.state.value}</label><br />
<input value={this.state.value} />
</div>
)
}
}
33
4. TodoApp (Backend)
4.1. Visão Geral
npm init -y
code .
34
Listagem 92 - Alterar arquivo package.json
todo-app/backend/package.json (aprox. linha 5 e 7)
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "src/loader.js",
"scripts": {
"dev": "nodemon",
"production": "pm2 start src/loader.js --name todo-app"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"body-parser": "^1.15.2",
"express": "^4.14.0",
"mongoose": "^4.7.0",
"node-restful": "^0.2.5",
"nodemon": "^1.11.0",
"pm2": "^2.1.5"
}
}
node_modules
*.log
require('./config/server')
35
Criar uma pasta chamada config em src/config
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
require('./config/server')
require('./config/database')
36
4.5. ODM e Criação da API REST
Criar uma pasta chamada api e dentro dela criar uma pasta chamada todo em
src/api/todo
module.exports = Todo
37
Listagem 101 - Criar arquivo routes.js
todo-app/backend/src/config/routes.js
module.exports = function(server) {
// API Routes
const router = express.Router()
server.use('/api', router)
// TODO Routes
const todoService = require('../api/todo/todoService')
todoService.register(router, '/todos')
}
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
module.exports = server
38
4.7. Testando a API (Postman)
Acesse o site do Postman: https://www.getpostman.com para instalar no
computador ou usar a extensão Chrome para testar a Api.
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
module.exports = server
39
Listagem 106 - Executar a aplicação com PM2
todo-app/backend
./node_modules/.bin/pm2 monit
40
5. TodoApp (Frontend)
5.1. Configuração e Instalação
Abra o terminal e dentro da pasta todo-app execute o seguinte comando:
npm init -y
41
Listagem 113 - Instalando as dependências do Bootstrap e Font Awesome
code .
node_modules
*.log
42
Listagem 117 - Criar arquivo webpack.config.js
todo-app/frontend/webpack.config.js
module.exports = {
entry: './src/index.jsx',
output: {
path: __dirname + '/public',
filename: './app.js'
},
devServer: {
port: 8080,
contentBase: './public',
},
resolve: {
extensions: ['', '.js', '.jsx'],
alias: {
modules: __dirname + '/node_modules'
}
},
plugins: [
new ExtractTextPlugin('app.css')
],
module: {
loaders: [{
test: /.js[x]?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['transform-object-rest-spread']
}
}, {
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
}, {
test: /\.woff|.woff2|.ttf|.eot|.svg*.*$/,
loader: 'file'
}]
}
}
43
Listagem 118 - Alterar arquivo package.json
todo-app/frontend/package.json (aprox. linha 7 e 8)
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --progress --colors --inline --hot",
"production": "webpack --progress -p"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"axios": "^0.15.3",
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-plugin-react-html-attrs": "^2.0.0",
"babel-plugin-transform-object-rest-spread": "^6.22.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.22.0",
"bootstrap": "^3.3.7",
"css-loader": "^0.26.1",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"font-awesome": "^4.7.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^5.0.2",
"react-router": "^3.0.2",
"redux": "^3.6.0",
"redux-multi": "^0.1.12",
"redux-promise": "^0.5.3",
"redux-thunk": "^2.2.0",
"style-loader": "^0.13.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
}
}
44
Listagem 119 - Criar arquivo index.html
todo-app/frontend/public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>Todo App</title>
<link rel='stylesheet' href='app.css'>
</head>
<body>
<div id="app" class='container'></div>
<script src='app.js'></script>
</body>
</html>
import 'modules/bootstrap/dist/css/bootstrap.min.css'
import 'modules/font-awesome/css/font-awesome.min.css'
45
Listagem 121 - Criar arquivo index.jsx
todo-app/frontend/src/index.jsx
46
Listagem 124 - Alterar arquivo app.jsx
todo-app/frontend/src/main/app.jsx (aprox. linha 5 e 9)
import 'modules/bootstrap/dist/css/bootstrap.min.css'
import 'modules/font-awesome/css/font-awesome.min.css'
47
Listagem 126 - Alterar arquivo app.jsx
todo-app/frontend/src/main/app.jsx (aprox. linha 6 e 11)
import 'modules/bootstrap/dist/css/bootstrap.min.css'
import 'modules/font-awesome/css/font-awesome.min.css'
48
Listagem 128 - Alterar arquivo app.jsx
todo-app/frontend/src/main/app.jsx (aprox. linha 7 e 11)
import 'modules/bootstrap/dist/css/bootstrap.min.css'
import 'modules/font-awesome/css/font-awesome.min.css'
49
Listagem 130 - Alterar arquivo app.jsx
todo-app/frontend/src/main/app.jsx (aprox. linha 6 e 11)
import 'modules/bootstrap/dist/css/bootstrap.min.css'
import 'modules/font-awesome/css/font-awesome.min.css'
50
Listagem 132 - Alterar arquivo todo.jsx
todo-app/frontend/src/todo/todo.jsx (aprox. linha 2 e 8)
<h2>Nossa História</h2>
<p>Lorem ipsum dolor sit amet...</p>
<h2>Missão e Visão</h2>
<p>Lorem ipsum dolor sit amet...</p>
<h2>Imprensa</h2>
<p>Lorem ipsum dolor sit amet...</p>
</div>
)
51
Listagem 134 - Criar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx
52
5.10. Estratégia de Implementação
53
Listagem 138 - Criar arquivo grid.jsx
todo-app/frontend/src/template/grid.jsx
return classes
}
render() {
const gridClasses = this.toCssClasses(this.props.cols || 12)
return (
<div className={gridClasses}>
{this.props.children}
</div>
)
}
}
54
Listagem 139 - Alterar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx (aprox. linha 2, 6, 9, 10 e 14)
55
Listagem 141 - Alterar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx (aprox. linha 3 e 12)
56
Listagem 143 - Sobrescrever arquivo iconButton.jsx
todo-app/frontend/src/template/iconButton.jsx
57
Listagem 145 - Alterar arquivo todo.jsx
todo-app/frontend/src/todo/todo.jsx (aprox. linha 7 e 20)
handleAdd() {
console.log(this)
}
render() {
return (
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
<TodoForm handleAdd={this.handleAdd} />
<TodoList />
</div>
)
}
}
58
Listagem 146 - Alterar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx (aprox. linha 10 e 11)
59
Listagem 147 - Alterar arquivo todo.jsx
todo-app/frontend/src/todo/todo.jsx (aprox. linha 9, 11, 15, 20, 23 e 27)
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
}
handleChange(e) {
this.setState({...this.state, description: e.target.value })
}
handleAdd() {
console.log(this.state.description)
}
render() {
return (
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
<TodoForm description={this.state.description}
handleChange={this.handleChange}
handleAdd={this.handleAdd} />
<TodoList />
</div>
)
}
}
60
Listagem 148 - Alterar arquivo todo.jsx
todo-app/frontend/src/todo/todo.jsx (aprox. linha 2, 8 e 24)
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
}
handleChange(e) {
this.setState({...this.state, description: e.target.value })
}
handleAdd() {
const description = this.state.description
axios.post(URL, { description })
.then(resp => console.log('Funcionou!'))
}
render() {
return (
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
<TodoForm description={this.state.description}
handleChange={this.handleChange}
handleAdd={this.handleAdd} />
<TodoList />
</div>
)
}
}
61
Listagem 149 - Alterar arquivo todo.jsx
todo-app/frontend/src/todo/todo.jsx (aprox. linha 18, 20, 23, 34, 37 e 49)
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
this.handleRemove = this.handleRemove.bind(this)
this.refresh()
}
refresh() {
axios.get(`${URL}?sort=-createAt`)
.then(resp => this.setState({...this.state, description: '', list:
resp.data}))
}
handleChange(e) {
this.setState({...this.state, description: e.target.value })
}
handleAdd() {
const description = this.state.description
axios.post(URL, { description })
.then(resp => this.refresh())
}
handleRemove(todo) {
axios.delete(`${URL}/${todo._id}`)
.then(resp => this.refresh())
}
render() {
return (
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
62
<TodoForm description={this.state.description}
handleChange={this.handleChange}
handleAdd={this.handleAdd} />
<TodoList list={this.state.list}
handleRemove={this.handleRemove} />
</div>
)
}
}
return (
<table className='table'>
<thead>
<tr>
<th>Descrição</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{renderRows()}
</tbody>
</table>
)
}
63
5.18. Marcar como Concluído/Pendente
return (
<table className='table'>
<thead>
<tr>
<th>Descrição</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{renderRows()}
</tbody>
</table>
)
}
64
todo-app/frontend/src/todo/todo.jsx (aprox. linha 18, 43, 48 e 63)
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
this.handleMarkAsDone = this.handleMarkAsDone.bind(this)
this.handleMarkAsPending = this.handleMarkAsPending.bind(this)
this.handleRemove = this.handleRemove.bind(this)
this.refresh()
}
refresh() {
axios.get(`${URL}?sort=-createAt`)
.then(resp => this.setState({...this.state, description: '', list:
resp.data}))
}
handleChange(e) {
this.setState({...this.state, description: e.target.value })
}
handleAdd() {
const description = this.state.description
axios.post(URL, { description })
.then(resp => this.refresh())
}
handleRemove(todo) {
axios.delete(`${URL}/${todo._id}`)
.then(resp => this.refresh())
}
handleMarkAsDone(todo) {
axios.put(`${URL}/${todo._id}`, { ...todo, done: true })
.then(resp => this.refresh())
}
65
handleMarkAsPending(todo) {
axios.put(`${URL}/${todo._id}`, { ...todo, done: false })
.then(resp => this.refresh())
}
render() {
return (
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
<TodoForm description={this.state.description}
handleChange={this.handleChange}
handleAdd={this.handleAdd} />
<TodoList list={this.state.list}
handleMarkAsDone={this.handleMarkAsDone}
handleMarkAsPending={this.handleMarkAsPending}
handleRemove={this.handleRemove} />
</div>
)
}
}
.btn {
margin-right: 5px;
}
.markedAsDone {
text-decoration: line-through;
color: #777;
}
66
Listagem 154 - Alterar arquivo app.jsx
todo-app/frontend/src/main/app.jsx (aprox. linha 3)
import 'modules/bootstrap/dist/css/bootstrap.min.css'
import 'modules/font-awesome/css/font-awesome.min.css'
import '../template/custom.css'
67
Listagem 156 - Alterar arquivo todo.jsx
todo-app/frontend/src/todo/todo.jsx (aprox. linha 17, 26, 32, 48, 53, 58 e 69)
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
this.handleSearch = this.handleSearch.bind(this)
this.handleMarkAsDone = this.handleMarkAsDone.bind(this)
this.handleMarkAsPending = this.handleMarkAsPending.bind(this)
this.handleRemove = this.handleRemove.bind(this)
this.refresh()
}
refresh(description = '') {
const search = description ? `&description__regex=/${description}/` : ''
axios.get(`${URL}?sort=-createAt${search}`)
.then(resp => this.setState({...this.state, description, list:
resp.data}))
}
handleSearch() {
this.refresh(this.state.description)
}
handleChange(e) {
this.setState({...this.state, description: e.target.value })
}
handleAdd() {
const description = this.state.description
axios.post(URL, { description })
.then(resp => this.refresh())
}
handleRemove(todo) {
68
axios.delete(`${URL}/${todo._id}`)
.then(resp => this.refresh(this.state.description))
}
handleMarkAsDone(todo) {
axios.put(`${URL}/${todo._id}`, { ...todo, done: true })
.then(resp => this.refresh(this.state.description))
}
handleMarkAsPending(todo) {
axios.put(`${URL}/${todo._id}`, { ...todo, done: false })
.then(resp => this.refresh(this.state.description))
}
render() {
return (
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
<TodoForm description={this.state.description}
handleChange={this.handleChange}
handleAdd={this.handleAdd}
handleSearch={this.handleSearch} />
<TodoList list={this.state.list}
handleMarkAsDone={this.handleMarkAsDone}
handleMarkAsPending={this.handleMarkAsPending}
handleRemove={this.handleRemove} />
</div>
)
}
}
69
Listagem 157 - Alterar arquivo custom.css
todo-app/frontend/src/template/custom.css (aprox. linha 10)
.btn {
margin-right: 5px;
}
.markedAsDone {
text-decoration: line-through;
color: #777;
}
.tableActions {
width: 105px;
}
.todoForm {
padding-bottom: 60px;
}
70
Listagem 158 - Alterar arquivo todoList.jsx
todo-app/frontend/src/todo/todoList.jsx (aprox. linha 28)
return (
<table className='table'>
<thead>
<tr>
<th>Descrição</th>
<th className='tableActions'>Ações</th>
</tr>
</thead>
<tbody>
{renderRows()}
</tbody>
</table>
)
}
71
Listagem 159 - Alterar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx (aprox. linha 18)
this.handleChange = this.handleChange.bind(this)
this.handleAdd = this.handleAdd.bind(this)
this.handleSearch = this.handleSearch.bind(this)
72
this.handleClear = this.handleClear.bind(this)
this.handleMarkAsDone = this.handleMarkAsDone.bind(this)
this.handleMarkAsPending = this.handleMarkAsPending.bind(this)
this.handleRemove = this.handleRemove.bind(this)
this.refresh()
}
refresh(description = '') {
const search = description ? `&description__regex=/${description}/` : ''
axios.get(`${URL}?sort=-createAt${search}`)
.then(resp => this.setState({...this.state, description, list:
resp.data}))
}
handleSearch() {
this.refresh(this.state.description)
}
handleChange(e) {
this.setState({...this.state, description: e.target.value })
}
handleAdd() {
const description = this.state.description
axios.post(URL, { description })
.then(resp => this.refresh())
}
handleRemove(todo) {
axios.delete(`${URL}/${todo._id}`)
.then(resp => this.refresh(this.state.description))
}
handleMarkAsDone(todo) {
axios.put(`${URL}/${todo._id}`, { ...todo, done: true })
.then(resp => this.refresh(this.state.description))
}
handleMarkAsPending(todo) {
axios.put(`${URL}/${todo._id}`, { ...todo, done: false })
.then(resp => this.refresh(this.state.description))
}
handleClear() {
this.refresh()
}
render() {
return (
73
<div>
<PageHeader name='Tarefas' small='Cadastro'></PageHeader>
<TodoForm description={this.state.description}
handleChange={this.handleChange}
handleAdd={this.handleAdd}
handleSearch={this.handleSearch}
handleClear={this.handleClear} />
<TodoList list={this.state.list}
handleMarkAsDone={this.handleMarkAsDone}
handleMarkAsPending={this.handleMarkAsPending}
handleRemove={this.handleRemove} />
</div>
)
}
}
74
Listagem 161 - Alterar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx (aprox. linha 5, 6, 20 e 32)
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
> <IconButton style='info' icon='search'
> <IconButton style='default' icon='close'
> </Grid>
</div>
)
}
75
6. React com Redux
6.1. Palavras Iniciais e Instalação
Abra o terminal e dentro da pasta exercicios_react execute o seguinte comando:
ReactDOM.render(
<Provider store={createStore(reducers)}>
<Field initialValue='Teste' />
</Provider>
, document.getElementById('app'))
76
6.4. Ex 01 - Integrando o React-Redux (Parte 1)
function mapStateToProps(state) {
return {
value: state.field.value
}
}
77
Listagem 167 - Alterar arquivo field.js
exercicios_react/ex/field.js (aprox. linha 3, 4, 11, 23 e 27)
function mapStateToProps(state) {
return {
value: state.field.value
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ changeValue }, dispatch)
}
78
Listagem 168 - Criar arquivo fieldReducer.js
exercicios_react/ex/fieldReducer.js
ReactDOM.render(
<Provider store={createStore(reducers)}>
<Field initialValue='Teste' />
</Provider>
, document.getElementById('app'))
79
Listagem 170 - Criar arquivo counterActions.js
exercicios_react/ex/counterActions.js
80
Listagem 172 - Criar arquivo counter.jsx
exercicios_react/ex/counter.jsx
ReactDOM.render(
<Provider store={createStore(reducers)}>
<Counter />
</Provider>
, document.getElementById('app'))
81
Listagem 174 - Sobrescrever arquivo counter.jsx
exercicios_react/ex/counter.jsx
82
7. TodoApp (Migração para Redux)
7.1. Instalando as Dependências
Abra o terminal e dentro da pasta todo-app/frontend execute o seguinte comando:
code .
83
Listagem 178 - Criar arquivo reducers.js
todo-app/frontend/src/main/reducers.js
84
Listagem 179 - Alterar arquivo index.jsx
todo-app/frontend/src/index.jsx (aprox. linha 3, 4, 7, 9 e 11)
85
Listagem 180 - Alterar arquivo todoList.jsx
todo-app/frontend/src/todo/todoList.jsx (aprox. linha 5, 39 e 40)
return (
<table className='table'>
<thead>
<tr>
<th>Descrição</th>
<th className='tableActions'>Ações</th>
</tr>
</thead>
<tbody>
{renderRows()}
</tbody>
</table>
)
}
86
7.5. Conectar o TodoForm com Redux
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
> <IconButton style='info' icon='search'
> <IconButton style='default' icon='close'
> </Grid>
</div>
)
}
87
7.6. Action Creator changeDescription
88
Listagem 183 - Alterar arquivo todoForm.jsx
todo-app/frontend/src/todo/todoForm.jsx (aprox. linha 3, 7, 23, 40 e 42)
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
> <IconButton style='info' icon='search'
> <IconButton style='default' icon='close'
> </Grid>
</div>
)
}
89
Listagem 184 - Criar arquivo todoReducer.js
todo-app/frontend/src/todo/todoReducer.js
const INITIAL_STATE = {
description: 'Ler livro',
list: [{
_id: 1,
description: 'Pagar fatura do cartão',
done: true
},
{
_id: 2,
description: 'Reunião com a equipe às 10:00',
done: false
},
{
_id: 3,
description: 'Consulta médicas na terça depois do almoço',
done: false
}]
}
90
7.7. Configurando o Redux Dev Tools
Para instalar a extensão do plugin do Redux DevTools para Chrome acesse o link:
https://chrome.google.com/webstore/detail/redux-
devtools/lmhkpmbekcpmknklioeibfkpmmfibljd
91
Listagem 187 - Alterar arquivo todoActions.js
todo-app/frontend/src/todo/todoActions.js (aprox. linha 1, 3 e 10)
92
Listagem 188 - Alterar arquivo todoReducer.js
todo-app/frontend/src/todo/todoReducer.js (aprox. linha 22)
const INITIAL_STATE = {
description: 'Ler livro',
list: [{
_id: 1,
description: 'Pagar fatura do cartão',
done: true
},
{
_id: 2,
description: 'Reunião com a equipe às 10:00',
done: false
},
{
_id: 3,
description: 'Consulta médicas na terça depois do almoço',
done: false
}]
}
93
super(props)
this.keyHandler = this.keyHandler.bind(this)
}
componentWillMount() {
this.props.search()
}
keyHandler(e) {
if(e.key === 'Enter'){
e.shiftKey ? this.props.handleSearch() : this.props.handleAdd()
} else if(e.key === 'Escape') {
props.handleClear()
}
}
render() {
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={this.props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
> <IconButton style='info' icon='search'
> <IconButton style='default' icon='close'
> </Grid>
</div>
)
}
}
94
Listagem 190 - Instalando a dependência
95
Listagem 193 - Alterar arquivo todoReducer.js
todo-app/frontend/src/todo/todoReducer.js (aprox. linha 1)
96
Listagem 194 - Alterar arquivo todoActions.js
todo-app/frontend/src/todo/todoActions.js (aprox. linha 18)
97
Listagem 195 - Alterar arquivo todoReducer.js
todo-app/frontend/src/todo/todoReducer.js (aprox. linha 1)
componentWillMount() {
this.props.search()
}
keyHandler(e) {
const { add, search, description } = this.props
if(e.key === 'Enter'){
e.shiftKey ? search() : add(description)
} else if(e.key === 'Escape') {
props.handleClear()
}
98
}
render() {
const { add, search, description } = this.props
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={this.props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
=> add(description)}></IconButton>
<IconButton style='info' icon='search'
=> search()}></IconButton>
<IconButton style='default' icon='close'
> </Grid>
</div>
)
}
}
99
Listagem 199 - Alterar arquivo index.jsx
todo-app/frontend/src/index.jsx (aprox. linha 7 e 15)
100
Listagem 200 - Alterar arquivo todoActions.js
todo-app/frontend/src/todo/todoActions.js (aprox. linha 20)
101
Listagem 203 - Alterar arquivo index.jsx
todo-app/frontend/src/index.jsx (aprox. linha 8 e 15)
102
Listagem 204 - Alterar arquivo todoActions.js
todo-app/frontend/src/todo/todoActions.js (aprox. linha 18)
103
Listagem 205 - Alterar arquivo todoActions.js
todo-app/frontend/src/todo/todoActions.js (aprox. linha 26 e 33)
104
todo-app/frontend/src/todo/todoList.jsx (aprox. linha 2, 3, 6, 17, 19 e 43)
return (
<table className='table'>
<thead>
<tr>
<th>Descrição</th>
<th className='tableActions'>Ações</th>
</tr>
</thead>
<tbody>
{renderRows()}
</tbody>
</table>
)
}
105
7.14. Exclusão de TODOs
106
Listagem 207 - Alterar arquivo todoActions.js
todo-app/frontend/src/todo/todoActions.js (aprox. linha 40)
107
Listagem 208 - Alterar arquivo todoList.jsx
108
todo-app/frontend/src/todo/todoList.jsx (aprox. linha 6, 21 e 44)
return (
<table className='table'>
<thead>
<tr>
<th>Descrição</th>
<th className='tableActions'>Ações</th>
</tr>
</thead>
<tbody>
{renderRows()}
</tbody>
</table>
)
}
109
7.15. Limpar Formulário
110
todo-app/frontend/src/todo/todoActions.js (aprox. linha 21, 47)
111
Listagem 210 - Alterar arquivo todoReducer.js
todo-app/frontend/src/todo/todoReducer.js (aprox. linha 9)
componentWillMount() {
this.props.search()
}
keyHandler(e) {
const { add, search, description, clear } = this.props
if(e.key === 'Enter'){
e.shiftKey ? search() : add(description)
} else if(e.key === 'Escape') {
clear()
}
112
}
render() {
const { add, search, description } = this.props
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={this.props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
=> add(description)}></IconButton>
<IconButton style='info' icon='search'
=> search()}></IconButton>
<IconButton style='default' icon='close'
> </Grid>
</div>
)
}
}
113
const description = getState().todo.description
const search = description ? `&description__regex=/${description}/` : ''
const request = axios.get(`${URL}?sort=-createdAt${search}`)
.then(resp => dispatch({type: 'TODO_SEARCHED', payload: resp.data}))
}
}
114
import IconButton from '../template/iconButton'
import { add, changeDescription, search, clear } from './todoActions'
componentWillMount() {
this.props.search()
}
keyHandler(e) {
const { add, search, description, clear } = this.props
if(e.key === 'Enter'){
e.shiftKey ? search() : add(description)
} else if(e.key === 'Escape') {
clear()
}
}
render() {
const { add, search, description } = this.props
return (
<div role='form' className='todoForm'>
<Grid cols='12 9 10'>
<input id='description' className='form-control'
placeholder='Adicione uma tarefa'
> > value={this.props.description}></input>
</Grid>
<Grid cols='12 3 2'>
<IconButton style='primary' icon='plus'
=> add(description)}></IconButton>
<IconButton style='info' icon='search'
> <IconButton style='default' icon='close'
> </Grid>
</div>
)
}
}
115
Listagem 214 - Alterar arquivo todoReducer.js
todo-app/frontend/src/todo/todoReducer.js (aprox. linha 8)
116
8. Aplicação Final - Ciclos de Pagamentos
(Backend)
8.1. Visão Geral
npm init -y
117
Listagem 221 - Alterar arquivo package.json
my-money-app/backend/package.json (aprox. linha 2, 5 e 7)
{
"name": "my-money-backend",
"version": "1.0.0",
"description": "",
"main": "src/loader.js",
"scripts": {
"dev": "nodemon",
"production": "pm2 start src/loader.js --name my-money-backend"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.15.2",
"express": "^4.14.0",
"express-query-int": "^1.0.1",
"lodash": "^4.17.4",
"mongoose": "^4.7.0",
"mongoose-paginate": "^5.0.3",
"node-restful": "^0.2.5",
"pm2": "^2.1.5"
},
"devDependencies": {
"nodemon": "^1.11.0"
}
}
node_modules
*.log
118
Listagem 223 - Criar arquivo loader.js
my-money-app/backend/src/loader.js
require('./config/server')
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
119
Listagem 227 - Alterar arquivo loader.js
my-money-app/backend/src/loader.js (aprox. linha 2)
require('./config/server')
require('./config/database')
mongod
120
Listagem 229 - Criar arquivo billingCycle.js
my-money-app/backend/src/api/billingCycle/billingCycle.js
module.exports = BillingCycle
121
Listagem 231 - Criar arquivo routes.js
my-money-app/backend/src/config/routes.js
module.exports = function(server) {
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
module.exports = server
122
8.8. Testando a API (Postman)
123
Listagem 235 - Alterar arquivo billingCycle.js
my-money-app/backend/src/api/billingCycle/billingCycle.js (aprox. linha 11)
124
Listagem 236 - Alterar arquivo billingCycleService.js
my-money-app/backend/src/api/billingCycle/billingCycleService.js (aprox. linha 6)
module.exports = BillingCycle
125
Listagem 237 - Alterar arquivo billingCycleService.js
my-money-app/backend/src/api/billingCycle/billingCycleService.js (aprox. linha 16)
module.exports = BillingCycle
126
Listagem 238 - Criar arquivo errorHandler.js
my-money-app/backend/src/api/common/errorHandler.js
const _ = require('lodash')
if(bundle.errors) {
const errors = parseErrors(bundle.errors)
res.status(500).json({errors})
} else {
next()
}
}
127
Listagem 239 - Alterar arquivo billingCycleService.js
my-money-app/backend/src/api/billingCycle/billingCycleService.js (aprox. linha 2 e 6)
module.exports = BillingCycle
128
Listagem 240 - Criar arquivo cors.js
my-money-app/backend/src/config/cors.js
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
module.exports = server
129
Listagem 242 - Alterar arquivo server.js
my-money-app/backend/src/config/server.js (aprox. linha 7 e 12)
server.listen(port, function() {
console.log(`BACKEND is running on port ${port}.`)
})
module.exports = server
130
9. Aplicação Final - Ciclos de Pagamentos
(Frontend)
9.1. Visão Geral
./node_modules/.bin/pm2 monit
npm init -y
131
Listagem 247 - Instalando as dependências do Webpack
code .
132
Listagem 253 - Criar arquivo .gitignore
my-money-app/frontend/.gitignore
node_modules
*.log
133
my-money-app/frontend/webpack.config.js
module.exports = {
entry: './src/index.jsx',
output: {
path: __dirname + '/public',
filename: './app.js'
},
devServer: {
port: 8080,
contentBase: './public',
},
resolve: {
extensions: ['', '.js', '.jsx'],
alias: {
modules: __dirname + '/node_modules',
jquery: 'modules/admin-lte/plugins/jQuery/jquery-2.2.3.min.js',
bootstrap: 'modules/admin-lte/bootstrap/js/bootstrap.js'
}
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
}),
new ExtractTextPlugin('app.css')
],
module: {
loaders: [{
test: /.js[x]?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['transform-object-rest-spread']
}
}, {
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
}, {
test: /\.woff|.woff2|.ttf|.eot|.svg|.png|.jpg*.*$/,
loader: 'file'
}]
}
}
134
Listagem 255 - Alterar arquivo package.json
my-money-app/frontend/package.json (aprox. linha 2 e 7)
{
"name": "my-money-frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --progress --colors --inline --hot",
"production": "webpack --progress -p"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"admin-lte": "^2.3.6",
"axios": "^0.15.3",
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-plugin-react-html-attrs": "^2.0.0",
"babel-plugin-transform-object-rest-spread": "^6.22.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.22.0",
"css-loader": "^0.26.1",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"font-awesome": "^4.7.0",
"ionicons": "^3.0.0",
"lodash": "^4.17.4",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^4.4.6",
"react-redux-toastr": "^4.4.2",
"react-router": "^3.0.2",
"redux": "^3.6.0",
"redux-form": "^6.4.1",
"redux-multi": "^0.1.12",
"redux-promise": "^0.5.3",
"redux-thunk": "^2.1.0",
"style-loader": "^0.13.1",
"webpack": "^1.14.0",
"webpack-dev-server": "^1.16.2"
}
}
135
9.4. Criação do index.html
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>My Money</title>
<link rel='stylesheet' href='app.css'>
</head>
<body class='skin-blue fixed sidebar-mini'>
<div id="app"></div>
<script src='app.js'></script>
</body>
</html>
136
Listagem 258 - Criar arquivo index.jsx
my-money-app/frontend/src/index.jsx
import 'modules/admin-lte/plugins/jQueryUI/jquery-ui.min'
import 'modules/admin-lte/plugins/fastclick/fastclick'
import 'modules/admin-lte/plugins/slimScroll/jquery.slimscroll.min'
import 'modules/admin-lte/dist/js/app.min'
import 'modules/font-awesome/css/font-awesome.min.css'
import 'modules/ionicons/dist/css/ionicons.min.css'
import 'modules/admin-lte/bootstrap/css/bootstrap.min.css'
import 'modules/admin-lte/dist/css/AdminLTE.min.css'
import 'modules/admin-lte/dist/css/skins/_all-skins.min.css'
import 'modules/admin-lte/plugins/iCheck/flat/blue.css'
import './custom.css'
137
Listagem 261 - Alterar arquivo app.jsx
my-money-app/frontend/src/main/app.jsx (na linha 1)
import '../common/template/dependencies'
import React from 'react'
138
Listagem 263 - Alterar arquivo app.jsx
my-money-app/frontend/src/main/app.jsx (aprox. linha 4 e 8)
import '../common/template/dependencies'
import React from 'react'
</ul>
)
139
Listagem 266 - Alterar arquivo app.jsx
my-money-app/frontend/src/main/app.jsx (aprox. linha 5 e 10)
import '../common/template/dependencies'
import React from 'react'
140
Listagem 268 - Alterar arquivo menu.jsx
my-money-app/frontend/src/common/template/menu.jsx (aprox. linha 2 e 6)
141
Listagem 270 - Alterar arquivo menu.jsx
my-money-app/frontend/src/common/template/menu.jsx (aprox. linha 3, 7 e 8)
142
Listagem 272 - Alterar arquivo app.jsx
my-money-app/frontend/src/main/app.jsx (aprox. linha 6 e 12)
import '../common/template/dependencies'
import React from 'react'
.main-footer {
position: fixed;
bottom:0px;
width:100%;
}
143
Listagem 274 - Criar arquivo dashboard.jsx
my-money-app/frontend/src/dashboard/dashboard.jsx
144
Listagem 277 - Alterar arquivo app.jsx
my-money-app/frontend/src/main/app.jsx (aprox. linha 7 e 14)
import '../common/template/dependencies'
import React from 'react'
145
Listagem 279 - Criar arquivo content.jsx
my-money-app/frontend/src/common/template/content.jsx
146
Listagem 281 - Criar arquivo grid.jsx
my-money-app/frontend/src/common/layout/grid.jsx
toCssClasses(numbers) {
const cols = numbers ? numbers.split(' ') : []
let classes = ''
return classes
}
render() {
const gridClasses = this.toCssClasses(this.props.cols || '12')
return (
<div className={gridClasses}>
{this.props.children}
</div>
)
}
}
147
Listagem 282 - Criar arquivo valueBox.jsx
my-money-app/frontend/src/common/widget/valueBox.jsx
148
Listagem 283 - Alterar arquivo dashboard.jsx
my-money-app/frontend/src/dashboard/dashboard.jsx (aprox. linha 5 e 13)
149
Listagem 285 - Criar arquivo reducers.js
my-money-app/frontend/src/main/reducers.js
150
Listagem 287 - Alterar arquivo dashboard.jsx
my-money-app/frontend/src/dashboard/dashboard.jsx (aprox. linha 2, 11, 18, 20, 22, 29 e 30)
151
Listagem 289 - Alterar arquivo reducers.js
my-money-app/frontend/src/main/reducers.js (aprox. linha 3 e 6)
152
Listagem 291 - Alterar arquivo dashboardReducer.js
my-money-app/frontend/src/dashboard/dashboardReducer.js (aprox. linha 4)
153
Listagem 292 - Alterar arquivo dashboard.jsx
my-money-app/frontend/src/dashboard/dashboard.jsx (aprox. linha 3, 5, 13, 33 e 34)
componentWillMount() {
this.props.getSummary()
}
render() {
const { credit, debt } = this.props.summary
return (
<div>
<ContentHeader title='Dashboard' small='Versão 1.0' />
<Content>
<Row>
<ValueBox cols='12 4' color='green' icon='bank'
value={`R$ ${credit}`} text='Total de Créditos' />
<ValueBox cols='12 4' color='red' icon='credit-card'
value={`R$ ${debt}`} text='Total de Débitos' />
<ValueBox cols='12 4' color='blue' icon='money'
value={`R$ ${credit - debt}`} text='Valor
Consolidado' />
</Row>
</Content>
</div>
)
}
}
154
Listagem 293 - Alterar arquivo index.jsx
my-money-app/frontend/src/index.jsx (aprox. linha 3, 6 e 11 )
155
Listagem 294 - Alterar arquivo index.jsx
my-money-app/frontend/src/index.jsx (aprox. linha 11 e 13 )
156
Listagem 295 - Criar arquivo dashboard2.jsx
my-money-app/frontend/src/dashboard2/dashboard2.jsx
constructor(props) {
super(props)
this.state = { credit: 0, debt: 0 }
}
componentWillMount() {
axios.get(`${BASE_URL}/billingCycles/summary`)
.then(resp => this.setState(resp.data))
}
render() {
const { credit, debt } = this.state
return (
<div>
<ContentHeader title='Dashboard' small='Versão 2.0' />
<Content>
<Row>
<ValueBox cols='12 4' color='green' icon='bank'
value={`R$ ${credit}`} text='Total de Créditos' />
<ValueBox cols='12 4' color='red' icon='credit-card'
value={`R$ ${debt}`} text='Total de Débitos' />
<ValueBox cols='12 4' color='blue' icon='money'
value={`R$ ${credit - debt}`} text='Valor
Consolidado' />
</Row>
</Content>
</div>
)
}
}
157
9.23. Visão Geral dos Componentes de Abas
158
Listagem 298 - Criar arquivo tabsHeader.jsx
my-money-app/frontend/src/common/tab/tabsHeader.jsx
159
Listagem 300 - Alterar arquivo billingCycle.jsx
my-money-app/frontend/src/billingCycle/billingCycle.jsx (aprox. linha 5, 6, 7 e 15)
</TabsHeader>
<TabsContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
160
Listagem 301 - Criar arquivo tabHeader.jsx
my-money-app/frontend/src/common/tab/tabHeader.jsx
161
Listagem 302 - Alterar arquivo billingCycle.jsx
my-money-app/frontend/src/billingCycle/billingCycle.jsx (aprox. linha 8)
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
162
Listagem 303 - Alterar arquivo tabHeader.jsx
my-money-app/frontend/src/common/tab/tabHeader.jsx (aprox. linha 7)
163
Listagem 305 - Criar arquivo tabReducer.js
my-money-app/frontend/src/common/tab/tabReducer.js
164
Listagem 307 - Alterar arquivo tabHeader.jsx
my-money-app/frontend/src/common/tab/tabHeader.jsx (aprox. linha 2, 3, 5, 9, 11, 13, 21, 22 e 23)
165
Listagem 309 - Criar arquivo tabContent.jsx
my-money-app/frontend/src/common/tab/tabContent.jsx
166
my-money-app/frontend/src/billingCycle/billingCycle.jsx (aprox. linha 9 e 25)
167
9.30. Componente BillingCycle: Conectando com Redux
componentWillMount() {
this.props.selectTab('tabList')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<h1>Lista</h1>
</TabContent>
<TabContent id='tabCreate'>
<h1>Incluir</h1>
</TabContent>
<TabContent id='tabUpdate'>
<h1>Alterar</h1>
168
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
169
Listagem 313 - Alterar arquivo tabReducer.js
my-money-app/frontend/src/common/tab/tabReducer.js (aprox. linha 1 e 7)
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
170
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<h1>Lista</h1>
</TabContent>
<TabContent id='tabCreate'>
<h1>Incluir</h1>
</TabContent>
<TabContent id='tabUpdate'>
<h1>Alterar</h1>
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
171
Listagem 315 - Criar arquivo if.jsx
my-money-app/frontend/src/common/operator/if.jsx
172
Listagem 316 - Alterar arquivo tabHeader.jsx
my-money-app/frontend/src/common/tab/tabHeader.jsx (aprox. linha 5, 11, 13 e 22)
173
Listagem 317 - Alterar arquivo tabContent.jsx
my-money-app/frontend/src/common/tab/tabContent.jsx (aprox. linha 4, 9, 11 e 16)
174
Listagem 319 - Criar arquivo billingCycleReducer.js
my-money-app/frontend/src/billingCycle/billingCycleReducer.js
175
Listagem 321 - Criar arquivo billingCycleList.jsx
my-money-app/frontend/src/billingCycle/billingCycleList.jsx
render() {
return (
<div>
<table className='table'>
<thead>
<tr>
<th>Nome</th>
<th>Mês</th>
<th>Ano</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
)
}
}
176
class BillingCycle extends Component {
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<h1>Incluir</h1>
</TabContent>
<TabContent id='tabUpdate'>
<h1>Alterar</h1>
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
177
9.36. Integrando BillingCycleList com Redux
componentWillMount() {
this.props.getList()
}
render() {
return (
<div>
<table className='table'>
<thead>
<tr>
<th>Nome</th>
<th>Mês</th>
<th>Ano</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
)
}
}
178
Listagem 324 - Alterar arquivo billingCycleList.jsx_
my-money-app/frontend/src/billingCycle/billingCycleList.jsx (aprox. linha 12 e 34)
componentWillMount() {
this.props.getList()
}
renderRows() {
const list = this.props.list || []
return list.map(bc => (
<tr key={bc._id}>
<td>{bc.name}</td>
<td>{bc.month}</td>
<td>{bc.year}</td>
</tr>
))
}
render() {
return (
<div>
<table className='table'>
<thead>
<tr>
<th>Nome</th>
<th>Mês</th>
<th>Ano</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</div>
)
}
}
179
9.38. BillingCycleForm com Redux-Form (Parte 01)
render() {
return (
<form role='form'>
<div className='box-body'>
</div>
<div className='box-footer'>
<button type='submit' className='btn btn-primary'
>Submit</button>
</div>
</form>
)
}
}
180
Listagem 327 - Alterar arquivo billingCycle.jsx
my-money-app/frontend/src/billingCycle/billingCycle.jsx (aprox. linha 15 e 41)
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form />
</TabContent>
181
<TabContent id='tabUpdate'>
<h1>Alterar</h1>
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
182
Listagem 328 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 2, 7, 10)
render() {
const { handleSubmit } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component='input' />
<Field name='month' component='input' />
<Field name='year' component='input' />
</div>
<div className='box-footer'>
<button type='submit' className='btn btn-primary'
>Submit</button>
</div>
</form>
)
}
}
183
Listagem 329 - Alterar arquivo billingCycleActions.js
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 12)
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
184
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form />
</TabContent>
<TabContent id='tabUpdate'>
<h1>Alterar</h1>
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
185
Listagem 331 - Criar arquivo messages.jsx
my-money-app/frontend/src/common/msg/messages.jsx
import '../common/template/dependencies'
import React from 'react'
186
Listagem 333 - Alterar arquivo reducers.js
my-money-app/frontend/src/main/reducers.js (aprox. linha 3 e 14)
187
Listagem 334 - Alterar arquivo billingCycleActions.js
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 2 e 15)
188
Listagem 335 - Alterar arquivo index.jsx
my-money-app/frontend/src/index.jsx (aprox. linha 7, 8 e 15 )
189
Listagem 336 - Alterar arquivo billingCycleActions.js
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 3, 4, 14 e 21)
190
Listagem 337 - Criar arquivo labelAndInput.jsx
my-money-app/frontend/src/common/form/labelAndInput.jsx
191
Listagem 338 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 3, 12, 14 e 16)
render() {
const { handleSubmit } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
label='Ano' cols='12 4' placeholder='Informe o ano' />
</div>
<div className='box-footer'>
<button type='submit' className='btn btn-primary'
>Submit</button>
</div>
</form>
)
}
}
192
Listagem 339 - Alterar arquivo billingCycleActions.js
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 34)
193
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { getList, showUpdate } from './billingCycleActions'
componentWillMount() {
this.props.getList()
}
renderRows() {
const list = this.props.list || []
return list.map(bc => (
<tr key={bc._id}>
<td>{bc.name}</td>
<td>{bc.month}</td>
<td>{bc.year}</td>
<td>
<button className='btn btn-warning' =>
this.props.showUpdate(bc)}>
<i className='fa fa-pencil'></i>
</button>
</td>
</tr>
))
}
render() {
return (
<div>
<table className='table'>
<thead>
<tr>
<th>Nome</th>
<th>Mês</th>
<th>Ano</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</div>
)
}
}
194
dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(BillingCycleList)
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
195
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form />
</TabContent>
<TabContent id='tabUpdate'>
<Form />
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
196
Listagem 342 - Alterar arquivo billingCycleActions.js
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 3 e 38)
197
Listagem 343 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 27)
render() {
const { handleSubmit } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
label='Ano' cols='12 4' placeholder='Informe o ano' />
</div>
<div className='box-footer'>
<button type='submit' className='btn btn-primary'
>Submit</button>
</div>
</form>
)
}
}
198
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 7, 22 e 38)
199
Listagem 345 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 2, 3, 6, 22 e 33)
render() {
const { handleSubmit } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
label='Ano' cols='12 4' placeholder='Informe o ano' />
</div>
<div className='box-footer'>
<button type='submit' className='btn btn-primary'
>Submit</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
200
import axios from 'axios'
import { toastr } from 'react-redux-toastr'
import { reset as resetForm, initialize } from 'redux-form'
import { showTabs, selectTab } from '../common/tab/tabActions'
201
getList(),
initialize('billingCycleForm', INITIAL_VALUES)
]
}
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
202
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form />
</TabContent>
<TabContent id='tabUpdate'>
<Form />
</TabContent>
<TabContent id='tabDelete'>
<h1>Excluir</h1>
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
203
export function create(values) {
return submit(values, 'post')
}
204
my-money-app/frontend/src/billingCycle/billingCycleList.jsx (aprox. linha 4, 23 e 53)
componentWillMount() {
this.props.getList()
}
renderRows() {
const list = this.props.list || []
return list.map(bc => (
<tr key={bc._id}>
<td>{bc.name}</td>
<td>{bc.month}</td>
<td>{bc.year}</td>
<td>
<button className='btn btn-warning' =>
this.props.showUpdate(bc)}>
<i className='fa fa-pencil'></i>
</button>
<button className='btn btn-danger' =>
this.props.showDelete(bc)}>
<i className='fa fa-trash-o'></i>
</button>
</td>
</tr>
))
}
render() {
return (
<div>
<table className='table'>
<thead>
<tr>
<th>Nome</th>
<th>Mês</th>
<th>Ano</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</div>
205
)
}
}
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
206
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form />
</TabContent>
<TabContent id='tabUpdate'>
<Form />
</TabContent>
<TabContent id='tabDelete'>
<Form readOnly={
true} />
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
207
}
208
Listagem 352 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 12, 16, 18 e 20)
render() {
const { handleSubmit, readOnly } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
</div>
<div className='box-footer'>
<button type='submit' className='btn btn-primary'
>Submit</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
209
Listagem 353 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 24 e 25)
render() {
const { handleSubmit, readOnly } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
210
my-money-app/frontend/src/billingCycle/billingCycle.jsx (aprox. linha 43, 47 e 51)
componentWillMount() {
this.props.selectTab('tabList')
this.props.showTabs('tabList', 'tabCreate')
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form > submitLabel='Incluir' submitClass='primary'
/>
</TabContent>
211
<TabContent id='tabUpdate'>
<Form > submitLabel='Alterar' submitClass='info' />
</TabContent>
<TabContent id='tabDelete'>
<Form readOnly={
true}
submitLabel='Excluir' submitClass='danger' />
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
.main-footer {
position: fixed;
bottom:0px;
width:100%;
}
button {
margin-left: 5px;
}
.table-actions {
width: 150px;
}
212
import { getList, showUpdate, showDelete } from './billingCycleActions'
componentWillMount() {
this.props.getList()
}
renderRows() {
const list = this.props.list || []
return list.map(bc => (
<tr key={bc._id}>
<td>{bc.name}</td>
<td>{bc.month}</td>
<td>{bc.year}</td>
<td>
<button className='btn btn-warning' =>
this.props.showUpdate(bc)}>
<i className='fa fa-pencil'></i>
</button>
<button className='btn btn-danger' =>
this.props.showDelete(bc)}>
<i className='fa fa-trash-o'></i>
</button>
</td>
</tr>
))
}
render() {
return (
<div>
<table className='table'>
<thead>
<tr>
<th>Nome</th>
<th>Mês</th>
<th>Ano</th>
<th className='table-actions'>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</div>
)
}
}
213
const mapDispatchToProps = dispatch => bindActionCreators({getList, showUpdate,
showDelete}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(BillingCycleList)
214
Listagem 357 - Criar arquivo creditList.jsx_
my-money-app/frontend/src/billingCycle/creditList.jsx
renderRows() {
return (
<tr>
<td><Field name='credits[0].name' component='input' /></td>
<td><Field name='credits[0].value' component='input' /></td>
<td></td>
</tr>
)
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>Créditos</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
215
Listagem 358 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 8 e 23)
render() {
const { handleSubmit, readOnly } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<CreditList cols='12 6' />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
216
9.50. Componente Input para CreditList
217
Listagem 360 - Alterar arquivo creditList.jsx_
my-money-app/frontend/src/billingCycle/creditList.jsx (aprox. linha 4, 12 e 14)
renderRows() {
return (
<tr>
<td><Field name='credits[0].name' component={Input}
placeholder='Informe o nome' readOnly={this.props.readOnly}
/></td>
<td><Field name='credits[0].value' component={Input}
placeholder='Informe o valor' readOnly={this.props.readOnly}
/></td>
<td></td>
</tr>
)
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>Créditos</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
218
Listagem 361 - Alterar arquivo billingCycleForm.jsx
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 23)
render() {
const { handleSubmit, readOnly } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<CreditList cols='12 6' readOnly={readOnly} />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
219
9.51. Evoluindo o Componente CreditList
220
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 4, 13, 23, 38, 39 e 41)
render() {
const { handleSubmit, readOnly, credits } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<CreditList cols='12 6' list={credits} readOnly={readOnly} />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
221
Listagem 363 - Alterar arquivo creditList.jsx_
my-money-app/frontend/src/billingCycle/creditList.jsx (aprox. linha 9, 10, 12 e 14)
renderRows() {
const list = this.props.list || []
return list.map((item, index) => (
<tr key={index}>
<td><Field name={`credits[${index}].name`} component={Input}
placeholder='Informe o nome' readOnly={this.props.readOnly}
/></td>
<td><Field name={`credits[${index}].value`} component={Input}
placeholder='Informe o valor' readOnly={this.props.readOnly}
/></td>
<td></td>
</tr>
))
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>Créditos</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
222
Listagem 364 - Alterar arquivo billingCycleActions.js
my-money-app/frontend/src/billingCycle/billingCycleActions.js (aprox. linha 7)
223
selectTab('tabUpdate'),
initialize('billingCycleForm', billingCycle)
]
}
componentWillMount() {
this.props.init()
}
render() {
return (
<div>
<ContentHeader title='Ciclos de Pagamentos' small='Cadastro' />
<Content>
224
<Tabs>
<TabsHeader>
<TabHeader label='Listar' icon='bars'
target='tabList' />
<TabHeader label='Incluir' icon='plus'
target='tabCreate' />
<TabHeader label='Alterar' icon='pencil'
target='tabUpdate' />
<TabHeader label='Excluir' icon='trash-o'
target='tabDelete' />
</TabsHeader>
<TabsContent>
<TabContent id='tabList'>
<List />
</TabContent>
<TabContent id='tabCreate'>
<Form > submitLabel='Incluir' submitClass='primary'
/>
</TabContent>
<TabContent id='tabUpdate'>
<Form > submitLabel='Alterar' submitClass='info' />
</TabContent>
<TabContent id='tabDelete'>
<Form readOnly={
true}
submitLabel='Excluir' submitClass='danger' />
</TabContent>
</TabsContent>
</Tabs>
</Content>
</div>
)
}
}
225
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { Field, arrayInsert } from 'redux-form'
import Grid from '../common/layout/grid'
import Input from '../common/form/input'
renderRows() {
const list = this.props.list || []
return list.map((item, index) => (
<tr key={index}>
<td><Field name={`credits[${index}].name`} component={Input}
placeholder='Informe o nome' readOnly={this.props.readOnly}
/></td>
<td><Field name={`credits[${index}].value`} component={Input}
placeholder='Informe o valor' readOnly={this.props.readOnly}
/></td>
<td>
<button type='button' className='btn btn-success'
=> this.add(index + 1)}>
<i className="fa fa-plus"></i>
</button>
<button type='button' className='btn btn-warning'
=> this.add(index + 1, item)}>
<i className="fa fa-clone"></i>
</button>
</td>
</tr>
))
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>Créditos</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<th className='table-actions'>Ações</th>
</tr>
226
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
remove(index) {
if(!this.props.readOnly && this.props.list.length > 1) {
this.props.arrayRemove('billingCycleForm', 'credits', index)
}
}
renderRows() {
const list = this.props.list || []
return list.map((item, index) => (
<tr key={index}>
<td><Field name={`credits[${index}].name`} component={Input}
placeholder='Informe o nome' readOnly={this.props.readOnly}
227
/></td>
<td><Field name={`credits[${index}].value`} component={Input}
placeholder='Informe o valor' readOnly={this.props.readOnly}
/></td>
<td>
<button type='button' className='btn btn-success'
=> this.add(index + 1)}>
<i className="fa fa-plus"></i>
</button>
<button type='button' className='btn btn-warning'
=> this.add(index + 1, item)}>
<i className="fa fa-clone"></i>
</button>
<button type='button' className='btn btn-danger'
=> this.remove(index)}>
<i className="fa fa-trash-o"></i>
</button>
</td>
</tr>
))
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>Créditos</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<th className='table-actions'>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
228
9.54. Generalizando o Componente CreditList
remove(index) {
if(!this.props.readOnly && this.props.list.length > 1) {
this.props.arrayRemove('billingCycleForm', this.props.field, index)
}
}
renderRows() {
const list = this.props.list || []
return list.map((item, index) => (
<tr key={index}>
<td><Field name={`${this.props.field}[${index}].name`}
component={Input}
placeholder='Informe o nome' readOnly={this.props.readOnly}
/></td>
<td><Field name={`${this.props.field}[${index}].value`}
component={Input}
placeholder='Informe o valor' readOnly={this.props.readOnly}
/></td>
<td>
<button type='button' className='btn btn-success'
=> this.add(index + 1)}>
<i className="fa fa-plus"></i>
</button>
<button type='button' className='btn btn-warning'
=> this.add(index + 1, item)}>
229
<i className="fa fa-clone"></i>
</button>
<button type='button' className='btn btn-danger'
=> this.remove(index)}>
<i className="fa fa-trash-o"></i>
</button>
</td>
</tr>
))
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>{this.props.legend}</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<th className='table-actions'>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
230
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 8, 23 e 24)
render() {
const { handleSubmit, readOnly, credits } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<ItemList cols='12 6' list={credits} readOnly={readOnly}
field='credits' legend='Créditos' />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
231
9.55. Adicionando o Campo Status no ItemList
render() {
const { handleSubmit, readOnly, credits, debts } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<ItemList cols='12 6' list={credits} readOnly={readOnly}
field='credits' legend='Créditos' />
<ItemList cols='12 6' list={debts} readOnly={readOnly}
field='debts' legend='Débitos' showStatus={true} />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
232
false})(BillingCycleForm)
const selector = formValueSelector('billingCycleForm')
const mapStateToProps = state => ({
credits: selector(state, 'credits'),
debts: selector(state, 'debts')
})
const mapDispatchToProps = dispatch => bindActionCreators({init}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(BillingCycleForm)
233
.catch(e => {
e.response.data.errors.forEach(error => toastr.error('Erro',
error))
})
}
}
remove(index) {
if(!this.props.readOnly && this.props.list.length > 1) {
this.props.arrayRemove('billingCycleForm', this.props.field, index)
}
234
}
renderRows() {
const list = this.props.list || []
return list.map((item, index) => (
<tr key={index}>
<td><Field name={`${this.props.field}[${index}].name`}
component={Input}
placeholder='Informe o nome' readOnly={this.props.readOnly}
/></td>
<td><Field name={`${this.props.field}[${index}].value`}
component={Input}
placeholder='Informe o valor' readOnly={this.props.readOnly}
/></td>
<If test={this.props.showStatus}>
<td><Field name={`${this.props.field}[${index}].status`}
component={Input}
placeholder='Informe o status' readOnly={
this.props.readOnly} /></td>
</If>
<td>
<button type='button' className='btn btn-success'
=> this.add(index + 1)}>
<i className="fa fa-plus"></i>
</button>
<button type='button' className='btn btn-warning'
=> this.add(index + 1, item)}>
<i className="fa fa-clone"></i>
</button>
<button type='button' className='btn btn-danger'
=> this.remove(index)}>
<i className="fa fa-trash-o"></i>
</button>
</td>
</tr>
))
}
render() {
return (
<Grid cols={this.props.cols}>
<fieldset>
<legend>{this.props.legend}</legend>
<table>
<thead>
<tr>
<th>Nome</th>
<th>Valor</th>
<If test={this.props.showStatus}>
<th>Status</th>
</If>
235
<th className='table-actions'>Ações</th>
</tr>
</thead>
<tbody>
{this.renderRows()}
</tbody>
</table>
</fieldset>
</Grid>
)
}
}
236
my-money-app/frontend/src/billingCycle/billingCycleForm.jsx (aprox. linha 9 e 24)
render() {
const { handleSubmit, readOnly, credits, debts } = this.props
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<Summary credit={1000} debt={100} />
<ItemList cols='12 6' list={credits} readOnly={readOnly}
field='credits' legend='Créditos' />
<ItemList cols='12 6' list={debts} readOnly={readOnly}
field='debts' legend='Débitos' showStatus={true} />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
237
credits: selector(state, 'credits'),
debts: selector(state, 'debts')
})
const mapDispatchToProps = dispatch => bindActionCreators({init}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(BillingCycleForm)
calculateSummary() {
const sum = (t, v) => t + v
return {
sumOfCredits: this.props.credits.map(c => +c.value || 0).reduce(sum),
sumOfDebts: this.props.debts.map(d => +d.value || 0).reduce(sum)
}
}
render() {
const { handleSubmit, readOnly, credits, debts } = this.props
const { sumOfCredits, sumOfDebts } = this.calculateSummary()
return (
<form role='form' > <div className='box-body'>
<Field name='name' component={LabelAndInput}
readOnly={readOnly}
label='Nome' cols='12 4' placeholder='Informe o nome' />
<Field name='month' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Mês' cols='12 4' placeholder='Informe o mês' />
<Field name='year' component={LabelAndInput} type='number'
readOnly={readOnly}
label='Ano' cols='12 4' placeholder='Informe o ano' />
<Summary credit={sumOfCredits} debt={sumOfDebts} />
238
<ItemList cols='12 6' list={credits} readOnly={readOnly}
field='credits' legend='Créditos' />
<ItemList cols='12 6' list={debts} readOnly={readOnly}
field='debts' legend='Débitos' showStatus={true} />
</div>
<div className='box-footer'>
<button type='submit' className={`btn btn-${
this.props.submitClass}`}>
{this.props.submitLabel}
</button>
<button type='button' className='btn btn-default'
> </div>
</form>
)
}
}
239
10. Melhorias e Correções
10.1. Problema de Responsividade do Menu
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>My Money</title>
<link rel='stylesheet' href='app.css'>
</head>
<body class='skin-blue fixed sidebar-mini'>
<div id="app" class="wrapper"></div>
<script src='app.js'></script>
</body>
</html>
240
Listagem 378 - Alterar arquivo menuTree.jsx
my-money-app/frontend/src/common/template/menuTree.jsx (aprox. linha 6)
241
Listagem 380 - Alterar arquivo routes.jsx
my-money-app/frontend/src/main/routes.jsx (aprox. linha 2, 4, 10 e 13)
import '../common/template/dependencies'
import React from 'react'
242
Listagem 382 - Alterar arquivo index.jsx
my-money-app/frontend/src/index.jsx (aprox. linha 10 e 18)
243
11. My Money APP: Autenticação
11.1. Configurar Ambiente
Criei uma nova pasta e no terminal dentro da pasta execute o seguinte comando:
• jsonwebtoken@7.3.0
11.2.1. Instalação
aplicação backend, por enquando será armazendo apenas a chave usada para
gerar o token JWT.
244
Listagem 3 - Criar arquivo .env
my-money-app/backend/src/.env
module.exports = {
// Você pode alterar essa chave!
authSecret: 'skjdhf6$$%dojkhfˆ(sdkjhf'
}
É muito importante que o arquivo .env não seja commitado no repositório, pois
nele está a chave secreta para geração do token.
node_modules
*.log
.env
Criar uma nova pasta ao projeto backend chamada user dentro de src/api.
ou seja, mapear o objeto javascript user para o documento que será armazenado
no MongoDB.
245
11.3.3. Criar arquivo authService.js
const _ = require('lodash')
const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')
const User = require('./user')
const env = require('../../.env')
246
Listagem 8 - Criar método validateToken de authService.js
my-money-app/backend/src/api/user/authService.js
247
Listagem 9 - Criar método signup de authService.js
my-money-app/backend/src/api/user/authService.js
if(!email.match(emailRegex)) {
return res.status(400).send({errors: ['O e-mail informa está inválido']})
}
if(!password.match(passwordRegex)) {
return res.status(400).send({errors: [
"Senha precisar ter: uma letra maiúscula, uma letra minúscula, um n
úmero, uma caractere especial(@#$%) e tamanho entre 6-20."
]})
}
248
Listagem 10 - Exportar os métodos de authService.js
my-money-app/backend/src/api/user/authService.js
Esse middleware será o responsável por validar o token JWT para as routas
protegidas e garantir que a API esteja protegida.
if(!token) {
return res.status(403).send({errors: ['No token provided.']})
}
249
Listagem 12 - Adicionar header em cors.js
my-money-app/backend/src/config/cors.js
Além de cadastrar as novas rotas login, signup e validateToken, essa alteração tem
por objetivo separar as routas públicas das rotas privadas (acesso com
autenticação).
250
Listagem 13 - Alterar arquivo routes.js
my-money-app/backend/src/config/routes.js
module.exports = function(server) {
/*
* Rotas protegidas por Token JWT
*/
const protectedApi = express.Router()
server.use('/api', protectedApi)
protectedApi.use(auth)
/*
* Rotas abertas
*/
const openApi = express.Router()
server.use('/oapi', openApi)
251
Listagem 14 - Criar arquivo authReducer.js
my-money-app/frontend/src/auth/authReducer.js
auth: AuthReducer
252
Listagem 16 - Criar arquivo consts.js
my-money-app/frontend/src/consts.js
export default {
API_URL: 'http://localhost:3003/api',
OAPI_URL: 'http://localhost:3003/oapi',
}
253
Listagem 18 - Adicionar novas actions à authActions.js
my-money-app/frontend/src/auth/authActions.js
254
Listagem 19 - Criar componente navbar.jsx
my-money-app/frontend/src/common/template/navbar.jsx
changeOpen() {
this.setState({ open: !this.state.open })
}
render() {
const { name, email } = this.props.user
return (
)
}
}
255
Listagem 20 - Implementar a estrutura do navbar (JSX)
my-money-app/frontend/src/common/template/navbar.jsx
<div className="navbar-custom-menu">
<ul className="nav navbar-nav">
<li => this.changeOpen()}
className={`dropdown user user-menu ${this.state.open ? 'open' :
''}`}>
<a href="javascript:;" => this.changeOpen()}
aria-expanded={this.state.open ? 'true' : 'false'}
className="dropdown-toggle"
data-toggle="dropdown">
<img src="http://lorempixel.com/160/160/abstract"
className="user-image" alt="User Image" />
<span className="hidden-xs">{name}</span>
</a>
<ul className="dropdown-menu">
<li className="user-header">
<img src="http://lorempixel.com/160/160/abstract"
className="img-circle" alt="User Image" />
<p>{name}<small>{email}</small></p>
</li>
<li className="user-footer">
<div className="pull-right">
<a href="#" > className="btn btn-default btn-flat">Sair</a>
</div>
</li>
</ul>
</li>
</ul>
</div>
<Navbar />
256
11.4.6. Criar componente inputAuth
.wrapper {
background-color: #fff!important;
}
.login-box-body {
background-color: #eee;
}
.login-box button {
margin-left: 0px;
}
.login-box a:hover {
cursor: pointer;
}
257
11.4.8. Criar componente auth.jsx
import './auth.css'
import React, { Component } from 'react'
import { reduxForm, Field } from 'redux-form'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
changeMode() {
this.setState({ loginMode: !this.state.loginMode })
}
onSubmit(values) {
const { login, signup } = this.props
this.state.loginMode ? login(values) : signup(values)
}
render() {
const { loginMode } = this.state
const { handleSubmit } = this.props
return (
)
}
}
258
Listagem 25 - Implementar a estrutura do auth (JSX)
my-money-app/frontend/src/auth/auth.jsx
<div className="login-box">
<div className="login-logo"><b> My</b> Money</div>
<div className="login-box-body">
<p className="login-box-msg">Bem vindo!</p>
<form => this.onSubmit(v))}>
<Field component={Input} type="input" name="name"
placeholder="Nome" icon='user' hide={loginMode} />
<Field component={Input} type="email" name="email"
placeholder="E-mail" icon='envelope'/>
<Field component={Input} type="password" name="password"
placeholder="Senha" icon='lock' />
<Field component={Input} type="password" name="confirm_password"
placeholder="Confirmar Senha" icon='lock' hide={loginMode} />
<Row>
<Grid cols="4">
<button type="submit"
className="btn btn-primary btn-block btn-flat">
{loginMode ? 'Entrar' : 'Registrar'}
</button>
</Grid>
</Row>
</form>
<br />
<a => this.changeMode()}>
{loginMode ? 'Novo usuário? Registrar aqui!' :
'Já é cadastrado? Entrar aqui!'}
</a>
</div>
<Messages />
</div>
259
Listagem 26 - Criar componente authOrApp.jsx
my-money-app/frontend/src/main/authOrApp.jsx
import '../common/template/dependencies'
import React, { Component } from 'react'
import axios from 'axios'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
componentWillMount() {
if(this.props.auth.user) {
this.props.validateToken(this.props.auth.user.token)
}
}
render() {
const { user, validToken } = this.props.auth
260
Listagem 27 - Remover a seguinte linha do componente app.jsx
my-money-app/frontend/src/main/app.jsx
import '../common/template/dependencies'
261
Appendix A: Tabela de Códigos
• Listagem 1 - Baixando Repositório
262
Glossário
JWT
…
Middleware
…
263