├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── config.js ├── contracts ├── todo.abi └── todo.js ├── dapp ├── components │ ├── app.js │ ├── footer.js │ ├── header.js │ ├── main-section.js │ ├── todo-item.js │ └── todo-text-input.js └── index.js ├── dist └── index.html ├── package-lock.json ├── package.json ├── scripts ├── deploy.js ├── server.js └── webpack.dev.js ├── start_fibos └── node.js └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/env"], ["@babel/preset-react"]], 3 | "plugins": [ 4 | "@babel/plugin-proposal-object-rest-spread", 5 | "transform-class-properties", 6 | "@babel/plugin-transform-arrow-functions" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # fibos blockchain data 64 | start_fibos/fibos_data_dir/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shawn Xie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FIBOS TodoMVC 2 | Todo DApp for FIBOS blockchain by React and fibos.js 3 | 4 | ## Installation 5 | 6 | You need install FIBOS first: 7 | 8 | ``` 9 | curl -s https://fibos.io/download/installer.sh | sh 10 | ``` 11 | 12 | Then check FIBOS version: 13 | 14 | ``` 15 | ~$ which fibos 16 | /usr/local/bin/fibos 17 | 18 | ~$ fibos --version 19 | v0.27.0-dev 20 | ``` 21 | 22 | Install packages: 23 | 24 | ``` 25 | npm install 26 | ``` 27 | 28 | ## Setup and run 29 | 30 | Launch FIBOS local net: 31 | 32 | ``` 33 | npm run start:fibos 34 | ``` 35 | 36 | Deploy the contract to FIBOS local net: 37 | 38 | ``` 39 | npm run deploy:contract 40 | ``` 41 | 42 | Run Todo DApp: 43 | 44 | ``` 45 | npm run start:dapp 46 | ``` -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | client: { 3 | chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f', 4 | httpEndpoint: 'http://127.0.0.1:8888', 5 | keyProvider: process.env.PRIVATE_KEY || '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' 6 | }, 7 | contract: { 8 | name: 'todo', 9 | sender: 'todo' 10 | }, 11 | testContract:{ 12 | name: 'testtodo', 13 | sender: 'testtodo' 14 | }, 15 | account: { 16 | publicKey: process.env.PUBLIC_KEY || 'FO6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV', 17 | privateKey: process.env.PRIVATE_KEY || '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3' 18 | } 19 | } 20 | 21 | module.exports = config -------------------------------------------------------------------------------- /contracts/todo.abi: -------------------------------------------------------------------------------- 1 | { 2 | "version": "eosio::abi/1.0", 3 | "structs": [ 4 | { 5 | "name": "todo_index", 6 | "base": "", 7 | "fields": [ 8 | { 9 | "name": "id", 10 | "type": "int64" 11 | } 12 | ] 13 | }, 14 | { 15 | "name": "todo", 16 | "base" : "", 17 | "fields": [ 18 | { 19 | "name": "id", 20 | "type": "int64" 21 | }, 22 | { 23 | "name": "text", 24 | "type": "string" 25 | }, 26 | { 27 | "name": "completed", 28 | "type": "bool" 29 | } 30 | ] 31 | } 32 | ], 33 | "actions": [ 34 | { 35 | "name": "emplacetodo", 36 | "type": "todo", 37 | "ricardian_contract": "" 38 | }, 39 | { 40 | "name": "findtodo", 41 | "type": "todo_index", 42 | "ricardian_contract": "" 43 | }, 44 | { 45 | "name": "updatetodo", 46 | "type": "todo", 47 | "ricardian_contract": "" 48 | }, 49 | { 50 | "name": "destorytodo", 51 | "type": "todo_index", 52 | "ricardian_contract": "" 53 | } 54 | ], 55 | "tables": [ 56 | { 57 | "name": "todos", 58 | "type": "todo", 59 | "index_type": "i64", 60 | "key_names": ["id"], 61 | "key_types": ["int64"] 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /contracts/todo.js: -------------------------------------------------------------------------------- 1 | // create 似乎是个关键词 2 | exports.emplacetodo = (id, text, completed) => { 3 | var todos = db.todos(action.account, action.account); 4 | todos.emplace(action.account, { 5 | text, 6 | completed, 7 | id 8 | }); 9 | console.log('todo#', id, ' created'); 10 | } 11 | exports.findtodo = (id) => { 12 | var todos = db.todos(action.account, action.account); 13 | console.log(todos.find(id)) 14 | }; 15 | exports.updatetodo = (id, text, completed) => { 16 | var todos = db.todos(action.account, action.account); 17 | var itr = todos.find(id); 18 | itr.data.text = text; 19 | itr.data.completed = completed; 20 | itr.update(action.account); 21 | console.log('todos#', id, ' updated'); 22 | } 23 | exports.destorytodo = (id) => { 24 | var todos = db.todos(action.account, action.account); 25 | var itr = todos.find(id); 26 | itr.remove(); 27 | console.log('todos#', id, ' removed'); 28 | } -------------------------------------------------------------------------------- /dapp/components/app.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import Header from './header' 3 | import MainSection from './main-section' 4 | // import EOS from 'eosjs' 5 | import FIBOS from 'fibos.js' 6 | import config from '../../config' 7 | 8 | // const eosClient = EOS(config.clientConfig) 9 | const fibosClient = FIBOS(config.client) 10 | 11 | const initialState = [ 12 | { 13 | text: 'React ES6 TodoMVC', 14 | completed: 0, 15 | id: 0 16 | } 17 | ] 18 | 19 | class App extends Component { 20 | constructor(props) { 21 | super(props) 22 | this.state = { 23 | todos: [] || initialState, 24 | gameID: null, 25 | player: 0 26 | } 27 | this.fetchTodo() 28 | } 29 | 30 | fetchTodo = () => { 31 | fibosClient.getTableRows( 32 | true, 33 | config.contract.sender, 34 | config.contract.name, 35 | 'todos').then((data)=>{ 36 | this.setState({todos: data.rows}) 37 | }).catch((e) => { 38 | console.error(e) 39 | } 40 | ) 41 | } 42 | 43 | addTodo = (text) => { 44 | const todo_id = this.state.todos.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1 45 | fibosClient.contract(config.contract.name).then((contract) => { 46 | contract.emplacetodo( 47 | { 48 | id: todo_id, 49 | text, 50 | completed: 0 51 | }, 52 | { authorization: [config.contract.sender] } 53 | ).then((res) => { 54 | const todos = [ 55 | { 56 | id: todo_id, 57 | completed: 0, 58 | text: text 59 | }, 60 | ...this.state.todos 61 | ] 62 | this.setState({todos}) 63 | } 64 | ) 65 | .catch((err) => { console.log(err) }) 66 | }) 67 | } 68 | 69 | deleteTodo = (id) => { 70 | fibosClient.contract(config.contract.name).then((contract) => { 71 | contract.destorytodo( 72 | { 73 | id 74 | }, 75 | { authorization: [config.contract.sender]} 76 | ).then((res) => { 77 | const todos = this.state.todos.filter(todo => todo.id !== id) 78 | this.setState({todos}) 79 | }) 80 | .catch((err) => { 81 | console.log(err) 82 | }) 83 | }) 84 | } 85 | 86 | editTodo = (id, text) => { 87 | const todo_item = this.state.todos.find((el) => el.id === id) 88 | if (!todo_item){ 89 | return 90 | } 91 | fibosClient.contract(config.contract.name).then((contract) => { 92 | contract.updatetodo( 93 | { 94 | id: id, 95 | text, 96 | completed: todo_item.completed 97 | }, 98 | { authorization: [config.contract.sender] } 99 | ).then((res) => { 100 | const todos = this.state.todos.map(todo => 101 | todo.id === id ? {...todo, text} : todo 102 | ) 103 | this.setState({todos}) 104 | } 105 | ) 106 | .catch((err) => { console.log(err) }) 107 | }) 108 | } 109 | 110 | completeTodo = (id) => { 111 | const todo_item = this.state.todos.find((el) => el.id === id) 112 | if (!todo_item){ 113 | return 114 | } 115 | fibosClient.contract(config.contract.name).then((contract) => { 116 | contract.updatetodo( 117 | { 118 | id: id, 119 | text: todo_item.text, 120 | completed: todo_item.completed === 1?0:1 121 | }, 122 | { authorization: [config.contract.sender] } 123 | ).then((res) => { 124 | const todos = this.state.todos.map(todo => 125 | todo.id === id ? {...todo, completed: !todo.completed} : todo 126 | ) 127 | this.setState({todos}) 128 | } 129 | ) 130 | .catch((err) => { console.log(err) }) 131 | }) 132 | } 133 | 134 | clearCompleted = () => { 135 | const todos = this.state.todos.filter(todo => todo.completed === 0) 136 | this.setState({todos}) 137 | } 138 | 139 | actions = { 140 | addTodo: this.addTodo, 141 | deleteTodo: this.deleteTodo, 142 | editTodo: this.editTodo, 143 | completeTodo: this.completeTodo, 144 | clearCompleted: this.clearCompleted 145 | } 146 | 147 | render() { 148 | return( 149 |
150 |
151 | 152 |
153 | ) 154 | } 155 | } 156 | 157 | export default App -------------------------------------------------------------------------------- /dapp/components/footer.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import classnames from 'classnames' 4 | 5 | const FILTER_TITLES = { 6 | SHOW_ALL: 'All', 7 | SHOW_ACTIVE: 'Active', 8 | SHOW_COMPLETED: 'Completed' 9 | } 10 | 11 | export default class Footer extends Component { 12 | static propTypes = { 13 | completedCount: PropTypes.number.isRequired, 14 | activeCount: PropTypes.number.isRequired, 15 | filter: PropTypes.string.isRequired, 16 | onClearCompleted: PropTypes.func.isRequired, 17 | onShow: PropTypes.func.isRequired 18 | } 19 | 20 | renderTodoCount() { 21 | const {activeCount} = this.props 22 | 23 | const itemWord = activeCount === 1 ? 'item' : 'items' 24 | 25 | return ( 26 | 27 | {activeCount || 'No'} 28 | {itemWord} left 29 | 30 | ) 31 | } 32 | 33 | renderFilterLink(filter) { 34 | const title = FILTER_TITLES[filter] 35 | const {filter: selectedFilter, onShow} = this.props 36 | 37 | return ( 38 | onShow(filter)}> 42 | {title} 43 | 44 | ) 45 | } 46 | 47 | renderClearButton() { 48 | const {completedCount, onClearCompleted} = this.props 49 | if (completedCount > 0) { 50 | return ( 51 | 54 | ) 55 | } 56 | } 57 | 58 | renderFilterList() { 59 | return ['SHOW_ALL', 'SHOW_ACTIVE', 'SHOW_COMPLETED'] 60 | .map(filter => 61 |
  • 62 | {this.renderFilterLink(filter)} 63 |
  • 64 | ) 65 | } 66 | 67 | render() { 68 | return ( 69 | 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /dapp/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types'; 3 | import TodoTextInput from './todo-text-input' 4 | 5 | const Header = ({addTodo}) => { 6 | 7 | const handleSave = text => { 8 | if (text.length !== 0) { 9 | addTodo(text) 10 | } 11 | } 12 | 13 | return ( 14 |
    15 |

    todos

    16 | 21 |
    22 | ) 23 | } 24 | 25 | Header.propTypes = { 26 | addTodo: PropTypes.func.isRequired 27 | } 28 | 29 | export default Header 30 | -------------------------------------------------------------------------------- /dapp/components/main-section.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types'; 3 | import TodoItem from './todo-item' 4 | import Footer from './footer' 5 | 6 | const TODO_FILTERS = { 7 | SHOW_ALL: () => true, 8 | SHOW_ACTIVE: todo => !todo.completed, 9 | SHOW_COMPLETED: todo => todo.completed 10 | } 11 | 12 | export default class MainSection extends Component { 13 | static propTypes = { 14 | todos: PropTypes.array.isRequired, 15 | actions: PropTypes.object.isRequired 16 | } 17 | 18 | state = { filter: 'SHOW_ALL' } 19 | 20 | handleClearCompleted = () => { 21 | this.props.actions.clearCompleted() 22 | } 23 | 24 | handleShow = filter => { 25 | this.setState({ filter }) 26 | } 27 | 28 | // renderToggleAll(completedCount) { 29 | // const { todos, actions } = this.props 30 | // if (todos.length > 0) { 31 | // return ( 32 | // 38 | // ) 39 | // } 40 | // } 41 | 42 | renderFooter(completedCount) { 43 | const { todos } = this.props 44 | const { filter } = this.state 45 | const activeCount = todos.length - completedCount 46 | 47 | if (todos.length) { 48 | return ( 49 |