├── .gitattributes ├── box-img-lg.png ├── box-img-sm.png ├── public ├── favicon.ico └── index.html ├── src ├── index.css ├── fonts │ ├── Oswald-300 │ │ ├── Oswald-300.eot │ │ ├── Oswald-300.ttf │ │ ├── Oswald-300.woff │ │ ├── Oswald-300.woff2 │ │ ├── LICENSE.txt │ │ └── Oswald-300.svg │ ├── Oswald-regular │ │ ├── Oswald-regular.eot │ │ ├── Oswald-regular.ttf │ │ ├── Oswald-regular.woff │ │ ├── Oswald-regular.woff2 │ │ └── LICENSE.txt │ └── Open-Sans-regular │ │ ├── Open-Sans-regular.eot │ │ ├── Open-Sans-regular.ttf │ │ ├── Open-Sans-regular.woff │ │ ├── Open-Sans-regular.woff2 │ │ └── LICENSE.txt ├── App.test.js ├── user │ ├── ui │ │ ├── loginbutton │ │ │ ├── LoginButton.js │ │ │ ├── LoginButtonContainer.js │ │ │ └── LoginButtonActions.js │ │ ├── logoutbutton │ │ │ ├── LogoutButton.js │ │ │ ├── LogoutButtonActions.js │ │ │ └── LogoutButtonContainer.js │ │ ├── signupform │ │ │ ├── SignUpFormContainer.js │ │ │ ├── SignUpForm.js │ │ │ └── SignUpFormActions.js │ │ └── profileform │ │ │ ├── ProfileFormContainer.js │ │ │ ├── ProfileForm.js │ │ │ └── ProfileFormActions.js │ ├── userReducer.js │ └── layouts │ │ ├── profile │ │ └── Profile.js │ │ └── signup │ │ └── SignUp.js ├── reducer.js ├── util │ ├── web3 │ │ ├── web3Reducer.js │ │ └── getWeb3.js │ └── wrappers.js ├── store.js ├── layouts │ ├── dashboard │ │ └── Dashboard.js │ └── home │ │ └── Home.js ├── css │ ├── open-sans.css │ ├── oswald.css │ └── pure-min.css ├── App.css ├── index.js └── App.js ├── truffle.js ├── truffle-config.js ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── .gitignore ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── env.js ├── paths.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── contracts ├── zeppelin │ ├── lifecycle │ │ └── Killable.sol │ └── ownership │ │ └── Ownable.sol ├── Migrations.sol └── Authentication.sol ├── truffle-box.json ├── test ├── TestAuthentication.sol └── authentication.js ├── scripts ├── test.js ├── build.js └── start.js ├── LICENSE ├── README.md └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /box-img-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/box-img-lg.png -------------------------------------------------------------------------------- /box-img-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/box-img-sm.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /src/fonts/Oswald-300/Oswald-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-300/Oswald-300.eot -------------------------------------------------------------------------------- /src/fonts/Oswald-300/Oswald-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-300/Oswald-300.ttf -------------------------------------------------------------------------------- /src/fonts/Oswald-300/Oswald-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-300/Oswald-300.woff -------------------------------------------------------------------------------- /src/fonts/Oswald-300/Oswald-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-300/Oswald-300.woff2 -------------------------------------------------------------------------------- /src/fonts/Oswald-regular/Oswald-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-regular/Oswald-regular.eot -------------------------------------------------------------------------------- /src/fonts/Oswald-regular/Oswald-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-regular/Oswald-regular.ttf -------------------------------------------------------------------------------- /src/fonts/Oswald-regular/Oswald-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-regular/Oswald-regular.woff -------------------------------------------------------------------------------- /src/fonts/Oswald-regular/Oswald-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Oswald-regular/Oswald-regular.woff2 -------------------------------------------------------------------------------- /src/fonts/Open-Sans-regular/Open-Sans-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Open-Sans-regular/Open-Sans-regular.eot -------------------------------------------------------------------------------- /src/fonts/Open-Sans-regular/Open-Sans-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Open-Sans-regular/Open-Sans-regular.ttf -------------------------------------------------------------------------------- /src/fonts/Open-Sans-regular/Open-Sans-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Open-Sans-regular/Open-Sans-regular.woff -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 5 | -------------------------------------------------------------------------------- /src/fonts/Open-Sans-regular/Open-Sans-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truffle-box/react-auth-box/HEAD/src/fonts/Open-Sans-regular/Open-Sans-regular.woff2 -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 5 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | }) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | build_webpack 12 | 13 | # misc 14 | .DS_Store 15 | .env 16 | npm-debug.log 17 | .truffle-solidity-loader 18 | -------------------------------------------------------------------------------- /src/user/ui/loginbutton/LoginButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LoginButton = ({ onLoginUserClick }) => { 4 | return( 5 |
  • 6 | onLoginUserClick(event)}>Login 7 |
  • 8 | ) 9 | } 10 | 11 | export default LoginButton 12 | -------------------------------------------------------------------------------- /src/user/ui/logoutbutton/LogoutButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LogoutButton = ({ onLogoutUserClick }) => { 4 | return( 5 |
  • 6 | onLogoutUserClick(event)}>Logout 7 |
  • 8 | ) 9 | } 10 | 11 | export default LogoutButton 12 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process(src, filename) { 8 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /contracts/zeppelin/lifecycle/Killable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | 4 | import "./../ownership/Ownable.sol"; 5 | 6 | 7 | /* 8 | * Killable 9 | * Base contract that can be killed by owner. All funds in contract will be sent to the owner. 10 | */ 11 | contract Killable is Ownable { 12 | function kill() onlyOwner { 13 | selfdestruct(owner); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { routerReducer } from 'react-router-redux' 3 | import userReducer from './user/userReducer' 4 | import web3Reducer from './util/web3/web3Reducer' 5 | 6 | const reducer = combineReducers({ 7 | routing: routerReducer, 8 | user: userReducer, 9 | web3: web3Reducer 10 | }) 11 | 12 | export default reducer 13 | -------------------------------------------------------------------------------- /src/util/web3/web3Reducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | web3Instance: null 3 | } 4 | 5 | const web3Reducer = (state = initialState, action) => { 6 | if (action.type === 'WEB3_INITIALIZED') 7 | { 8 | return Object.assign({}, state, { 9 | web3Instance: action.payload.web3Instance 10 | }) 11 | } 12 | 13 | return state 14 | } 15 | 16 | export default web3Reducer 17 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return 'module.exports = {};'; 7 | }, 8 | getCacheKey(fileData, filename) { 9 | // The output is always the same. 10 | return 'cssTransform'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /truffle-box.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "README.md", 4 | ".gitignore" 5 | ], 6 | "commands": { 7 | "Compile": "truffle compile", 8 | "Migrate": "truffle migrate", 9 | "Test contracts": "truffle test", 10 | "Test dapp": "npm test", 11 | "Run dev server": "npm run start", 12 | "Build for production": "npm run build" 13 | }, 14 | "hooks": { 15 | "post-unpack": "npm install" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var Ownable = artifacts.require("./zeppelin/ownership/Ownable.sol"); 2 | var Killable = artifacts.require("./zeppelin/lifecycle/Killable.sol"); 3 | var Authentication = artifacts.require("./Authentication.sol"); 4 | 5 | module.exports = function(deployer) { 6 | deployer.deploy(Ownable); 7 | deployer.link(Ownable, Killable); 8 | deployer.deploy(Killable); 9 | deployer.link(Killable, Authentication); 10 | deployer.deploy(Authentication); 11 | }; 12 | -------------------------------------------------------------------------------- /src/user/ui/logoutbutton/LogoutButtonActions.js: -------------------------------------------------------------------------------- 1 | import { browserHistory } from 'react-router' 2 | 3 | export const USER_LOGGED_OUT = 'USER_LOGGED_OUT' 4 | function userLoggedOut(user) { 5 | return { 6 | type: USER_LOGGED_OUT, 7 | payload: user 8 | } 9 | } 10 | 11 | export function logoutUser() { 12 | return function(dispatch) { 13 | // Logout user. 14 | dispatch(userLoggedOut()) 15 | 16 | // Redirect home. 17 | return browserHistory.push('/') 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/user/userReducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | data: null 3 | } 4 | 5 | const userReducer = (state = initialState, action) => { 6 | if (action.type === 'USER_LOGGED_IN' || action.type === 'USER_UPDATED') 7 | { 8 | return Object.assign({}, state, { 9 | data: action.payload 10 | }) 11 | } 12 | 13 | if (action.type === 'USER_LOGGED_OUT') 14 | { 15 | return Object.assign({}, state, { 16 | data: null 17 | }) 18 | } 19 | 20 | return state 21 | } 22 | 23 | export default userReducer 24 | -------------------------------------------------------------------------------- /test/TestAuthentication.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | import "truffle/Assert.sol"; 4 | import "truffle/DeployedAddresses.sol"; 5 | import "../contracts/Authentication.sol"; 6 | 7 | contract TestAuthentication { 8 | 9 | function testUserCanSignUpAndLogin() { 10 | Authentication authentication = Authentication(DeployedAddresses.Authentication()); 11 | 12 | authentication.signup('testuser'); 13 | 14 | bytes32 expected = 'testuser'; 15 | 16 | Assert.equal(authentication.login(), expected, "It should sign up and log in a user."); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/user/ui/signupform/SignUpFormContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import SignUpForm from './SignUpForm' 3 | import { signUpUser } from './SignUpFormActions' 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return {} 7 | } 8 | 9 | const mapDispatchToProps = (dispatch) => { 10 | return { 11 | onSignUpFormSubmit: (name) => { 12 | dispatch(signUpUser(name)) 13 | } 14 | } 15 | } 16 | 17 | const SignUpFormContainer = connect( 18 | mapStateToProps, 19 | mapDispatchToProps 20 | )(SignUpForm) 21 | 22 | export default SignUpFormContainer 23 | -------------------------------------------------------------------------------- /src/user/layouts/profile/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ProfileFormContainer from '../../ui/profileform/ProfileFormContainer' 3 | 4 | class Profile extends Component { 5 | render() { 6 | return( 7 |
    8 |
    9 |
    10 |

    Profile

    11 |

    Edit your account details here.

    12 | 13 |
    14 |
    15 |
    16 | ) 17 | } 18 | } 19 | 20 | export default Profile 21 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/zeppelin/ownership/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | 4 | /* 5 | * Ownable 6 | * 7 | * Base contract with an owner. 8 | * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner. 9 | */ 10 | contract Ownable { 11 | address public owner; 12 | 13 | function Ownable() { 14 | owner = msg.sender; 15 | } 16 | 17 | modifier onlyOwner() { 18 | if (msg.sender == owner) 19 | _; 20 | } 21 | 22 | function transferOwnership(address newOwner) onlyOwner { 23 | if (newOwner != address(0)) owner = newOwner; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/user/ui/loginbutton/LoginButtonContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import LoginButton from './LoginButton' 3 | import { loginUser } from './LoginButtonActions' 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return {} 7 | } 8 | 9 | const mapDispatchToProps = (dispatch) => { 10 | return { 11 | onLoginUserClick: (event) => { 12 | event.preventDefault(); 13 | 14 | dispatch(loginUser()) 15 | } 16 | } 17 | } 18 | 19 | const LoginButtonContainer = connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(LoginButton) 23 | 24 | export default LoginButtonContainer 25 | -------------------------------------------------------------------------------- /src/user/ui/logoutbutton/LogoutButtonContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import LogoutButton from './LogoutButton' 3 | import { logoutUser } from './LogoutButtonActions' 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return {} 7 | } 8 | 9 | const mapDispatchToProps = (dispatch) => { 10 | return { 11 | onLogoutUserClick: (event) => { 12 | event.preventDefault(); 13 | 14 | dispatch(logoutUser()) 15 | } 16 | } 17 | } 18 | 19 | const LogoutButtonContainer = connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(LogoutButton) 23 | 24 | export default LogoutButtonContainer 25 | -------------------------------------------------------------------------------- /src/user/layouts/signup/SignUp.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import SignUpFormContainer from '../../ui/signupform/SignUpFormContainer' 3 | 4 | class SignUp extends Component { 5 | render() { 6 | return( 7 |
    8 |
    9 |
    10 |

    Sign Up

    11 |

    We've got your wallet information, simply input your name and your account is made!

    12 | 13 |
    14 |
    15 |
    16 | ) 17 | } 18 | } 19 | 20 | export default SignUp 21 | -------------------------------------------------------------------------------- /test/authentication.js: -------------------------------------------------------------------------------- 1 | var Authentication = artifacts.require("./Authentication.sol"); 2 | 3 | contract('Authentication', function(accounts) { 4 | 5 | it("...should sign up and log in a user.", function() { 6 | return Authentication.deployed().then(function(instance) { 7 | authenticationInstance = instance; 8 | 9 | return authenticationInstance.signup('testuser', {from: accounts[0]}); 10 | }).then(function() { 11 | return authenticationInstance.login.call(); 12 | }).then(function(userName) { 13 | assert.equal(web3.toUtf8(userName), 'testuser', "The user was not signed up."); 14 | }); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { browserHistory } from 'react-router' 2 | import { createStore, applyMiddleware, compose } from 'redux' 3 | import thunkMiddleware from 'redux-thunk' 4 | import { routerMiddleware } from 'react-router-redux' 5 | import reducer from './reducer' 6 | 7 | // Redux DevTools 8 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 9 | 10 | const routingMiddleware = routerMiddleware(browserHistory) 11 | 12 | const store = createStore( 13 | reducer, 14 | composeEnhancers( 15 | applyMiddleware( 16 | thunkMiddleware, 17 | routingMiddleware 18 | ) 19 | ) 20 | ) 21 | 22 | export default store 23 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise === 'undefined') { 2 | // Rejection tracking prevents a common issue where React gets into an 3 | // inconsistent state due to an error, but it gets swallowed by a Promise, 4 | // and the user has no idea what causes React's erratic future behavior. 5 | require('promise/lib/rejection-tracking').enable(); 6 | window.Promise = require('promise/lib/es6-extensions.js'); 7 | } 8 | 9 | // fetch() polyfill for making API calls. 10 | require('whatwg-fetch'); 11 | 12 | // Object.assign() is commonly used with React. 13 | // It will use the native implementation if it's present and isn't buggy. 14 | Object.assign = require('object-assign'); 15 | -------------------------------------------------------------------------------- /src/user/ui/profileform/ProfileFormContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import ProfileForm from './ProfileForm' 3 | import { updateUser } from './ProfileFormActions' 4 | 5 | const mapStateToProps = (state, ownProps) => { 6 | return { 7 | name: state.user.data.name 8 | } 9 | } 10 | 11 | const mapDispatchToProps = (dispatch) => { 12 | return { 13 | onProfileFormSubmit: (name) => { 14 | event.preventDefault(); 15 | 16 | dispatch(updateUser(name)) 17 | } 18 | } 19 | } 20 | 21 | const ProfileFormContainer = connect( 22 | mapStateToProps, 23 | mapDispatchToProps 24 | )(ProfileForm) 25 | 26 | export default ProfileFormContainer 27 | -------------------------------------------------------------------------------- /src/layouts/dashboard/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class Dashboard extends Component { 4 | constructor(props, { authData }) { 5 | super(props) 6 | authData = this.props 7 | } 8 | 9 | render() { 10 | return( 11 |
    12 |
    13 |
    14 |

    Dashboard

    15 |

    Congratulations {this.props.authData.name}! If you're seeing this page, you've logged in with your own smart contract successfully.

    16 |
    17 |
    18 |
    19 | ) 20 | } 21 | } 22 | 23 | export default Dashboard 24 | -------------------------------------------------------------------------------- /src/css/open-sans.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-weight: 400; 4 | font-style: normal; 5 | src: url('../fonts/Open-Sans-regular/Open-Sans-regular.eot'); 6 | src: url('../fonts/Open-Sans-regular/Open-Sans-regular.eot?#iefix') format('embedded-opentype'), 7 | local('Open Sans'), 8 | local('Open-Sans-regular'), 9 | url('../fonts/Open-Sans-regular/Open-Sans-regular.woff2') format('woff2'), 10 | url('../fonts/Open-Sans-regular/Open-Sans-regular.woff') format('woff'), 11 | url('../fonts/Open-Sans-regular/Open-Sans-regular.ttf') format('truetype'), 12 | url('../fonts/Open-Sans-regular/Open-Sans-regular.svg#OpenSans') format('svg'); 13 | } 14 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | process.env.PUBLIC_URL = ''; 3 | 4 | // Load environment variables from .env file. Suppress warnings using silent 5 | // if this file is missing. dotenv will never modify any environment variables 6 | // that have already been set. 7 | // https://github.com/motdotla/dotenv 8 | require('dotenv').config({silent: true}); 9 | 10 | const jest = require('jest'); 11 | const argv = process.argv.slice(2); 12 | 13 | // Watch unless on CI or in coverage mode 14 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 15 | argv.push('--watch'); 16 | } 17 | 18 | // A temporary hack to clear terminal correctly. 19 | // You can remove this after updating to Jest 18 when it's out. 20 | // https://github.com/facebook/jest/pull/2230 21 | var realWrite = process.stdout.write; 22 | var CLEAR = process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'; 23 | process.stdout.write = function(chunk, encoding, callback) { 24 | if (chunk === '\x1B[2J\x1B[H') { 25 | chunk = CLEAR; 26 | } 27 | return realWrite.call(this, chunk, encoding, callback); 28 | }; 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Truffle 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 | 23 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | 4 | var REACT_APP = /^REACT_APP_/i; 5 | 6 | function getClientEnvironment(publicUrl) { 7 | var processEnv = Object 8 | .keys(process.env) 9 | .filter(key => REACT_APP.test(key)) 10 | .reduce((env, key) => { 11 | env[key] = JSON.stringify(process.env[key]); 12 | return env; 13 | }, { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | 'NODE_ENV': JSON.stringify( 17 | process.env.NODE_ENV || 'development' 18 | ), 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': JSON.stringify(publicUrl) 24 | }); 25 | return {'process.env': processEnv}; 26 | } 27 | 28 | module.exports = getClientEnvironment; 29 | -------------------------------------------------------------------------------- /src/user/ui/signupform/SignUpForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class SignUpForm extends Component { 4 | constructor(props) { 5 | super(props) 6 | 7 | this.state = { 8 | name: '' 9 | } 10 | } 11 | 12 | onInputChange(event) { 13 | this.setState({ name: event.target.value }) 14 | } 15 | 16 | handleSubmit(event) { 17 | event.preventDefault() 18 | 19 | if (this.state.name.length < 2) 20 | { 21 | return alert('Please fill in your name.') 22 | } 23 | 24 | this.props.onSignUpFormSubmit(this.state.name) 25 | } 26 | 27 | render() { 28 | return( 29 |
    30 |
    31 | 32 | 33 | This is a required field. 34 | 35 |
    36 | 37 | 38 |
    39 |
    40 | ) 41 | } 42 | } 43 | 44 | export default SignUpForm 45 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | /* PAGE */ 2 | 3 | body, 4 | .pure-g [class*=pure-u] { 5 | font-family: 'Open Sans', sans-serif; 6 | } 7 | 8 | h1, h2, h3 { 9 | font-family: 'Oswald', 'Arial Narrow', sans-serif; 10 | } 11 | 12 | code { 13 | display: block; 14 | margin: 20px 0 15px 0; 15 | padding: 10px; 16 | background: #eee; 17 | } 18 | 19 | .container { 20 | box-sizing: border-box; 21 | width: 100%; 22 | padding: 45px 20px; 23 | } 24 | 25 | .pure-button-primary { 26 | background-color: #0c1a2b; 27 | } 28 | 29 | .pure-button-primary:hover { 30 | background-color: #233e5e; 31 | } 32 | 33 | .pure-form input[type="text"]:focus { 34 | border-color: #0c1a2b; 35 | } 36 | 37 | /* NAVBAR */ 38 | 39 | .navbar { 40 | position: fixed; 41 | padding: 5px; 42 | background: #0c1a2b; 43 | font-family: 'Oswald', 'Arial Narrow', sans-serif; 44 | } 45 | 46 | .navbar a { 47 | color: #fff; 48 | } 49 | 50 | .navbar a:active, 51 | .navbar a:focus, 52 | .navbar a:hover { 53 | background: #233e5e; 54 | } 55 | 56 | .navbar .pure-menu-heading { 57 | font-weight: bold; 58 | text-transform: none; 59 | } 60 | 61 | .navbar .navbar-right { 62 | float: right; 63 | } 64 | 65 | .navbar .uport-logo { 66 | height: 16px; 67 | margin-right: 10px; 68 | } 69 | -------------------------------------------------------------------------------- /src/user/ui/profileform/ProfileForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class ProfileForm extends Component { 4 | constructor(props) { 5 | super(props) 6 | 7 | this.state = { 8 | name: this.props.name 9 | } 10 | } 11 | 12 | onInputChange(event) { 13 | this.setState({ name: event.target.value }) 14 | } 15 | 16 | handleSubmit(event) { 17 | event.preventDefault() 18 | 19 | if (this.state.name.length < 2) 20 | { 21 | return alert('Please fill in your name.') 22 | } 23 | 24 | this.props.onProfileFormSubmit(this.state.name) 25 | } 26 | 27 | render() { 28 | return( 29 |
    30 |
    31 | 32 | 33 | This is a required field. 34 | 35 |
    36 | 37 | 38 |
    39 |
    40 | ) 41 | } 42 | } 43 | 44 | export default ProfileForm 45 | -------------------------------------------------------------------------------- /src/css/oswald.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Oswald'; 3 | font-weight: 300; 4 | font-style: normal; 5 | src: url('../fonts/Oswald-300/Oswald-300.eot'); 6 | src: url('../fonts/Oswald-300/Oswald-300.eot?#iefix') format('embedded-opentype'), 7 | local('Oswald Light'), 8 | local('Oswald-300'), 9 | url('../fonts/Oswald-300/Oswald-300.woff2') format('woff2'), 10 | url('../fonts/Oswald-300/Oswald-300.woff') format('woff'), 11 | url('../fonts/Oswald-300/Oswald-300.ttf') format('truetype'), 12 | url('../fonts/Oswald-300/Oswald-300.svg#Oswald') format('svg'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'Oswald'; 17 | font-weight: 400; 18 | font-style: normal; 19 | src: url('../fonts/Oswald-regular/Oswald-regular.eot'); 20 | src: url('../fonts/Oswald-regular/Oswald-regular.eot?#iefix') format('embedded-opentype'), 21 | local('Oswald Regular'), 22 | local('Oswald-regular'), 23 | url('../fonts/Oswald-regular/Oswald-regular.woff2') format('woff2'), 24 | url('../fonts/Oswald-regular/Oswald-regular.woff') format('woff'), 25 | url('../fonts/Oswald-regular/Oswald-regular.ttf') format('truetype'), 26 | url('../fonts/Oswald-regular/Oswald-regular.svg#Oswald') format('svg'); 27 | } 28 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | Truffle Box 17 | 18 | 19 |
    20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/util/wrappers.js: -------------------------------------------------------------------------------- 1 | import { UserAuthWrapper } from 'redux-auth-wrapper' 2 | import { routerActions } from 'react-router-redux' 3 | 4 | // Layout Component Wrappers 5 | 6 | export const UserIsAuthenticated = UserAuthWrapper({ 7 | authSelector: state => state.user.data, 8 | redirectAction: routerActions.replace, 9 | failureRedirectPath: '/', // '/login' by default. 10 | wrapperDisplayName: 'UserIsAuthenticated' 11 | }) 12 | 13 | export const UserIsNotAuthenticated = UserAuthWrapper({ 14 | authSelector: state => state.user, 15 | redirectAction: routerActions.replace, 16 | failureRedirectPath: (state, ownProps) => ownProps.location.query.redirect || '/dashboard', 17 | wrapperDisplayName: 'UserIsNotAuthenticated', 18 | predicate: user => user.data === null, 19 | allowRedirectBack: false 20 | }) 21 | 22 | // UI Component Wrappers 23 | 24 | export const VisibleOnlyAuth = UserAuthWrapper({ 25 | authSelector: state => state.user, 26 | wrapperDisplayName: 'VisibleOnlyAuth', 27 | predicate: user => user.data, 28 | FailureComponent: null 29 | }) 30 | 31 | export const HiddenOnlyAuth = UserAuthWrapper({ 32 | authSelector: state => state.user, 33 | wrapperDisplayName: 'HiddenOnlyAuth', 34 | predicate: user => user.data === null, 35 | FailureComponent: null 36 | }) 37 | -------------------------------------------------------------------------------- /src/util/web3/getWeb3.js: -------------------------------------------------------------------------------- 1 | import store from '../../store' 2 | import Web3 from 'web3' 3 | 4 | export const WEB3_INITIALIZED = 'WEB3_INITIALIZED' 5 | function web3Initialized(results) { 6 | return { 7 | type: WEB3_INITIALIZED, 8 | payload: results 9 | } 10 | } 11 | 12 | let getWeb3 = new Promise(function(resolve, reject) { 13 | // Wait for loading completion to avoid race conditions with web3 injection timing. 14 | window.addEventListener('load', function(dispatch) { 15 | var results 16 | var web3 = window.web3 17 | 18 | // Checking if Web3 has been injected by the browser (Mist/MetaMask) 19 | if (typeof web3 !== 'undefined') { 20 | // Use Mist/MetaMask's provider. 21 | web3 = new Web3(web3.currentProvider) 22 | 23 | results = { 24 | web3Instance: web3 25 | } 26 | 27 | console.log('Injected web3 detected.'); 28 | 29 | resolve(store.dispatch(web3Initialized(results))) 30 | } else { 31 | 32 | // Fallback to localhost if no web3 injection. We've configured this to 33 | // use the development console's port by default. 34 | var provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545') 35 | 36 | web3 = new Web3(provider) 37 | 38 | results = { 39 | web3Instance: web3 40 | } 41 | 42 | console.log('No web3 instance injected, using Local web3.'); 43 | 44 | resolve(store.dispatch(web3Initialized(results))) 45 | } 46 | }) 47 | }) 48 | 49 | export default getWeb3 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Router, Route, IndexRoute, browserHistory } from 'react-router' 4 | import { Provider } from 'react-redux' 5 | import { syncHistoryWithStore } from 'react-router-redux' 6 | import { UserIsAuthenticated, UserIsNotAuthenticated } from './util/wrappers.js' 7 | import getWeb3 from './util/web3/getWeb3' 8 | 9 | // Layouts 10 | import App from './App' 11 | import Home from './layouts/home/Home' 12 | import Dashboard from './layouts/dashboard/Dashboard' 13 | import SignUp from './user/layouts/signup/SignUp' 14 | import Profile from './user/layouts/profile/Profile' 15 | 16 | // Redux Store 17 | import store from './store' 18 | 19 | // Initialize react-router-redux. 20 | const history = syncHistoryWithStore(browserHistory, store) 21 | 22 | // Initialize web3 and set in Redux. 23 | getWeb3 24 | .then(results => { 25 | console.log('Web3 initialized!') 26 | }) 27 | .catch(() => { 28 | console.log('Error in web3 initialization.') 29 | }) 30 | 31 | ReactDOM.render(( 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ), 43 | document.getElementById('root') 44 | ) 45 | -------------------------------------------------------------------------------- /contracts/Authentication.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | import './zeppelin/lifecycle/Killable.sol'; 4 | 5 | contract Authentication is Killable { 6 | struct User { 7 | bytes32 name; 8 | } 9 | 10 | mapping (address => User) private users; 11 | 12 | uint private id; // Stores user id temporarily 13 | 14 | modifier onlyExistingUser { 15 | // Check if user exists or terminate 16 | 17 | require(!(users[msg.sender].name == 0x0)); 18 | _; 19 | } 20 | 21 | modifier onlyValidName(bytes32 name) { 22 | // Only valid names allowed 23 | 24 | require(!(name == 0x0)); 25 | _; 26 | } 27 | 28 | function login() constant 29 | public 30 | onlyExistingUser 31 | returns (bytes32) { 32 | return (users[msg.sender].name); 33 | } 34 | 35 | function signup(bytes32 name) 36 | public 37 | payable 38 | onlyValidName(name) 39 | returns (bytes32) { 40 | // Check if user exists. 41 | // If yes, return user name. 42 | // If no, check if name was sent. 43 | // If yes, create and return user. 44 | 45 | if (users[msg.sender].name == 0x0) 46 | { 47 | users[msg.sender].name = name; 48 | 49 | return (users[msg.sender].name); 50 | } 51 | 52 | return (users[msg.sender].name); 53 | } 54 | 55 | function update(bytes32 name) 56 | public 57 | payable 58 | onlyValidName(name) 59 | onlyExistingUser 60 | returns (bytes32) { 61 | // Update user name. 62 | 63 | if (users[msg.sender].name != 0x0) 64 | { 65 | users[msg.sender].name = name; 66 | 67 | return (users[msg.sender].name); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/user/ui/signupform/SignUpFormActions.js: -------------------------------------------------------------------------------- 1 | import AuthenticationContract from '../../../../build/contracts/Authentication.json' 2 | import { loginUser } from '../loginbutton/LoginButtonActions' 3 | import store from '../../../store' 4 | 5 | const contract = require('truffle-contract') 6 | 7 | export function signUpUser(name) { 8 | let web3 = store.getState().web3.web3Instance 9 | 10 | // Double-check web3's status. 11 | if (typeof web3 !== 'undefined') { 12 | 13 | return function(dispatch) { 14 | // Using truffle-contract we create the authentication object. 15 | const authentication = contract(AuthenticationContract) 16 | authentication.setProvider(web3.currentProvider) 17 | 18 | // Declaring this for later so we can chain functions on Authentication. 19 | var authenticationInstance 20 | 21 | // Get current ethereum wallet. 22 | web3.eth.getCoinbase((error, coinbase) => { 23 | // Log errors, if any. 24 | if (error) { 25 | console.error(error); 26 | } 27 | 28 | authentication.deployed().then(function(instance) { 29 | authenticationInstance = instance 30 | 31 | // Attempt to sign up user. 32 | authenticationInstance.signup(name, {from: coinbase}) 33 | .then(function(result) { 34 | // If no error, login user. 35 | return dispatch(loginUser()) 36 | }) 37 | .catch(function(result) { 38 | // If error... 39 | }) 40 | }) 41 | }) 42 | } 43 | } else { 44 | console.error('Web3 is not initialized.'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Link } from 'react-router' 3 | import { HiddenOnlyAuth, VisibleOnlyAuth } from './util/wrappers.js' 4 | 5 | // UI Components 6 | import LoginButtonContainer from './user/ui/loginbutton/LoginButtonContainer' 7 | import LogoutButtonContainer from './user/ui/logoutbutton/LogoutButtonContainer' 8 | 9 | // Styles 10 | import './css/oswald.css' 11 | import './css/open-sans.css' 12 | import './css/pure-min.css' 13 | import './App.css' 14 | 15 | class App extends Component { 16 | render() { 17 | const OnlyAuthLinks = VisibleOnlyAuth(() => 18 | 19 |
  • 20 | Dashboard 21 |
  • 22 |
  • 23 | Profile 24 |
  • 25 | 26 |
    27 | ) 28 | 29 | const OnlyGuestLinks = HiddenOnlyAuth(() => 30 | 31 |
  • 32 | Sign Up 33 |
  • 34 | 35 |
    36 | ) 37 | 38 | return ( 39 |
    40 | 47 | 48 | {this.props.children} 49 |
    50 | ); 51 | } 52 | } 53 | 54 | export default App 55 | -------------------------------------------------------------------------------- /src/user/ui/profileform/ProfileFormActions.js: -------------------------------------------------------------------------------- 1 | import AuthenticationContract from '../../../../build/contracts/Authentication.json' 2 | import store from '../../../store' 3 | 4 | const contract = require('truffle-contract') 5 | 6 | export const USER_UPDATED = 'USER_UPDATED' 7 | function userUpdated(user) { 8 | return { 9 | type: USER_UPDATED, 10 | payload: user 11 | } 12 | } 13 | 14 | export function updateUser(name) { 15 | let web3 = store.getState().web3.web3Instance 16 | 17 | // Double-check web3's status. 18 | if (typeof web3 !== 'undefined') { 19 | 20 | return function(dispatch) { 21 | // Using truffle-contract we create the authentication object. 22 | const authentication = contract(AuthenticationContract) 23 | authentication.setProvider(web3.currentProvider) 24 | 25 | // Declaring this for later so we can chain functions on Authentication. 26 | var authenticationInstance 27 | 28 | // Get current ethereum wallet. 29 | web3.eth.getCoinbase((error, coinbase) => { 30 | // Log errors, if any. 31 | if (error) { 32 | console.error(error); 33 | } 34 | 35 | authentication.deployed().then(function(instance) { 36 | authenticationInstance = instance 37 | 38 | // Attempt to login user. 39 | authenticationInstance.update(name, {from: coinbase}) 40 | .then(function(result) { 41 | // If no error, update user. 42 | 43 | dispatch(userUpdated({"name": name})) 44 | 45 | return alert('Name updated!') 46 | }) 47 | .catch(function(result) { 48 | // If error... 49 | }) 50 | }) 51 | }) 52 | } 53 | } else { 54 | console.error('Web3 is not initialized.'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/layouts/home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | class Home extends Component { 4 | render() { 5 | return( 6 |
    7 |
    8 |
    9 |

    Good to Go!

    10 |

    Your Truffle Box is installed and ready.

    11 |

    Smart Contract Authentication

    12 |

    This particular box comes with autentication via a smart contract built-in.

    13 |

    In the upper-right corner, you'll see a login button. Click it to login with with the Authentication smart contract. If there is no user information for the given address, you'll be redirected to sign up. There are two authenticated routes: "/dashboard", which displays the user's name once authenticated; and "/profile", which allows a user to update their name.

    14 |

    Redirect Path

    15 |

    This example redirects home ("/") when trying to access an authenticated route without first authenticating. You can change this path in the failureRedriectUrl property of the UserIsAuthenticated wrapper on line 9 of util/wrappers.js.

    16 |

    Accessing User Data

    17 |

    Once authenticated, any component can access the user's data by assigning the authData object to a component's props.
    {"// In component's render function."}
    {"const { authData } = this.props"}

    {"// Use in component."}
    {"Hello { this.props.authData.name }!"}

    18 |

    Further Reading

    19 |

    The React/Redux portions of the authentication fuctionality are provided by mjrussell/redux-auth-wrapper.

    20 |
    21 |
    22 |
    23 | ) 24 | } 25 | } 26 | 27 | export default Home 28 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | var appDirectory = fs.realpathSync(process.cwd()); 7 | function resolveApp(relativePath) { 8 | return path.resolve(appDirectory, relativePath); 9 | } 10 | 11 | // We support resolving modules according to `NODE_PATH`. 12 | // This lets you use absolute paths in imports inside large monorepos: 13 | // https://github.com/facebookincubator/create-react-app/issues/253. 14 | 15 | // It works similar to `NODE_PATH` in Node itself: 16 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 17 | 18 | // We will export `nodePaths` as an array of absolute paths. 19 | // It will then be used by Webpack configs. 20 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 21 | 22 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 23 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 24 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 25 | 26 | var nodePaths = (process.env.NODE_PATH || '') 27 | .split(process.platform === 'win32' ? ';' : ':') 28 | .filter(Boolean) 29 | .filter(folder => !path.isAbsolute(folder)) 30 | .map(resolveApp); 31 | 32 | // config after eject: we're in ./config/ 33 | module.exports = { 34 | // Changed from build to build_webpack so smart contract compilations are not overwritten. 35 | appBuild: resolveApp('build_webpack'), 36 | appPublic: resolveApp('public'), 37 | appHtml: resolveApp('public/index.html'), 38 | appIndexJs: resolveApp('src/index.js'), 39 | appPackageJson: resolveApp('package.json'), 40 | appSrc: resolveApp('src'), 41 | yarnLockFile: resolveApp('yarn.lock'), 42 | testsSetup: resolveApp('src/setupTests.js'), 43 | appNodeModules: resolveApp('node_modules'), 44 | ownNodeModules: resolveApp('node_modules'), 45 | nodePaths: nodePaths 46 | }; 47 | -------------------------------------------------------------------------------- /src/user/ui/loginbutton/LoginButtonActions.js: -------------------------------------------------------------------------------- 1 | import AuthenticationContract from '../../../../build/contracts/Authentication.json' 2 | import { browserHistory } from 'react-router' 3 | import store from '../../../store' 4 | 5 | const contract = require('truffle-contract') 6 | 7 | export const USER_LOGGED_IN = 'USER_LOGGED_IN' 8 | function userLoggedIn(user) { 9 | return { 10 | type: USER_LOGGED_IN, 11 | payload: user 12 | } 13 | } 14 | 15 | export function loginUser() { 16 | let web3 = store.getState().web3.web3Instance 17 | 18 | // Double-check web3's status. 19 | if (typeof web3 !== 'undefined') { 20 | 21 | return function(dispatch) { 22 | // Using truffle-contract we create the authentication object. 23 | const authentication = contract(AuthenticationContract) 24 | authentication.setProvider(web3.currentProvider) 25 | 26 | // Declaring this for later so we can chain functions on Authentication. 27 | var authenticationInstance 28 | 29 | // Get current ethereum wallet. 30 | web3.eth.getCoinbase((error, coinbase) => { 31 | // Log errors, if any. 32 | if (error) { 33 | console.error(error); 34 | } 35 | 36 | authentication.deployed().then(function(instance) { 37 | authenticationInstance = instance 38 | 39 | // Attempt to login user. 40 | authenticationInstance.login({from: coinbase}) 41 | .then(function(result) { 42 | // If no error, login user. 43 | var userName = web3.toUtf8(result) 44 | 45 | dispatch(userLoggedIn({"name": userName})) 46 | 47 | // Used a manual redirect here as opposed to a wrapper. 48 | // This way, once logged in a user can still access the home page. 49 | var currentLocation = browserHistory.getCurrentLocation() 50 | 51 | if ('redirect' in currentLocation.query) 52 | { 53 | return browserHistory.push(decodeURIComponent(currentLocation.query.redirect)) 54 | } 55 | 56 | return browserHistory.push('/dashboard') 57 | }) 58 | .catch(function(result) { 59 | // If error, go to signup page. 60 | console.error('Wallet ' + coinbase + ' does not have an account!') 61 | 62 | return browserHistory.push('/signup') 63 | }) 64 | }) 65 | }) 66 | } 67 | } else { 68 | console.error('Web3 is not initialized.'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ **No new changes will be made to this box! Truffle no longer actively maintains official boxes that encourage production use of their included contracts. [We encourage you to take a look at other boxes in our listing that offer authentication](https://truffleframework.com/boxes).** 2 | 3 | # React, Redux and Authentication Truffle Box 4 | 5 | In addition to Webpack and React, this box adds: react-router, redux and redux-auth-wrapper for authentication powered by a smart contract. Great for building your own auth system. 6 | 7 | ## Installation 8 | 9 | 1. Install Truffle globally. 10 | ```javascript 11 | npm install -g truffle 12 | ``` 13 | 14 | 2. Download the box. This also takes care of installing the necessary dependencies. 15 | ```javascript 16 | truffle unbox react-auth 17 | ``` 18 | 19 | 3. Run the development console. 20 | ```javascript 21 | truffle develop 22 | ``` 23 | 24 | 4. Compile and migrate the smart contracts. Note inside the development console we don't preface commands with `truffle`. 25 | ```javascript 26 | compile 27 | migrate 28 | ``` 29 | 30 | 5. Run the webpack server for front-end hot reloading (outside the development console). Smart contract changes must be manually recompiled and migrated. 31 | ```javascript 32 | // Serves the front-end on http://localhost:3000 33 | npm run start 34 | ``` 35 | 36 | 6. Truffle can run tests written in Solidity or JavaScript against your smart contracts. Note the command varies slightly if you're in or outside of the development console. 37 | ```javascript 38 | // If inside the development console. 39 | test 40 | 41 | // If outside the development console.. 42 | truffle test 43 | ``` 44 | 45 | 7. Jest is included for testing React components. Compile your contracts before running Jest, or you may receive some file not found errors. 46 | ```javascript 47 | // Run Jest outside of the development console for front-end component tests. 48 | npm run test 49 | ``` 50 | 51 | 8. To build the application for production, use the build command. A production build will be in the build_webpack folder. 52 | ```javascript 53 | npm run build 54 | ``` 55 | 56 | ## FAQ 57 | 58 | * __How do I use this with the EthereumJS TestRPC?__ 59 | 60 | It's as easy as modifying the config file! [Check out our documentation on adding network configurations](http://truffleframework.com/docs/advanced/configuration#networks). Depending on the port you're using, you'll also need to update line 34 of `src/util/web3/getWeb3.js`. 61 | 62 | * __Why is there both a truffle.js file and a truffle-config.js file?__ 63 | 64 | `truffle-config.js` is a copy of `truffle.js` for compatibility with Windows development environments. Feel free to it if it's irrelevant to your platform. 65 | 66 | * __Where is my production build?__ 67 | 68 | The production build will be in the build_webpack folder. This is because Truffle outputs contract compilations to the build folder. 69 | 70 | * __Where can I find more documentation?__ 71 | 72 | This box is a marriage of [Truffle](http://truffleframework.com/) and a React setup created with [create-react-app](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md). Either one would be a great place to start! 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-auth-box", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "autoprefixer": "6.5.1", 7 | "babel-core": "6.17.0", 8 | "babel-eslint": "7.1.1", 9 | "babel-jest": "17.0.2", 10 | "babel-loader": "6.2.7", 11 | "babel-preset-react-app": "^2.0.1", 12 | "case-sensitive-paths-webpack-plugin": "1.1.4", 13 | "chalk": "1.1.3", 14 | "connect-history-api-fallback": "1.3.0", 15 | "cross-spawn": "4.0.2", 16 | "css-loader": "0.26.0", 17 | "detect-port": "1.0.1", 18 | "dotenv": "2.0.0", 19 | "eslint": "3.8.1", 20 | "eslint-config-react-app": "^0.5.0", 21 | "eslint-loader": "1.6.0", 22 | "eslint-plugin-flowtype": "2.21.0", 23 | "eslint-plugin-import": "2.0.1", 24 | "eslint-plugin-jsx-a11y": "2.2.3", 25 | "eslint-plugin-react": "6.4.1", 26 | "extract-text-webpack-plugin": "1.0.1", 27 | "file-loader": "0.9.0", 28 | "filesize": "3.3.0", 29 | "fs-extra": "0.30.0", 30 | "gzip-size": "3.0.0", 31 | "html-webpack-plugin": "2.24.0", 32 | "http-proxy-middleware": "0.17.2", 33 | "jest": "18.1.0", 34 | "json-loader": "0.5.4", 35 | "object-assign": "4.1.0", 36 | "path-exists": "2.1.0", 37 | "postcss-loader": "1.0.0", 38 | "promise": "7.1.1", 39 | "react-dev-utils": "^0.4.2", 40 | "recursive-readdir": "2.1.0", 41 | "strip-ansi": "3.0.1", 42 | "style-loader": "0.13.1", 43 | "truffle-contract": "^1.1.8", 44 | "url-loader": "0.5.7", 45 | "webpack": "1.14.0", 46 | "webpack-dev-server": "1.16.2", 47 | "webpack-manifest-plugin": "1.1.0", 48 | "whatwg-fetch": "1.0.0" 49 | }, 50 | "dependencies": { 51 | "react": "^15.4.2", 52 | "react-dom": "^15.4.2", 53 | "react-redux": "^5.0.2", 54 | "react-router": "^3.0.2", 55 | "react-router-redux": "^4.0.7", 56 | "redux": "^3.6.0", 57 | "redux-auth-wrapper": "^1.0.0", 58 | "redux-thunk": "^2.2.0" 59 | }, 60 | "scripts": { 61 | "start": "node scripts/start.js", 62 | "build": "node scripts/build.js", 63 | "test": "node scripts/test.js --env=jsdom" 64 | }, 65 | "jest": { 66 | "collectCoverageFrom": [ 67 | "src/**/*.{js,jsx}" 68 | ], 69 | "setupFiles": [ 70 | "/config/polyfills.js" 71 | ], 72 | "testMatch": [ 73 | "/src/**/__tests__/**/*.js?(x)", 74 | "/src/**/?(*.)(spec|test).js?(x)" 75 | ], 76 | "testEnvironment": "node", 77 | "testURL": "http://localhost", 78 | "transform": { 79 | "^.+\\.(js|jsx)$": "/node_modules/babel-jest", 80 | "^.+\\.css$": "/config/jest/cssTransform.js", 81 | "^(?!.*\\.(js|jsx|css|json)$)": "/config/jest/fileTransform.js" 82 | }, 83 | "transformIgnorePatterns": [ 84 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$" 85 | ], 86 | "moduleNameMapper": { 87 | "^react-native$": "react-native-web" 88 | }, 89 | "moduleFileExtensions": [ 90 | "web.js", 91 | "js", 92 | "json", 93 | "web.jsx", 94 | "jsx" 95 | ] 96 | }, 97 | "babel": { 98 | "presets": [ 99 | "react-app" 100 | ] 101 | }, 102 | "eslintConfig": { 103 | "extends": "react-app" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/fonts/Oswald-300/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016 The Oswald Project Authors (contact@sansoxygen.com) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /src/fonts/Oswald-regular/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016 The Oswald Project Authors (contact@sansoxygen.com) 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | // Do this as the first thing so that any code reading it knows the right env. 2 | process.env.NODE_ENV = 'production'; 3 | 4 | // Load environment variables from .env file. Suppress warnings using silent 5 | // if this file is missing. dotenv will never modify any environment variables 6 | // that have already been set. 7 | // https://github.com/motdotla/dotenv 8 | require('dotenv').config({silent: true}); 9 | 10 | var chalk = require('chalk'); 11 | var fs = require('fs-extra'); 12 | var path = require('path'); 13 | var pathExists = require('path-exists'); 14 | var filesize = require('filesize'); 15 | var gzipSize = require('gzip-size').sync; 16 | var webpack = require('webpack'); 17 | var config = require('../config/webpack.config.prod'); 18 | var paths = require('../config/paths'); 19 | var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 20 | var recursive = require('recursive-readdir'); 21 | var stripAnsi = require('strip-ansi'); 22 | 23 | var useYarn = pathExists.sync(paths.yarnLockFile); 24 | 25 | // Warn and crash if required files are missing 26 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 27 | process.exit(1); 28 | } 29 | 30 | // Input: /User/dan/app/build/static/js/main.82be8.js 31 | // Output: /static/js/main.js 32 | function removeFileNameHash(fileName) { 33 | return fileName 34 | .replace(paths.appBuild, '') 35 | .replace(/\/?(.*)(\.\w+)(\.js|\.css)/, (match, p1, p2, p3) => p1 + p3); 36 | } 37 | 38 | // Input: 1024, 2048 39 | // Output: "(+1 KB)" 40 | function getDifferenceLabel(currentSize, previousSize) { 41 | var FIFTY_KILOBYTES = 1024 * 50; 42 | var difference = currentSize - previousSize; 43 | var fileSize = !Number.isNaN(difference) ? filesize(difference) : 0; 44 | if (difference >= FIFTY_KILOBYTES) { 45 | return chalk.red('+' + fileSize); 46 | } else if (difference < FIFTY_KILOBYTES && difference > 0) { 47 | return chalk.yellow('+' + fileSize); 48 | } else if (difference < 0) { 49 | return chalk.green(fileSize); 50 | } else { 51 | return ''; 52 | } 53 | } 54 | 55 | // First, read the current file sizes in build directory. 56 | // This lets us display how much they changed later. 57 | recursive(paths.appBuild, (err, fileNames) => { 58 | var previousSizeMap = (fileNames || []) 59 | .filter(fileName => /\.(js|css)$/.test(fileName)) 60 | .reduce((memo, fileName) => { 61 | var contents = fs.readFileSync(fileName); 62 | var key = removeFileNameHash(fileName); 63 | memo[key] = gzipSize(contents); 64 | return memo; 65 | }, {}); 66 | 67 | // Remove all content but keep the directory so that 68 | // if you're in it, you don't end up in Trash 69 | fs.emptyDirSync(paths.appBuild); 70 | 71 | // Start the webpack build 72 | build(previousSizeMap); 73 | 74 | // Merge with the public folder 75 | copyPublicFolder(); 76 | }); 77 | 78 | // Print a detailed summary of build files. 79 | function printFileSizes(stats, previousSizeMap) { 80 | var assets = stats.toJson().assets 81 | .filter(asset => /\.(js|css)$/.test(asset.name)) 82 | .map(asset => { 83 | var fileContents = fs.readFileSync(paths.appBuild + '/' + asset.name); 84 | var size = gzipSize(fileContents); 85 | var previousSize = previousSizeMap[removeFileNameHash(asset.name)]; 86 | var difference = getDifferenceLabel(size, previousSize); 87 | return { 88 | folder: path.join('build_webpack', path.dirname(asset.name)), 89 | name: path.basename(asset.name), 90 | size: size, 91 | sizeLabel: filesize(size) + (difference ? ' (' + difference + ')' : '') 92 | }; 93 | }); 94 | assets.sort((a, b) => b.size - a.size); 95 | var longestSizeLabelLength = Math.max.apply(null, 96 | assets.map(a => stripAnsi(a.sizeLabel).length) 97 | ); 98 | assets.forEach(asset => { 99 | var sizeLabel = asset.sizeLabel; 100 | var sizeLength = stripAnsi(sizeLabel).length; 101 | if (sizeLength < longestSizeLabelLength) { 102 | var rightPadding = ' '.repeat(longestSizeLabelLength - sizeLength); 103 | sizeLabel += rightPadding; 104 | } 105 | console.log( 106 | ' ' + sizeLabel + 107 | ' ' + chalk.dim(asset.folder + path.sep) + chalk.cyan(asset.name) 108 | ); 109 | }); 110 | } 111 | 112 | // Print out errors 113 | function printErrors(summary, errors) { 114 | console.log(chalk.red(summary)); 115 | console.log(); 116 | errors.forEach(err => { 117 | console.log(err.message || err); 118 | console.log(); 119 | }); 120 | } 121 | 122 | // Create the production build and print the deployment instructions. 123 | function build(previousSizeMap) { 124 | console.log('Creating an optimized production build...'); 125 | webpack(config).run((err, stats) => { 126 | if (err) { 127 | printErrors('Failed to compile.', [err]); 128 | process.exit(1); 129 | } 130 | 131 | if (stats.compilation.errors.length) { 132 | printErrors('Failed to compile.', stats.compilation.errors); 133 | process.exit(1); 134 | } 135 | 136 | if (process.env.CI && stats.compilation.warnings.length) { 137 | printErrors('Failed to compile.', stats.compilation.warnings); 138 | process.exit(1); 139 | } 140 | 141 | console.log(chalk.green('Compiled successfully.')); 142 | console.log(); 143 | 144 | console.log('File sizes after gzip:'); 145 | console.log(); 146 | printFileSizes(stats, previousSizeMap); 147 | console.log(); 148 | 149 | var openCommand = process.platform === 'win32' ? 'start' : 'open'; 150 | var appPackage = require(paths.appPackageJson); 151 | var homepagePath = appPackage.homepage; 152 | var publicPath = config.output.publicPath; 153 | if (homepagePath && homepagePath.indexOf('.github.io/') !== -1) { 154 | // "homepage": "http://user.github.io/project" 155 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.'); 156 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.'); 157 | console.log(); 158 | console.log('The ' + chalk.cyan('build_webpack') + ' folder is ready to be deployed.'); 159 | console.log('To publish it at ' + chalk.green(homepagePath) + ', run:'); 160 | // If script deploy has been added to package.json, skip the instructions 161 | if (typeof appPackage.scripts.deploy === 'undefined') { 162 | console.log(); 163 | if (useYarn) { 164 | console.log(' ' + chalk.cyan('yarn') + ' add --dev gh-pages'); 165 | } else { 166 | console.log(' ' + chalk.cyan('npm') + ' install --save-dev gh-pages'); 167 | } 168 | console.log(); 169 | console.log('Add the following script in your ' + chalk.cyan('package.json') + '.'); 170 | console.log(); 171 | console.log(' ' + chalk.dim('// ...')); 172 | console.log(' ' + chalk.yellow('"scripts"') + ': {'); 173 | console.log(' ' + chalk.dim('// ...')); 174 | console.log(' ' + chalk.yellow('"deploy"') + ': ' + chalk.yellow('"npm run build&&gh-pages -d build"')); 175 | console.log(' }'); 176 | console.log(); 177 | console.log('Then run:'); 178 | } 179 | console.log(); 180 | console.log(' ' + chalk.cyan(useYarn ? 'yarn' : 'npm') + ' run deploy'); 181 | console.log(); 182 | } else if (publicPath !== '/') { 183 | // "homepage": "http://mywebsite.com/project" 184 | console.log('The project was built assuming it is hosted at ' + chalk.green(publicPath) + '.'); 185 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.'); 186 | console.log(); 187 | console.log('The ' + chalk.cyan('build_webpack') + ' folder is ready to be deployed.'); 188 | console.log(); 189 | } else { 190 | // no homepage or "homepage": "http://mywebsite.com" 191 | console.log('The project was built assuming it is hosted at the server root.'); 192 | if (homepagePath) { 193 | // "homepage": "http://mywebsite.com" 194 | console.log('You can control this with the ' + chalk.green('homepage') + ' field in your ' + chalk.cyan('package.json') + '.'); 195 | console.log(); 196 | } else { 197 | // no homepage 198 | console.log('To override this, specify the ' + chalk.green('homepage') + ' in your ' + chalk.cyan('package.json') + '.'); 199 | console.log('For example, add this to build it for GitHub Pages:') 200 | console.log(); 201 | console.log(' ' + chalk.green('"homepage"') + chalk.cyan(': ') + chalk.green('"http://myname.github.io/myapp"') + chalk.cyan(',')); 202 | console.log(); 203 | } 204 | console.log('The ' + chalk.cyan('build_webpack') + ' folder is ready to be deployed.'); 205 | console.log('You may also serve it locally with a static server:') 206 | console.log(); 207 | if (useYarn) { 208 | console.log(' ' + chalk.cyan('yarn') + ' global add pushstate-server'); 209 | } else { 210 | console.log(' ' + chalk.cyan('npm') + ' install -g pushstate-server'); 211 | } 212 | console.log(' ' + chalk.cyan('pushstate-server') + ' build'); 213 | console.log(' ' + chalk.cyan(openCommand) + ' http://localhost:9000'); 214 | console.log(); 215 | } 216 | }); 217 | } 218 | 219 | function copyPublicFolder() { 220 | fs.copySync(paths.appPublic, paths.appBuild, { 221 | dereference: true, 222 | filter: file => file !== paths.appHtml 223 | }); 224 | } 225 | -------------------------------------------------------------------------------- /config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var autoprefixer = require('autoprefixer'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); 5 | var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); 6 | var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); 7 | var getClientEnvironment = require('./env'); 8 | var paths = require('./paths'); 9 | 10 | 11 | 12 | // Webpack uses `publicPath` to determine where the app is being served from. 13 | // In development, we always serve from the root. This makes config easier. 14 | var publicPath = '/'; 15 | // `publicUrl` is just like `publicPath`, but we will provide it to our app 16 | // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. 17 | // Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. 18 | var publicUrl = ''; 19 | // Get environment variables to inject into our app. 20 | var env = getClientEnvironment(publicUrl); 21 | 22 | // This is the development configuration. 23 | // It is focused on developer experience and fast rebuilds. 24 | // The production configuration is different and lives in a separate file. 25 | module.exports = { 26 | // You may want 'eval' instead if you prefer to see the compiled output in DevTools. 27 | // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. 28 | devtool: 'cheap-module-source-map', 29 | // These are the "entry points" to our application. 30 | // This means they will be the "root" imports that are included in JS bundle. 31 | // The first two entry points enable "hot" CSS and auto-refreshes for JS. 32 | entry: [ 33 | // Include an alternative client for WebpackDevServer. A client's job is to 34 | // connect to WebpackDevServer by a socket and get notified about changes. 35 | // When you save a file, the client will either apply hot updates (in case 36 | // of CSS changes), or refresh the page (in case of JS changes). When you 37 | // make a syntax error, this client will display a syntax error overlay. 38 | // Note: instead of the default WebpackDevServer client, we use a custom one 39 | // to bring better experience for Create React App users. You can replace 40 | // the line below with these two lines if you prefer the stock client: 41 | // require.resolve('webpack-dev-server/client') + '?/', 42 | // require.resolve('webpack/hot/dev-server'), 43 | require.resolve('react-dev-utils/webpackHotDevClient'), 44 | // We ship a few polyfills by default: 45 | require.resolve('./polyfills'), 46 | // Finally, this is your app's code: 47 | paths.appIndexJs 48 | // We include the app code last so that if there is a runtime error during 49 | // initialization, it doesn't blow up the WebpackDevServer client, and 50 | // changing JS code would still trigger a refresh. 51 | ], 52 | output: { 53 | // Next line is not used in dev but WebpackDevServer crashes without it: 54 | path: paths.appBuild, 55 | // Add /* filename */ comments to generated require()s in the output. 56 | pathinfo: true, 57 | // This does not produce a real file. It's just the virtual path that is 58 | // served by WebpackDevServer in development. This is the JS bundle 59 | // containing code from all our entry points, and the Webpack runtime. 60 | filename: 'static/js/bundle.js', 61 | // This is the URL that app is served from. We use "/" in development. 62 | publicPath: publicPath 63 | }, 64 | resolve: { 65 | // This allows you to set a fallback for where Webpack should look for modules. 66 | // We read `NODE_PATH` environment variable in `paths.js` and pass paths here. 67 | // We use `fallback` instead of `root` because we want `node_modules` to "win" 68 | // if there any conflicts. This matches Node resolution mechanism. 69 | // https://github.com/facebookincubator/create-react-app/issues/253 70 | fallback: paths.nodePaths, 71 | // These are the reasonable defaults supported by the Node ecosystem. 72 | // We also include JSX as a common component filename extension to support 73 | // some tools, although we do not recommend using it, see: 74 | // https://github.com/facebookincubator/create-react-app/issues/290 75 | extensions: ['.js', '.json', '.jsx', ''], 76 | alias: { 77 | // Support React Native Web 78 | // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 79 | 'react-native': 'react-native-web' 80 | } 81 | }, 82 | 83 | module: { 84 | // First, run the linter. 85 | // It's important to do this before Babel processes the JS. 86 | preLoaders: [ 87 | { 88 | test: /\.(js|jsx)$/, 89 | loader: 'eslint', 90 | include: paths.appSrc, 91 | } 92 | ], 93 | loaders: [ 94 | // Default loader: load all assets that are not handled 95 | // by other loaders with the url loader. 96 | // Note: This list needs to be updated with every change of extensions 97 | // the other loaders match. 98 | // E.g., when adding a loader for a new supported file extension, 99 | // we need to add the supported extension to this loader too. 100 | // Add one new line in `exclude` for each loader. 101 | // 102 | // "file" loader makes sure those assets get served by WebpackDevServer. 103 | // When you `import` an asset, you get its (virtual) filename. 104 | // In production, they would get copied to the `build` folder. 105 | // "url" loader works like "file" loader except that it embeds assets 106 | // smaller than specified limit in bytes as data URLs to avoid requests. 107 | // A missing `test` is equivalent to a match. 108 | { 109 | exclude: [ 110 | /\.html$/, 111 | /\.(js|jsx)$/, 112 | /\.css$/, 113 | /\.json$/, 114 | /\.woff$/, 115 | /\.woff2$/, 116 | /\.(ttf|svg|eot)$/ 117 | ], 118 | loader: 'url', 119 | query: { 120 | limit: 10000, 121 | name: 'static/media/[name].[hash:8].[ext]' 122 | } 123 | }, 124 | // Process JS with Babel. 125 | { 126 | test: /\.(js|jsx)$/, 127 | include: paths.appSrc, 128 | loader: 'babel', 129 | query: { 130 | 131 | // This is a feature of `babel-loader` for webpack (not Babel itself). 132 | // It enables caching results in ./node_modules/.cache/babel-loader/ 133 | // directory for faster rebuilds. 134 | cacheDirectory: true 135 | } 136 | }, 137 | // "postcss" loader applies autoprefixer to our CSS. 138 | // "css" loader resolves paths in CSS and adds assets as dependencies. 139 | // "style" loader turns CSS into JS modules that inject