├── .gitignore ├── Procfile ├── README.md ├── backend ├── .gitignore ├── app │ ├── account │ │ ├── helper.js │ │ ├── session.js │ │ └── table.js │ ├── accountDragon │ │ └── table.js │ ├── api │ │ ├── account.js │ │ ├── dragon.js │ │ ├── generation.js │ │ └── helper.js │ ├── config.js │ ├── dragon │ │ ├── breeder.js │ │ ├── helper.js │ │ ├── index.js │ │ └── table.js │ ├── dragonTrait │ │ └── table.js │ ├── generation │ │ ├── engine.js │ │ ├── index.js │ │ └── table.js │ ├── index.js │ └── trait │ │ └── table.js ├── bin │ ├── configure_db.sh │ ├── insertTraits.js │ ├── server.js │ └── sql │ │ ├── account.sql │ │ ├── accountDragon.sql │ │ ├── dragon.sql │ │ ├── dragonTrait.sql │ │ ├── generation.sql │ │ └── trait.sql ├── data │ └── traits.json ├── databasePool.js ├── package-lock.json ├── package.json └── secrets │ ├── databaseConfiguration.js │ └── index.js ├── course_logo_udemy.png ├── frontend ├── .babelrc ├── package-lock.json ├── package.json └── src │ ├── actions │ ├── account.js │ ├── accountDragons.js │ ├── accountInfo.js │ ├── dragon.js │ ├── generation.js │ ├── publicDragons.js │ └── types.js │ ├── assets │ ├── .DS_Store │ ├── index.js │ ├── patchy.png │ ├── plain.png │ ├── skinny.png │ ├── slender.png │ ├── sporty.png │ ├── spotted.png │ ├── stocky.png │ └── striped.png │ ├── components │ ├── AccountDragonRow.js │ ├── AccountDragons.js │ ├── AccountInfo.js │ ├── AuthForm.js │ ├── Dragon.js │ ├── DragonAvatar.js │ ├── Generation.js │ ├── Home.js │ ├── MatingOptions.js │ ├── PublicDragonRow.js │ ├── PublicDragons.js │ └── Root.js │ ├── config.js │ ├── history.js │ ├── index.css │ ├── index.html │ ├── index.js │ └── reducers │ ├── account.js │ ├── accountDragons.js │ ├── accountInfo.js │ ├── dragon.js │ ├── fetchStates.js │ ├── generation.js │ ├── index.js │ └── publicDragons.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | *node_modules* 2 | *.cache* 3 | *dist* 4 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | backend: nodemon ./backend/bin/server 2 | frontend: parcel ./frontend/src/index.html 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Master Full-Stack Development | Node, SQL, React, and More 2 | 3 | ![Course Logo](course_logo_udemy.png) 4 | 5 | The official code for the *Master Full-Stack Development | Node, SQL, React, and More* course on Udemy by David Joseph Katz. 6 | 7 | ## Check out the course: 8 | **[https://www.udemy.com/full-stack](https://www.udemy.com/full-stack)** 9 | 10 | This course is a full-stack web application with both a backend and a frontend. It uses Node.js, Express.js, and PostgreSQL on the backend to create a server, api, and manage the database. For the frontend, dragonstack uses React.js, Redux, and various JavaScript modules. 11 | 12 | Some of the main course highlights: 13 | - Build a backend server with Node.js and Express.js. 14 | - Create a full-on API with GET, POST, and PUT requests. 15 | - Build components with React.js. 16 | - Manage the internal state of the frontend of the frontend with Redux. 17 | - Discover best practices around naming and structure. 18 | - Understand the guiding principles of simplicity and singularity. 19 | - Deep dive into JavaScript promises. 20 | - Thoroughly explore the Node.js event loop and v8 engine. 21 | - Create a full on secure authentication system. 22 | 23 | Ultimately, this course will set you well on your way in the industry. Thorough knowledge of the full-stack will make you capable of earning and thriving in backend, frontend, or full-stack positions. -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | ##CONSIDER_IGNORING: secrets/ 3 | -------------------------------------------------------------------------------- /backend/app/account/helper.js: -------------------------------------------------------------------------------- 1 | const SHA256 = require('crypto-js/sha256'); 2 | const { APP_SECRET } = require('../../secrets'); 3 | 4 | const hash = string => { 5 | return SHA256(`${APP_SECRET}${string}${APP_SECRET}`).toString(); 6 | }; 7 | 8 | module.exports = { hash }; -------------------------------------------------------------------------------- /backend/app/account/session.js: -------------------------------------------------------------------------------- 1 | const uuid = require('uuid/v4'); 2 | const { hash } = require('./helper'); 3 | 4 | const SEPARATOR = '|'; 5 | 6 | class Session { 7 | constructor({ username }) { 8 | this.username = username; 9 | this.id = uuid(); 10 | } 11 | 12 | toString() { 13 | const { username, id } = this; 14 | 15 | return Session.sessionString({ username, id }); 16 | } 17 | 18 | static parse(sessionString) { 19 | const sessionData = sessionString.split(SEPARATOR); 20 | 21 | return { 22 | username: sessionData[0], 23 | id: sessionData[1], 24 | sessionHash: sessionData[2] 25 | }; 26 | } 27 | 28 | static verify(sessionString) { 29 | const { username, id, sessionHash } = Session.parse(sessionString); 30 | 31 | const accountData = Session.accountData({ username, id }); 32 | 33 | return hash(accountData) === sessionHash; 34 | } 35 | 36 | static accountData({ username, id }) { 37 | return `${username}${SEPARATOR}${id}`; 38 | } 39 | 40 | static sessionString({ username, id }) { 41 | const accountData = Session.accountData({ username, id }); 42 | 43 | return `${accountData}${SEPARATOR}${hash(accountData)}`; 44 | } 45 | } 46 | 47 | module.exports = Session; -------------------------------------------------------------------------------- /backend/app/account/table.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | const { STARTING_BALANCE } = require('../config'); 3 | 4 | class AccountTable { 5 | static storeAccount({ usernameHash, passwordHash }) { 6 | return new Promise((resolve, reject) => { 7 | pool.query( 8 | `INSERT INTO account("usernameHash", "passwordHash", balance) 9 | VALUES($1, $2, $3)`, 10 | [usernameHash, passwordHash, STARTING_BALANCE], 11 | (error, response) => { 12 | if (error) return reject(error); 13 | 14 | resolve(); 15 | } 16 | ); 17 | }); 18 | } 19 | 20 | static getAccount({ usernameHash }) { 21 | return new Promise((resolve, reject) => { 22 | pool.query( 23 | `SELECT id, "passwordHash", "sessionId", balance FROM account 24 | WHERE "usernameHash" = $1`, 25 | [usernameHash], 26 | (error, response) => { 27 | if (error) return reject(error); 28 | 29 | resolve({ account: response.rows[0] }); 30 | } 31 | ) 32 | }); 33 | } 34 | 35 | static updateSessionId({ sessionId, usernameHash }) { 36 | return new Promise((resolve, reject) => { 37 | pool.query( 38 | 'UPDATE account SET "sessionId" = $1 WHERE "usernameHash" = $2', 39 | [sessionId, usernameHash], 40 | (error, response) => { 41 | if (error) return reject(error); 42 | 43 | resolve(); 44 | } 45 | ) 46 | }); 47 | } 48 | 49 | static updateBalance({ accountId, value }) { 50 | return new Promise((resolve, reject) => { 51 | pool.query( 52 | 'UPDATE account SET balance = balance + $1 WHERE id = $2', 53 | [value, accountId], 54 | (error, response) => { 55 | if (error) return reject(error); 56 | 57 | resolve(); 58 | } 59 | ) 60 | }); 61 | } 62 | } 63 | 64 | module.exports = AccountTable; 65 | -------------------------------------------------------------------------------- /backend/app/accountDragon/table.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | 3 | class AccountDragonTable { 4 | static storeAccountDragon({ accountId, dragonId }) { 5 | return new Promise((resolve, reject) => { 6 | pool.query( 7 | 'INSERT INTO accountDragon("accountId", "dragonId") VALUES($1, $2)', 8 | [accountId, dragonId], 9 | (error, response) => { 10 | if (error) return reject(error); 11 | 12 | resolve(); 13 | } 14 | ) 15 | }); 16 | } 17 | 18 | static getAccountDragons({ accountId }) { 19 | return new Promise((resolve, reject) => { 20 | pool.query( 21 | 'SELECT "dragonId" FROM accountDragon WHERE "accountId" = $1', 22 | [accountId], 23 | (error, response) => { 24 | if (error) return reject(error); 25 | 26 | resolve({ accountDragons: response.rows }); 27 | } 28 | ) 29 | }) 30 | } 31 | 32 | static getDragonAccount({ dragonId }) { 33 | return new Promise((resolve, reject) => { 34 | pool.query( 35 | 'SELECT "accountId" FROM accountDragon WHERE "dragonId" = $1', 36 | [dragonId], 37 | (error, response) => { 38 | if (error) return reject(error); 39 | 40 | resolve({ accountId: response.rows[0].accountId }); 41 | } 42 | ) 43 | }); 44 | }; 45 | 46 | static updateDragonAccount({ dragonId, accountId }) { 47 | return new Promise((resolve, reject) => { 48 | pool.query( 49 | 'UPDATE accountDragon SET "accountId" = $1 WHERE "dragonId" = $2', 50 | [accountId, dragonId], 51 | (error, response) => { 52 | if (error) return reject(error); 53 | 54 | resolve(); 55 | } 56 | ) 57 | }); 58 | } 59 | } 60 | 61 | module.exports = AccountDragonTable; -------------------------------------------------------------------------------- /backend/app/api/account.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const AccountTable = require('../account/table'); 3 | const AccountDragonTable = require('../accountDragon/table'); 4 | const Session = require('../account/session'); 5 | const { hash } = require('../account/helper'); 6 | const { setSession, authenticatedAccount } = require('./helper'); 7 | const { getDragonWithTraits } = require('../dragon/helper'); 8 | 9 | const router = new Router(); 10 | 11 | router.post('/signup', (req, res, next) => { 12 | const { username, password } = req.body; 13 | const usernameHash = hash(username); 14 | const passwordHash = hash(password); 15 | 16 | AccountTable.getAccount({ usernameHash }) 17 | .then(({ account }) => { 18 | if (!account) { 19 | return AccountTable.storeAccount({ usernameHash, passwordHash }) 20 | } else { 21 | const error = new Error('This username has already been taken'); 22 | 23 | error.statusCode = 409; 24 | 25 | throw error; 26 | } 27 | }) 28 | .then(() => { 29 | return setSession({ username, res }); 30 | }) 31 | .then(({ message }) => res.json({ message })) 32 | .catch(error => next(error)); 33 | }); 34 | 35 | router.post('/login', (req, res, next) => { 36 | const { username, password } = req.body; 37 | 38 | AccountTable.getAccount({ usernameHash: hash(username) }) 39 | .then(({ account }) => { 40 | if (account && account.passwordHash === hash(password)) { 41 | const { sessionId } = account; 42 | 43 | return setSession({ username, res, sessionId }) 44 | } else { 45 | const error = new Error('Incorrect username/password'); 46 | 47 | error.statusCode = 409; 48 | 49 | throw error; 50 | } 51 | }) 52 | .then(({ message }) => res.json({ message })) 53 | .catch(error => next(error)); 54 | }); 55 | 56 | router.get('/logout', (req, res, next) => { 57 | const { username } = Session.parse(req.cookies.sessionString); 58 | 59 | AccountTable.updateSessionId({ 60 | sessionId: null, 61 | usernameHash: hash(username) 62 | }).then(() => { 63 | res.clearCookie('sessionString'); 64 | 65 | res.json({ message: 'Successful logout' }); 66 | }).catch(error => next(error)); 67 | }); 68 | 69 | router.get('/authenticated', (req, res, next) => { 70 | authenticatedAccount({ sessionString: req.cookies.sessionString }) 71 | .then(({ authenticated }) => res.json({ authenticated })) 72 | .catch(error => next(error)); 73 | }); 74 | 75 | router.get('/dragons', (req, res, next) => { 76 | authenticatedAccount({ sessionString: req.cookies.sessionString }) 77 | .then(({ account }) => { 78 | return AccountDragonTable.getAccountDragons({ 79 | accountId: account.id 80 | }); 81 | }) 82 | .then(({ accountDragons }) => { 83 | return Promise.all( 84 | accountDragons.map(accountDragon => { 85 | return getDragonWithTraits({ dragonId: accountDragon.dragonId }); 86 | }) 87 | ); 88 | }) 89 | .then(dragons => { 90 | res.json({ dragons }); 91 | }) 92 | .catch(error => next(error)); 93 | }); 94 | 95 | router.get('/info', (req, res, next) => { 96 | authenticatedAccount({ sessionString: req.cookies.sessionString }) 97 | .then(({ account, username }) => { 98 | res.json({ info: { balance: account.balance, username } }); 99 | }) 100 | .catch(error => next(error)); 101 | }); 102 | 103 | module.exports = router; -------------------------------------------------------------------------------- /backend/app/api/dragon.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | const DragonTable = require('../dragon/table'); 3 | const AccountDragonTable = require('../accountDragon/table'); 4 | const AccountTable = require('../account/table'); 5 | const Breeder = require('../dragon/breeder'); 6 | const { authenticatedAccount } = require('./helper'); 7 | const { getPublicDragons, getDragonWithTraits } = require('../dragon/helper'); 8 | 9 | const router = new Router(); 10 | 11 | router.get('/new', (req, res, next) => { 12 | let accountId, dragon; 13 | 14 | authenticatedAccount({ sessionString: req.cookies.sessionString }) 15 | .then(({ account }) => { 16 | accountId = account.id; 17 | 18 | dragon = req.app.locals.engine.generation.newDragon({ accountId }); 19 | 20 | return DragonTable.storeDragon(dragon); 21 | }) 22 | .then(({ dragonId }) => { 23 | dragon.dragonId = dragonId; 24 | 25 | console.log('resolved dragonId', dragonId); 26 | 27 | return AccountDragonTable.storeAccountDragon({ accountId, dragonId }); 28 | }) 29 | .then(() => res.json({ dragon })) 30 | .catch(error => next(error)); 31 | }); 32 | 33 | router.put('/update', (req, res, next) => { 34 | const { dragonId, nickname, isPublic, saleValue, sireValue } = req.body; 35 | 36 | DragonTable.updateDragon({ dragonId, nickname, isPublic, saleValue, sireValue }) 37 | .then(() => res.json({ message: 'successfully updated dragon' })) 38 | .catch(error => next(error)); 39 | }); 40 | 41 | router.get('/public-dragons', (req, res, next) => { 42 | getPublicDragons() 43 | .then(({ dragons }) => res.json({ dragons })) 44 | .catch(error => next(error)); 45 | }); 46 | 47 | router.post('/buy', (req, res, next) => { 48 | const { dragonId, saleValue } = req.body; 49 | let buyerId; 50 | 51 | DragonTable.getDragon({ dragonId }) 52 | .then(dragon => { 53 | if (dragon.saleValue !== saleValue) { 54 | throw new Error('Sale value is not correct'); 55 | } 56 | 57 | if (!dragon.isPublic) { 58 | throw new Error('Dragon must be public') 59 | } 60 | 61 | return authenticatedAccount({ sessionString: req.cookies. sessionString }); 62 | }) 63 | .then(({ account, authenticated }) => { 64 | if (!authenticated) { 65 | throw new Error('Unauthenticated') 66 | } 67 | 68 | if (saleValue > account.balance) { 69 | throw new Error('Sale value exceeds balance') 70 | } 71 | 72 | buyerId = account.id; 73 | 74 | return AccountDragonTable.getDragonAccount({ dragonId }); 75 | }) 76 | .then(({ accountId }) => { 77 | if (accountId === buyerId) { 78 | throw new Error('Cannot buy your own dragon!'); 79 | } 80 | 81 | const sellerId = accountId; 82 | 83 | return Promise.all([ 84 | AccountTable.updateBalance({ 85 | accountId: buyerId, value: -saleValue 86 | }), 87 | AccountTable.updateBalance({ 88 | accountId: sellerId, value: saleValue 89 | }), 90 | AccountDragonTable.updateDragonAccount({ 91 | dragonId, accountId: buyerId 92 | }), 93 | DragonTable.updateDragon({ 94 | dragonId, isPublic: false 95 | }) 96 | ]) 97 | }) 98 | .then(() => res.json({ message: 'success!' })) 99 | .catch(error => next(error)); 100 | }); 101 | 102 | router.post('/mate', (req, res, next) => { 103 | const { matronDragonId, patronDragonId } = req.body; 104 | 105 | if (matronDragonId === patronDragonId) { 106 | throw new Error('Cannot breed with the same dragon!'); 107 | } 108 | 109 | let matronDragon, patronDragon, patronSireValue; 110 | let matronAccountId, patronAccountId; 111 | 112 | getDragonWithTraits({ dragonId: patronDragonId }) 113 | .then(dragon => { 114 | if (!dragon.isPublic) { 115 | throw new Error('Dragon must be public'); 116 | } 117 | 118 | patronDragon = dragon; 119 | patronSireValue = dragon.sireValue; 120 | 121 | return getDragonWithTraits({ dragonId: matronDragonId }) 122 | }) 123 | .then(dragon => { 124 | matronDragon = dragon; 125 | 126 | return authenticatedAccount({ sessionString: req.cookies.sessionString }); 127 | }) 128 | .then(({ account, authenticated }) => { 129 | if (!authenticated) throw new Error('Unauthenticated'); 130 | 131 | if (patronSireValue > account.balance) { 132 | throw new Error('Sire value exceeds balance'); 133 | } 134 | 135 | matronAccountId = account.id; 136 | 137 | return AccountDragonTable.getDragonAccount({ dragonId: patronDragonId }); 138 | }) 139 | .then(({ accountId }) => { 140 | patronAccountId = accountId; 141 | 142 | if (matronAccountId === patronAccountId) { 143 | throw new Error('Cannot breed your own dragons!'); 144 | } 145 | 146 | const dragon = Breeder.breedDragon({ matron: matronDragon, patron: patronDragon }); 147 | 148 | return DragonTable.storeDragon(dragon); 149 | }) 150 | .then(({ dragonId }) => { 151 | Promise.all([ 152 | AccountTable.updateBalance({ 153 | accountId: matronAccountId, value: -patronSireValue 154 | }), 155 | AccountTable.updateBalance({ 156 | accountId: patronAccountId, value: patronSireValue 157 | }), 158 | AccountDragonTable.storeAccountDragon({ 159 | dragonId, accountId: matronAccountId 160 | }) 161 | ]).then(() => res.json({ message: 'success!' })) 162 | .catch(error => next(error)); 163 | }); 164 | }); 165 | 166 | module.exports = router; 167 | -------------------------------------------------------------------------------- /backend/app/api/generation.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('express'); 2 | 3 | const router = new Router(); 4 | 5 | router.get('/', (req, res) => { 6 | res.json({ generation: req.app.locals.engine.generation }); 7 | }); 8 | 9 | module.exports = router; -------------------------------------------------------------------------------- /backend/app/api/helper.js: -------------------------------------------------------------------------------- 1 | const Session = require('../account/session'); 2 | const AccountTable = require('../account/table'); 3 | const { hash } = require('../account/helper'); 4 | 5 | const setSession = ({ username, res, sessionId }) => { 6 | return new Promise((resolve, reject) => { 7 | let session, sessionString; 8 | 9 | if (sessionId) { 10 | sessionString = Session.sessionString({ username, id: sessionId }); 11 | 12 | setSessionCookie({ sessionString, res }); 13 | 14 | resolve({ message: 'session restored' }); 15 | } else { 16 | session = new Session({ username }); 17 | sessionString = session.toString(); 18 | 19 | AccountTable.updateSessionId({ 20 | sessionId: session.id, 21 | usernameHash: hash(username) 22 | }) 23 | .then(() => { 24 | setSessionCookie({ sessionString, res }); 25 | 26 | resolve({ message: 'session created' }); 27 | }) 28 | .catch(error => reject(error)); 29 | } 30 | }); 31 | } 32 | 33 | const setSessionCookie = ({ sessionString, res }) => { 34 | res.cookie('sessionString', sessionString, { 35 | expire: Date.now() + 3600000, 36 | httpOnly: true 37 | // secure: true // use with https 38 | }); 39 | }; 40 | 41 | const authenticatedAccount = ({ sessionString }) => { 42 | return new Promise((resolve, reject) => { 43 | if (!sessionString || !Session.verify(sessionString)) { 44 | const error = new Error('Invalid session'); 45 | 46 | error.statusCode = 400; 47 | 48 | return reject(error); 49 | } else { 50 | const { username, id } = Session.parse(sessionString); 51 | 52 | AccountTable.getAccount({ usernameHash: hash(username) }) 53 | .then(({ account }) => { 54 | const authenticated = account.sessionId === id; 55 | 56 | resolve({ account, authenticated, username }); 57 | }) 58 | .catch(error => reject(error)); 59 | } 60 | }); 61 | }; 62 | 63 | module.exports = { setSession, authenticatedAccount }; -------------------------------------------------------------------------------- /backend/app/config.js: -------------------------------------------------------------------------------- 1 | const SECONDS = 1000; 2 | const MINUTES = SECONDS * 60; 3 | const HOURS = MINUTES * 60; 4 | const DAYS = HOURS * 24; 5 | 6 | const REFRESH_RATE = 5; // units 7 | 8 | const STARTING_BALANCE = 50; 9 | 10 | module.exports = { 11 | SECONDS, 12 | MINUTES, 13 | HOURS, 14 | DAYS, 15 | REFRESH_RATE, 16 | STARTING_BALANCE 17 | }; 18 | -------------------------------------------------------------------------------- /backend/app/dragon/breeder.js: -------------------------------------------------------------------------------- 1 | const base64 = require('base-64'); 2 | const Dragon = require('./index'); 3 | 4 | class Breeder { 5 | static breedDragon({ matron, patron }) { 6 | const matronTraits = matron.traits; 7 | const patronTraits = patron.traits; 8 | 9 | const babyTraits = []; 10 | 11 | matronTraits.forEach(({ traitType, traitValue }) => { 12 | const matronTrait = traitValue; 13 | 14 | const patronTrait = patronTraits.find( 15 | trait => trait.traitType === traitType 16 | ).traitValue; 17 | 18 | babyTraits.push({ 19 | traitType, 20 | traitValue: Breeder.pickTrait({ matronTrait, patronTrait }) 21 | }); 22 | }); 23 | 24 | return new Dragon({ nickname: 'Unnamed baby', traits: babyTraits }); 25 | } 26 | 27 | // Two incoming traits: matronTrait and patronTrait 28 | // The matronTrait and patronTrait string values are encoded. 29 | // Both traits have their characters summed. 30 | // Get a range by adding both character sums. 31 | // Generate a random number, in that range. 32 | // If the number is less than the matron's character sum, pick matron. 33 | // Else, pick patron. 34 | static pickTrait({ matronTrait, patronTrait }) { 35 | if (matronTrait === patronTrait) return matronTrait; 36 | 37 | const matronTraitCharSum = Breeder.charSum(base64.encode(matronTrait)); 38 | const patronTraitCharSum = Breeder.charSum(base64.encode(patronTrait)); 39 | 40 | const randNum = Math.floor(Math.random() * (matronTraitCharSum + patronTraitCharSum)) 41 | 42 | return randNum < matronTraitCharSum ? matronTrait : patronTrait; 43 | } 44 | 45 | static charSum(string) { 46 | return string.split('').reduce( 47 | (sum, character) => sum += character.charCodeAt(), 48 | 0 49 | ); 50 | } 51 | } 52 | 53 | module.exports = Breeder; -------------------------------------------------------------------------------- /backend/app/dragon/helper.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | const DragonTable = require('./table'); 3 | const Dragon = require('./index'); 4 | 5 | const getDragonWithTraits = ({ dragonId }) => { 6 | return Promise.all([ 7 | DragonTable.getDragon({ dragonId }), 8 | new Promise((resolve, reject) => { 9 | pool.query( 10 | `SELECT "traitType", "traitValue" 11 | FROM trait 12 | INNER JOIN dragonTrait ON trait.id = dragonTrait."traitId" 13 | WHERE dragonTrait."dragonId" = $1`, 14 | [dragonId], 15 | (error, response) => { 16 | if (error) return reject(error); 17 | 18 | resolve(response.rows); 19 | } 20 | ) 21 | }) 22 | ]) 23 | .then(([dragon, dragonTraits]) => { 24 | return new Dragon({ ...dragon, dragonId, traits: dragonTraits }) 25 | }) 26 | .catch(error => console.error(error)); 27 | }; 28 | 29 | const getPublicDragons = () => { 30 | return new Promise((resolve, reject) => { 31 | pool.query( 32 | 'SELECT id FROM dragon WHERE "isPublic" = TRUE', 33 | (error, response) => { 34 | if (error) return reject(error); 35 | 36 | const publicDragonRows = response.rows; 37 | 38 | Promise.all( 39 | publicDragonRows.map( 40 | ({ id }) => getDragonWithTraits({ dragonId: id }) 41 | ) 42 | ).then(dragons => resolve({ dragons })) 43 | .catch(error => reject(error)); 44 | } 45 | ) 46 | }); 47 | } 48 | 49 | module.exports = { getDragonWithTraits, getPublicDragons }; -------------------------------------------------------------------------------- /backend/app/dragon/index.js: -------------------------------------------------------------------------------- 1 | const TRAITS = require('../../data/traits'); 2 | 3 | const DEFAULT_PROPERTIES = { 4 | dragonId: undefined, 5 | nickname: 'unnamed', 6 | generationId: undefined, 7 | isPublic: false, 8 | saleValue: 0, 9 | sireValue: 0, 10 | get birthdate() { 11 | return new Date() 12 | }, 13 | get randomTraits() { 14 | const traits = []; 15 | 16 | TRAITS.forEach(TRAIT => { 17 | const traitType = TRAIT.type; 18 | const traitValues = TRAIT.values; 19 | 20 | const traitValue = traitValues[ 21 | Math.floor(Math.random() * traitValues.length) 22 | ]; 23 | 24 | traits.push({ traitType, traitValue }); 25 | }); 26 | 27 | return traits; 28 | } 29 | } 30 | 31 | class Dragon { 32 | constructor({ 33 | dragonId, 34 | birthdate, 35 | nickname, 36 | traits, 37 | generationId, 38 | isPublic, 39 | saleValue, 40 | sireValue 41 | } = {}) { 42 | this.dragonId = dragonId || DEFAULT_PROPERTIES.dragonId; 43 | this.birthdate = birthdate || DEFAULT_PROPERTIES.birthdate; 44 | this.nickname = nickname || DEFAULT_PROPERTIES.nickname; 45 | this.traits = traits || DEFAULT_PROPERTIES.randomTraits; 46 | this.generationId = generationId || DEFAULT_PROPERTIES.generationId; 47 | this.isPublic = isPublic || DEFAULT_PROPERTIES.isPublic; 48 | this.saleValue = saleValue || DEFAULT_PROPERTIES.saleValue; 49 | this.sireValue = sireValue || DEFAULT_PROPERTIES.sireValue; 50 | } 51 | } 52 | 53 | module.exports = Dragon; -------------------------------------------------------------------------------- /backend/app/dragon/table.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | const DragonTraitTable = require('../dragonTrait/table'); 3 | 4 | class DragonTable { 5 | static storeDragon(dragon) { 6 | const { birthdate, nickname, generationId, isPublic, saleValue, sireValue } = dragon; 7 | 8 | return new Promise((resolve, reject) => { 9 | pool.query( 10 | `INSERT INTO dragon(birthdate, nickname, "generationId", "isPublic", "saleValue", "sireValue") 11 | VALUES($1, $2, $3, $4, $5, $6) RETURNING id`, 12 | [birthdate, nickname, generationId, isPublic, saleValue, sireValue], 13 | (error, response) => { 14 | if (error) return reject(error); 15 | 16 | const dragonId = response.rows[0].id; 17 | 18 | Promise.all(dragon.traits.map(({ traitType, traitValue }) => { 19 | return DragonTraitTable.storeDragonTrait({ 20 | dragonId, traitType, traitValue 21 | }); 22 | })) 23 | .then(() => resolve({ dragonId })) 24 | .catch(error => reject(error)); 25 | } 26 | ) 27 | }); 28 | } 29 | 30 | static getDragon({ dragonId }) { 31 | return new Promise((resolve, reject) => { 32 | pool.query( 33 | `SELECT birthdate, nickname, "generationId", "isPublic", "saleValue", "sireValue", "sireValue" 34 | FROM dragon 35 | WHERE dragon.id = $1`, 36 | [dragonId], 37 | (error, response) => { 38 | if (error) return reject(error); 39 | 40 | if (response.rows.length === 0) return reject(new Error('no dragon')); 41 | 42 | resolve(response.rows[0]); 43 | } 44 | ) 45 | }); 46 | } 47 | 48 | static updateDragon({ dragonId, nickname, isPublic, saleValue, sireValue }) { 49 | const settingsMap = { nickname, isPublic, saleValue, sireValue }; 50 | 51 | const validQueries = Object.entries(settingsMap).filter(([settingKey, settingValue]) => { 52 | if (settingValue !== undefined) { 53 | return new Promise((resolve, reject) => { 54 | pool.query( 55 | `UPDATE dragon SET "${settingKey}" = $1 WHERE id = $2`, 56 | [settingValue, dragonId], 57 | (error, response) => { 58 | if (error) return reject(error); 59 | 60 | resolve(); 61 | } 62 | ) 63 | }); 64 | } 65 | }); 66 | 67 | return Promise.all(validQueries); 68 | } 69 | } 70 | 71 | module.exports = DragonTable; 72 | -------------------------------------------------------------------------------- /backend/app/dragonTrait/table.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | const TraitTable = require('../trait/table'); 3 | 4 | class DragonTraitTable { 5 | static storeDragonTrait({ dragonId, traitType, traitValue }) { 6 | return new Promise((resolve, reject) => { 7 | TraitTable.getTraitId({ traitType, traitValue }) 8 | .then(({ traitId }) => { 9 | pool.query( 10 | 'INSERT INTO dragonTrait("traitId", "dragonId") VALUES($1, $2)', 11 | [traitId, dragonId], 12 | (error, response) => { 13 | if (error) return reject(error); 14 | 15 | resolve(); 16 | } 17 | ) 18 | }); 19 | }); 20 | } 21 | } 22 | 23 | module.exports = DragonTraitTable; -------------------------------------------------------------------------------- /backend/app/generation/engine.js: -------------------------------------------------------------------------------- 1 | const Generation = require('./index'); 2 | const GenerationTable = require('./table'); 3 | 4 | class GenerationEngine { 5 | constructor() { 6 | this.generation = null; 7 | this.timer = null; 8 | } 9 | 10 | start() { 11 | this.buildNewGeneration(); 12 | } 13 | 14 | stop() { 15 | clearTimeout(this.timer); 16 | } 17 | 18 | buildNewGeneration() { 19 | const generation = new Generation(); 20 | 21 | GenerationTable.storeGeneration(generation) 22 | .then(({ generationId }) => { 23 | this.generation = generation; 24 | 25 | this.generation.generationId = generationId; 26 | 27 | console.log('new generation', this.generation); 28 | 29 | this.timer = setTimeout( 30 | () => this.buildNewGeneration(), 31 | this.generation.expiration.getTime() - Date.now() 32 | ); 33 | }) 34 | .catch(error => console.error(error)); 35 | } 36 | } 37 | 38 | module.exports = GenerationEngine; 39 | -------------------------------------------------------------------------------- /backend/app/generation/index.js: -------------------------------------------------------------------------------- 1 | const Dragon = require('../dragon'); 2 | const { REFRESH_RATE, SECONDS } = require('../config'); 3 | 4 | const refreshRate = REFRESH_RATE * SECONDS; 5 | 6 | class Generation { 7 | constructor() { 8 | this.accountIds = new Set(); 9 | this.expiration = this.calculateExpiration(); 10 | this.generationId = undefined; 11 | } 12 | 13 | calculateExpiration() { 14 | const expirationPeriod = Math.floor(Math.random() * (refreshRate/2)); 15 | 16 | const msUntilExpiration = Math.random() < 0.5 ? 17 | refreshRate - expirationPeriod : 18 | refreshRate + expirationPeriod; 19 | 20 | return new Date(Date.now() + msUntilExpiration); 21 | } 22 | 23 | newDragon({ accountId }) { 24 | if (Date.now() > this.expiration) { 25 | throw new Error(`This generation expired on ${this.expiration}`); 26 | } 27 | 28 | if (this.accountIds.has(accountId)) { 29 | throw new Error('You already have a dragon from this generation'); 30 | } 31 | 32 | this.accountIds.add(accountId); 33 | 34 | return new Dragon({ generationId: this.generationId }); 35 | } 36 | } 37 | 38 | module.exports = Generation; 39 | -------------------------------------------------------------------------------- /backend/app/generation/table.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | 3 | class GenerationTable { 4 | static storeGeneration(generation) { 5 | return new Promise((resolve, reject) => { 6 | pool.query( 7 | 'INSERT INTO generation(expiration) VALUES($1) RETURNING id', 8 | [generation.expiration], 9 | (error, response) => { 10 | if (error) return reject(error); 11 | 12 | const generationId = response.rows[0].id; 13 | 14 | resolve({ generationId }); 15 | } 16 | ); 17 | }); 18 | } 19 | } 20 | 21 | module.exports = GenerationTable; -------------------------------------------------------------------------------- /backend/app/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const bodyParser = require('body-parser'); 4 | const cookieParser = require('cookie-parser'); 5 | const GenerationEngine = require('./generation/engine'); 6 | const dragonRouter = require('./api/dragon'); 7 | const generationRouter = require('./api/generation'); 8 | const accountRouter = require('./api/account'); 9 | 10 | const app = express(); 11 | const engine = new GenerationEngine(); 12 | 13 | app.locals.engine = engine; 14 | 15 | app.use(cors({ origin: 'http://localhost:1234', credentials: true })); 16 | app.use(bodyParser.json()); 17 | app.use(cookieParser()); 18 | 19 | app.use('/account', accountRouter); 20 | app.use('/dragon', dragonRouter); 21 | app.use('/generation', generationRouter); 22 | 23 | app.use((err, req, res, next) => { 24 | const statusCode = err.statusCode || 500; 25 | 26 | res.status(statusCode).json({ 27 | type: 'error', message: err.message 28 | }) 29 | }); 30 | 31 | engine.start(); 32 | 33 | module.exports = app; -------------------------------------------------------------------------------- /backend/app/trait/table.js: -------------------------------------------------------------------------------- 1 | const pool = require('../../databasePool'); 2 | 3 | class TraitTable { 4 | static getTraitId({ traitType, traitValue }) { 5 | return new Promise((resolve, reject) => { 6 | pool.query( 7 | 'SELECT id FROM trait WHERE "traitType" = $1 AND "traitValue" = $2', 8 | [traitType, traitValue], 9 | (error, response) => { 10 | if (error) return reject(error); 11 | 12 | resolve({ traitId: response.rows[0].id }); 13 | } 14 | ) 15 | }); 16 | } 17 | } 18 | 19 | module.exports = TraitTable; -------------------------------------------------------------------------------- /backend/bin/configure_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PGPASSWORD='node_password' 4 | 5 | echo "Configuring dragonstackdb" 6 | 7 | dropdb -U node_user dragonstackdb 8 | createdb -U node_user dragonstackdb 9 | 10 | psql -U node_user dragonstackdb < ./bin/sql/account.sql 11 | psql -U node_user dragonstackdb < ./bin/sql/generation.sql 12 | psql -U node_user dragonstackdb < ./bin/sql/dragon.sql 13 | psql -U node_user dragonstackdb < ./bin/sql/trait.sql 14 | psql -U node_user dragonstackdb < ./bin/sql/dragonTrait.sql 15 | psql -U node_user dragonstackdb < ./bin/sql/accountDragon.sql 16 | 17 | node ./bin/insertTraits.js 18 | 19 | echo "dragonstackdb configured" -------------------------------------------------------------------------------- /backend/bin/insertTraits.js: -------------------------------------------------------------------------------- 1 | const pool = require('../databasePool'); 2 | const TRAITS = require('../data/traits'); 3 | 4 | TRAITS.forEach(TRAIT => { 5 | const traitType = TRAIT.type; 6 | const traitValues = TRAIT.values; 7 | 8 | traitValues.forEach(traitValue => { 9 | pool.query( 10 | `INSERT INTO trait("traitType", "traitValue") 11 | VALUES($1, $2) 12 | RETURNING id`, 13 | [traitType, traitValue], 14 | (error, response) => { 15 | if (error) console.error(error); 16 | 17 | const traitId = response.rows[0].id; 18 | 19 | console.log(`Inserted trait - id: ${traitId}`); 20 | } 21 | ); 22 | }); 23 | }); -------------------------------------------------------------------------------- /backend/bin/server.js: -------------------------------------------------------------------------------- 1 | const app = require('../app'); 2 | 3 | const port = 3000; 4 | 5 | app.listen(port, () => console.log(`listening on port ${port}`)); -------------------------------------------------------------------------------- /backend/bin/sql/account.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE account( 2 | id SERIAL PRIMARY KEY, 3 | "usernameHash" CHARACTER(64), 4 | "passwordHash" CHARACTER(64), 5 | "sessionId" CHARACTER(36), 6 | balance INTEGER NOT NULL 7 | ); -------------------------------------------------------------------------------- /backend/bin/sql/accountDragon.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE accountDragon( 2 | "accountId" INTEGER REFERENCES account(id), 3 | "dragonId" INTEGER REFERENCES dragon(id), 4 | PRIMARY KEY ("accountId", "dragonId") 5 | ); -------------------------------------------------------------------------------- /backend/bin/sql/dragon.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE dragon( 2 | id SERIAL PRIMARY KEY, 3 | birthdate TIMESTAMP NOT NULL, 4 | nickname VARCHAR(64), 5 | "isPublic" BOOLEAN NOT NULL, 6 | "saleValue" INTEGER NOT NULL, 7 | "sireValue" INTEGER NOT NULL, 8 | "generationId" INTEGER, 9 | FOREIGN KEY ("generationId") REFERENCES generation(id) 10 | ); -------------------------------------------------------------------------------- /backend/bin/sql/dragonTrait.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE dragonTrait( 2 | "traitId" INTEGER, 3 | "dragonId" INTEGER, 4 | FOREIGN KEY ("traitId") REFERENCES trait(id), 5 | FOREIGN KEY ("dragonId") REFERENCES dragon(id) 6 | ); -------------------------------------------------------------------------------- /backend/bin/sql/generation.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE generation( 2 | id SERIAL PRIMARY KEY, 3 | expiration TIMESTAMP NOT NULL 4 | ); -------------------------------------------------------------------------------- /backend/bin/sql/trait.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE trait( 2 | id SERIAL PRIMARY KEY, 3 | "traitType" VARCHAR NOT NULL, 4 | "traitValue" VARCHAR NOT NULL 5 | ); -------------------------------------------------------------------------------- /backend/data/traits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "backgroundColor", 4 | "values": [ 5 | "black", 6 | "white", 7 | "green", 8 | "blue" 9 | ] 10 | }, 11 | { 12 | "type": "pattern", 13 | "values": [ 14 | "plain", 15 | "striped", 16 | "spotted", 17 | "patchy" 18 | ] 19 | }, 20 | { 21 | "type": "build", 22 | "values": [ 23 | "slender", 24 | "stocky", 25 | "sporty", 26 | "skinny" 27 | ] 28 | }, 29 | { 30 | "type": "size", 31 | "values": [ 32 | "small", 33 | "medium", 34 | "large", 35 | "enormous" 36 | ] 37 | } 38 | ] -------------------------------------------------------------------------------- /backend/databasePool.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | const databaseConfiguration = require('./secrets/databaseConfiguration'); 3 | 4 | const pool = new Pool(databaseConfiguration); 5 | 6 | module.exports = pool; 7 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node ./bin/server", 8 | "dev": "nodemon ./bin/server", 9 | "configure-dev": "npm run configure && npm run dev", 10 | "configure": "./bin/configure_db.sh" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "nodemon": "^1.17.5" 16 | }, 17 | "dependencies": { 18 | "base-64": "^0.1.0", 19 | "body-parser": "^1.18.3", 20 | "cookie-parser": "^1.4.3", 21 | "cors": "^2.8.4", 22 | "crypto-js": "^3.1.9-1", 23 | "express": "^4.16.3", 24 | "pg": "^7.4.3", 25 | "uuid": "^3.3.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/secrets/databaseConfiguration.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | user: 'node_user', 3 | host: 'localhost', 4 | database: 'dragonstackdb', 5 | password: 'node_password', 6 | port: 5432 7 | }; 8 | -------------------------------------------------------------------------------- /backend/secrets/index.js: -------------------------------------------------------------------------------- 1 | const APP_SECRET = 'foo123!123foo'; 2 | 3 | module.exports = { APP_SECRET } -------------------------------------------------------------------------------- /course_logo_udemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/course_logo_udemy.png -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"], 3 | "plugins": ["transform-class-properties", "transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /frontend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-regex": { 8 | "version": "2.1.1", 9 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 10 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 11 | "dev": true 12 | }, 13 | "ansi-styles": { 14 | "version": "2.2.1", 15 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 16 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 17 | "dev": true 18 | }, 19 | "asap": { 20 | "version": "2.0.6", 21 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 22 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 23 | }, 24 | "babel-code-frame": { 25 | "version": "6.26.0", 26 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 27 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 28 | "dev": true, 29 | "requires": { 30 | "chalk": "1.1.3", 31 | "esutils": "2.0.2", 32 | "js-tokens": "3.0.2" 33 | }, 34 | "dependencies": { 35 | "js-tokens": { 36 | "version": "3.0.2", 37 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 38 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 39 | "dev": true 40 | } 41 | } 42 | }, 43 | "babel-core": { 44 | "version": "6.26.3", 45 | "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", 46 | "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", 47 | "dev": true, 48 | "requires": { 49 | "babel-code-frame": "6.26.0", 50 | "babel-generator": "6.26.1", 51 | "babel-helpers": "6.24.1", 52 | "babel-messages": "6.23.0", 53 | "babel-register": "6.26.0", 54 | "babel-runtime": "6.26.0", 55 | "babel-template": "6.26.0", 56 | "babel-traverse": "6.26.0", 57 | "babel-types": "6.26.0", 58 | "babylon": "6.18.0", 59 | "convert-source-map": "1.5.1", 60 | "debug": "2.6.9", 61 | "json5": "0.5.1", 62 | "lodash": "4.17.10", 63 | "minimatch": "3.0.4", 64 | "path-is-absolute": "1.0.1", 65 | "private": "0.1.8", 66 | "slash": "1.0.0", 67 | "source-map": "0.5.7" 68 | } 69 | }, 70 | "babel-generator": { 71 | "version": "6.26.1", 72 | "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", 73 | "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", 74 | "dev": true, 75 | "requires": { 76 | "babel-messages": "6.23.0", 77 | "babel-runtime": "6.26.0", 78 | "babel-types": "6.26.0", 79 | "detect-indent": "4.0.0", 80 | "jsesc": "1.3.0", 81 | "lodash": "4.17.10", 82 | "source-map": "0.5.7", 83 | "trim-right": "1.0.1" 84 | }, 85 | "dependencies": { 86 | "jsesc": { 87 | "version": "1.3.0", 88 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", 89 | "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", 90 | "dev": true 91 | } 92 | } 93 | }, 94 | "babel-helper-builder-binary-assignment-operator-visitor": { 95 | "version": "6.24.1", 96 | "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", 97 | "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", 98 | "dev": true, 99 | "requires": { 100 | "babel-helper-explode-assignable-expression": "6.24.1", 101 | "babel-runtime": "6.26.0", 102 | "babel-types": "6.26.0" 103 | } 104 | }, 105 | "babel-helper-builder-react-jsx": { 106 | "version": "6.26.0", 107 | "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", 108 | "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", 109 | "dev": true, 110 | "requires": { 111 | "babel-runtime": "6.26.0", 112 | "babel-types": "6.26.0", 113 | "esutils": "2.0.2" 114 | } 115 | }, 116 | "babel-helper-call-delegate": { 117 | "version": "6.24.1", 118 | "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", 119 | "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", 120 | "dev": true, 121 | "requires": { 122 | "babel-helper-hoist-variables": "6.24.1", 123 | "babel-runtime": "6.26.0", 124 | "babel-traverse": "6.26.0", 125 | "babel-types": "6.26.0" 126 | } 127 | }, 128 | "babel-helper-define-map": { 129 | "version": "6.26.0", 130 | "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", 131 | "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", 132 | "dev": true, 133 | "requires": { 134 | "babel-helper-function-name": "6.24.1", 135 | "babel-runtime": "6.26.0", 136 | "babel-types": "6.26.0", 137 | "lodash": "4.17.10" 138 | } 139 | }, 140 | "babel-helper-explode-assignable-expression": { 141 | "version": "6.24.1", 142 | "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", 143 | "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", 144 | "dev": true, 145 | "requires": { 146 | "babel-runtime": "6.26.0", 147 | "babel-traverse": "6.26.0", 148 | "babel-types": "6.26.0" 149 | } 150 | }, 151 | "babel-helper-function-name": { 152 | "version": "6.24.1", 153 | "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", 154 | "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", 155 | "dev": true, 156 | "requires": { 157 | "babel-helper-get-function-arity": "6.24.1", 158 | "babel-runtime": "6.26.0", 159 | "babel-template": "6.26.0", 160 | "babel-traverse": "6.26.0", 161 | "babel-types": "6.26.0" 162 | } 163 | }, 164 | "babel-helper-get-function-arity": { 165 | "version": "6.24.1", 166 | "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", 167 | "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", 168 | "dev": true, 169 | "requires": { 170 | "babel-runtime": "6.26.0", 171 | "babel-types": "6.26.0" 172 | } 173 | }, 174 | "babel-helper-hoist-variables": { 175 | "version": "6.24.1", 176 | "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", 177 | "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", 178 | "dev": true, 179 | "requires": { 180 | "babel-runtime": "6.26.0", 181 | "babel-types": "6.26.0" 182 | } 183 | }, 184 | "babel-helper-optimise-call-expression": { 185 | "version": "6.24.1", 186 | "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", 187 | "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", 188 | "dev": true, 189 | "requires": { 190 | "babel-runtime": "6.26.0", 191 | "babel-types": "6.26.0" 192 | } 193 | }, 194 | "babel-helper-regex": { 195 | "version": "6.26.0", 196 | "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", 197 | "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", 198 | "dev": true, 199 | "requires": { 200 | "babel-runtime": "6.26.0", 201 | "babel-types": "6.26.0", 202 | "lodash": "4.17.10" 203 | } 204 | }, 205 | "babel-helper-remap-async-to-generator": { 206 | "version": "6.24.1", 207 | "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", 208 | "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", 209 | "dev": true, 210 | "requires": { 211 | "babel-helper-function-name": "6.24.1", 212 | "babel-runtime": "6.26.0", 213 | "babel-template": "6.26.0", 214 | "babel-traverse": "6.26.0", 215 | "babel-types": "6.26.0" 216 | } 217 | }, 218 | "babel-helper-replace-supers": { 219 | "version": "6.24.1", 220 | "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", 221 | "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", 222 | "dev": true, 223 | "requires": { 224 | "babel-helper-optimise-call-expression": "6.24.1", 225 | "babel-messages": "6.23.0", 226 | "babel-runtime": "6.26.0", 227 | "babel-template": "6.26.0", 228 | "babel-traverse": "6.26.0", 229 | "babel-types": "6.26.0" 230 | } 231 | }, 232 | "babel-helpers": { 233 | "version": "6.24.1", 234 | "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", 235 | "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", 236 | "dev": true, 237 | "requires": { 238 | "babel-runtime": "6.26.0", 239 | "babel-template": "6.26.0" 240 | } 241 | }, 242 | "babel-messages": { 243 | "version": "6.23.0", 244 | "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", 245 | "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", 246 | "dev": true, 247 | "requires": { 248 | "babel-runtime": "6.26.0" 249 | } 250 | }, 251 | "babel-plugin-check-es2015-constants": { 252 | "version": "6.22.0", 253 | "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", 254 | "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", 255 | "dev": true, 256 | "requires": { 257 | "babel-runtime": "6.26.0" 258 | } 259 | }, 260 | "babel-plugin-syntax-async-functions": { 261 | "version": "6.13.0", 262 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", 263 | "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", 264 | "dev": true 265 | }, 266 | "babel-plugin-syntax-class-properties": { 267 | "version": "6.13.0", 268 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", 269 | "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", 270 | "dev": true 271 | }, 272 | "babel-plugin-syntax-exponentiation-operator": { 273 | "version": "6.13.0", 274 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", 275 | "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", 276 | "dev": true 277 | }, 278 | "babel-plugin-syntax-flow": { 279 | "version": "6.18.0", 280 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", 281 | "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", 282 | "dev": true 283 | }, 284 | "babel-plugin-syntax-jsx": { 285 | "version": "6.18.0", 286 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", 287 | "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", 288 | "dev": true 289 | }, 290 | "babel-plugin-syntax-object-rest-spread": { 291 | "version": "6.13.0", 292 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", 293 | "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", 294 | "dev": true 295 | }, 296 | "babel-plugin-syntax-trailing-function-commas": { 297 | "version": "6.22.0", 298 | "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", 299 | "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", 300 | "dev": true 301 | }, 302 | "babel-plugin-transform-async-to-generator": { 303 | "version": "6.24.1", 304 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", 305 | "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", 306 | "dev": true, 307 | "requires": { 308 | "babel-helper-remap-async-to-generator": "6.24.1", 309 | "babel-plugin-syntax-async-functions": "6.13.0", 310 | "babel-runtime": "6.26.0" 311 | } 312 | }, 313 | "babel-plugin-transform-class-properties": { 314 | "version": "6.24.1", 315 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", 316 | "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", 317 | "dev": true, 318 | "requires": { 319 | "babel-helper-function-name": "6.24.1", 320 | "babel-plugin-syntax-class-properties": "6.13.0", 321 | "babel-runtime": "6.26.0", 322 | "babel-template": "6.26.0" 323 | } 324 | }, 325 | "babel-plugin-transform-es2015-arrow-functions": { 326 | "version": "6.22.0", 327 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", 328 | "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", 329 | "dev": true, 330 | "requires": { 331 | "babel-runtime": "6.26.0" 332 | } 333 | }, 334 | "babel-plugin-transform-es2015-block-scoped-functions": { 335 | "version": "6.22.0", 336 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", 337 | "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", 338 | "dev": true, 339 | "requires": { 340 | "babel-runtime": "6.26.0" 341 | } 342 | }, 343 | "babel-plugin-transform-es2015-block-scoping": { 344 | "version": "6.26.0", 345 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", 346 | "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", 347 | "dev": true, 348 | "requires": { 349 | "babel-runtime": "6.26.0", 350 | "babel-template": "6.26.0", 351 | "babel-traverse": "6.26.0", 352 | "babel-types": "6.26.0", 353 | "lodash": "4.17.10" 354 | } 355 | }, 356 | "babel-plugin-transform-es2015-classes": { 357 | "version": "6.24.1", 358 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", 359 | "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", 360 | "dev": true, 361 | "requires": { 362 | "babel-helper-define-map": "6.26.0", 363 | "babel-helper-function-name": "6.24.1", 364 | "babel-helper-optimise-call-expression": "6.24.1", 365 | "babel-helper-replace-supers": "6.24.1", 366 | "babel-messages": "6.23.0", 367 | "babel-runtime": "6.26.0", 368 | "babel-template": "6.26.0", 369 | "babel-traverse": "6.26.0", 370 | "babel-types": "6.26.0" 371 | } 372 | }, 373 | "babel-plugin-transform-es2015-computed-properties": { 374 | "version": "6.24.1", 375 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", 376 | "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", 377 | "dev": true, 378 | "requires": { 379 | "babel-runtime": "6.26.0", 380 | "babel-template": "6.26.0" 381 | } 382 | }, 383 | "babel-plugin-transform-es2015-destructuring": { 384 | "version": "6.23.0", 385 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", 386 | "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", 387 | "dev": true, 388 | "requires": { 389 | "babel-runtime": "6.26.0" 390 | } 391 | }, 392 | "babel-plugin-transform-es2015-duplicate-keys": { 393 | "version": "6.24.1", 394 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", 395 | "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", 396 | "dev": true, 397 | "requires": { 398 | "babel-runtime": "6.26.0", 399 | "babel-types": "6.26.0" 400 | } 401 | }, 402 | "babel-plugin-transform-es2015-for-of": { 403 | "version": "6.23.0", 404 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", 405 | "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", 406 | "dev": true, 407 | "requires": { 408 | "babel-runtime": "6.26.0" 409 | } 410 | }, 411 | "babel-plugin-transform-es2015-function-name": { 412 | "version": "6.24.1", 413 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", 414 | "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", 415 | "dev": true, 416 | "requires": { 417 | "babel-helper-function-name": "6.24.1", 418 | "babel-runtime": "6.26.0", 419 | "babel-types": "6.26.0" 420 | } 421 | }, 422 | "babel-plugin-transform-es2015-literals": { 423 | "version": "6.22.0", 424 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", 425 | "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", 426 | "dev": true, 427 | "requires": { 428 | "babel-runtime": "6.26.0" 429 | } 430 | }, 431 | "babel-plugin-transform-es2015-modules-amd": { 432 | "version": "6.24.1", 433 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", 434 | "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", 435 | "dev": true, 436 | "requires": { 437 | "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", 438 | "babel-runtime": "6.26.0", 439 | "babel-template": "6.26.0" 440 | } 441 | }, 442 | "babel-plugin-transform-es2015-modules-commonjs": { 443 | "version": "6.26.2", 444 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", 445 | "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", 446 | "dev": true, 447 | "requires": { 448 | "babel-plugin-transform-strict-mode": "6.24.1", 449 | "babel-runtime": "6.26.0", 450 | "babel-template": "6.26.0", 451 | "babel-types": "6.26.0" 452 | } 453 | }, 454 | "babel-plugin-transform-es2015-modules-systemjs": { 455 | "version": "6.24.1", 456 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", 457 | "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", 458 | "dev": true, 459 | "requires": { 460 | "babel-helper-hoist-variables": "6.24.1", 461 | "babel-runtime": "6.26.0", 462 | "babel-template": "6.26.0" 463 | } 464 | }, 465 | "babel-plugin-transform-es2015-modules-umd": { 466 | "version": "6.24.1", 467 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", 468 | "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", 469 | "dev": true, 470 | "requires": { 471 | "babel-plugin-transform-es2015-modules-amd": "6.24.1", 472 | "babel-runtime": "6.26.0", 473 | "babel-template": "6.26.0" 474 | } 475 | }, 476 | "babel-plugin-transform-es2015-object-super": { 477 | "version": "6.24.1", 478 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", 479 | "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", 480 | "dev": true, 481 | "requires": { 482 | "babel-helper-replace-supers": "6.24.1", 483 | "babel-runtime": "6.26.0" 484 | } 485 | }, 486 | "babel-plugin-transform-es2015-parameters": { 487 | "version": "6.24.1", 488 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", 489 | "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", 490 | "dev": true, 491 | "requires": { 492 | "babel-helper-call-delegate": "6.24.1", 493 | "babel-helper-get-function-arity": "6.24.1", 494 | "babel-runtime": "6.26.0", 495 | "babel-template": "6.26.0", 496 | "babel-traverse": "6.26.0", 497 | "babel-types": "6.26.0" 498 | } 499 | }, 500 | "babel-plugin-transform-es2015-shorthand-properties": { 501 | "version": "6.24.1", 502 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", 503 | "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", 504 | "dev": true, 505 | "requires": { 506 | "babel-runtime": "6.26.0", 507 | "babel-types": "6.26.0" 508 | } 509 | }, 510 | "babel-plugin-transform-es2015-spread": { 511 | "version": "6.22.0", 512 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", 513 | "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", 514 | "dev": true, 515 | "requires": { 516 | "babel-runtime": "6.26.0" 517 | } 518 | }, 519 | "babel-plugin-transform-es2015-sticky-regex": { 520 | "version": "6.24.1", 521 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", 522 | "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", 523 | "dev": true, 524 | "requires": { 525 | "babel-helper-regex": "6.26.0", 526 | "babel-runtime": "6.26.0", 527 | "babel-types": "6.26.0" 528 | } 529 | }, 530 | "babel-plugin-transform-es2015-template-literals": { 531 | "version": "6.22.0", 532 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", 533 | "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", 534 | "dev": true, 535 | "requires": { 536 | "babel-runtime": "6.26.0" 537 | } 538 | }, 539 | "babel-plugin-transform-es2015-typeof-symbol": { 540 | "version": "6.23.0", 541 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", 542 | "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", 543 | "dev": true, 544 | "requires": { 545 | "babel-runtime": "6.26.0" 546 | } 547 | }, 548 | "babel-plugin-transform-es2015-unicode-regex": { 549 | "version": "6.24.1", 550 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", 551 | "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", 552 | "dev": true, 553 | "requires": { 554 | "babel-helper-regex": "6.26.0", 555 | "babel-runtime": "6.26.0", 556 | "regexpu-core": "2.0.0" 557 | } 558 | }, 559 | "babel-plugin-transform-exponentiation-operator": { 560 | "version": "6.24.1", 561 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", 562 | "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", 563 | "dev": true, 564 | "requires": { 565 | "babel-helper-builder-binary-assignment-operator-visitor": "6.24.1", 566 | "babel-plugin-syntax-exponentiation-operator": "6.13.0", 567 | "babel-runtime": "6.26.0" 568 | } 569 | }, 570 | "babel-plugin-transform-flow-strip-types": { 571 | "version": "6.22.0", 572 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", 573 | "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", 574 | "dev": true, 575 | "requires": { 576 | "babel-plugin-syntax-flow": "6.18.0", 577 | "babel-runtime": "6.26.0" 578 | } 579 | }, 580 | "babel-plugin-transform-object-rest-spread": { 581 | "version": "6.26.0", 582 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", 583 | "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", 584 | "dev": true, 585 | "requires": { 586 | "babel-plugin-syntax-object-rest-spread": "6.13.0", 587 | "babel-runtime": "6.26.0" 588 | } 589 | }, 590 | "babel-plugin-transform-react-display-name": { 591 | "version": "6.25.0", 592 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", 593 | "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", 594 | "dev": true, 595 | "requires": { 596 | "babel-runtime": "6.26.0" 597 | } 598 | }, 599 | "babel-plugin-transform-react-jsx": { 600 | "version": "6.24.1", 601 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", 602 | "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", 603 | "dev": true, 604 | "requires": { 605 | "babel-helper-builder-react-jsx": "6.26.0", 606 | "babel-plugin-syntax-jsx": "6.18.0", 607 | "babel-runtime": "6.26.0" 608 | } 609 | }, 610 | "babel-plugin-transform-react-jsx-self": { 611 | "version": "6.22.0", 612 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", 613 | "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", 614 | "dev": true, 615 | "requires": { 616 | "babel-plugin-syntax-jsx": "6.18.0", 617 | "babel-runtime": "6.26.0" 618 | } 619 | }, 620 | "babel-plugin-transform-react-jsx-source": { 621 | "version": "6.22.0", 622 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", 623 | "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", 624 | "dev": true, 625 | "requires": { 626 | "babel-plugin-syntax-jsx": "6.18.0", 627 | "babel-runtime": "6.26.0" 628 | } 629 | }, 630 | "babel-plugin-transform-regenerator": { 631 | "version": "6.26.0", 632 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", 633 | "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", 634 | "dev": true, 635 | "requires": { 636 | "regenerator-transform": "0.10.1" 637 | } 638 | }, 639 | "babel-plugin-transform-strict-mode": { 640 | "version": "6.24.1", 641 | "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", 642 | "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", 643 | "dev": true, 644 | "requires": { 645 | "babel-runtime": "6.26.0", 646 | "babel-types": "6.26.0" 647 | } 648 | }, 649 | "babel-preset-env": { 650 | "version": "1.6.1", 651 | "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", 652 | "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", 653 | "dev": true, 654 | "requires": { 655 | "babel-plugin-check-es2015-constants": "6.22.0", 656 | "babel-plugin-syntax-trailing-function-commas": "6.22.0", 657 | "babel-plugin-transform-async-to-generator": "6.24.1", 658 | "babel-plugin-transform-es2015-arrow-functions": "6.22.0", 659 | "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", 660 | "babel-plugin-transform-es2015-block-scoping": "6.26.0", 661 | "babel-plugin-transform-es2015-classes": "6.24.1", 662 | "babel-plugin-transform-es2015-computed-properties": "6.24.1", 663 | "babel-plugin-transform-es2015-destructuring": "6.23.0", 664 | "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", 665 | "babel-plugin-transform-es2015-for-of": "6.23.0", 666 | "babel-plugin-transform-es2015-function-name": "6.24.1", 667 | "babel-plugin-transform-es2015-literals": "6.22.0", 668 | "babel-plugin-transform-es2015-modules-amd": "6.24.1", 669 | "babel-plugin-transform-es2015-modules-commonjs": "6.26.2", 670 | "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", 671 | "babel-plugin-transform-es2015-modules-umd": "6.24.1", 672 | "babel-plugin-transform-es2015-object-super": "6.24.1", 673 | "babel-plugin-transform-es2015-parameters": "6.24.1", 674 | "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", 675 | "babel-plugin-transform-es2015-spread": "6.22.0", 676 | "babel-plugin-transform-es2015-sticky-regex": "6.24.1", 677 | "babel-plugin-transform-es2015-template-literals": "6.22.0", 678 | "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", 679 | "babel-plugin-transform-es2015-unicode-regex": "6.24.1", 680 | "babel-plugin-transform-exponentiation-operator": "6.24.1", 681 | "babel-plugin-transform-regenerator": "6.26.0", 682 | "browserslist": "2.11.3", 683 | "invariant": "2.2.4", 684 | "semver": "5.5.0" 685 | } 686 | }, 687 | "babel-preset-flow": { 688 | "version": "6.23.0", 689 | "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", 690 | "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", 691 | "dev": true, 692 | "requires": { 693 | "babel-plugin-transform-flow-strip-types": "6.22.0" 694 | } 695 | }, 696 | "babel-preset-react": { 697 | "version": "6.24.1", 698 | "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", 699 | "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", 700 | "dev": true, 701 | "requires": { 702 | "babel-plugin-syntax-jsx": "6.18.0", 703 | "babel-plugin-transform-react-display-name": "6.25.0", 704 | "babel-plugin-transform-react-jsx": "6.24.1", 705 | "babel-plugin-transform-react-jsx-self": "6.22.0", 706 | "babel-plugin-transform-react-jsx-source": "6.22.0", 707 | "babel-preset-flow": "6.23.0" 708 | } 709 | }, 710 | "babel-register": { 711 | "version": "6.26.0", 712 | "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", 713 | "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", 714 | "dev": true, 715 | "requires": { 716 | "babel-core": "6.26.3", 717 | "babel-runtime": "6.26.0", 718 | "core-js": "2.5.7", 719 | "home-or-tmp": "2.0.0", 720 | "lodash": "4.17.10", 721 | "mkdirp": "0.5.1", 722 | "source-map-support": "0.4.18" 723 | }, 724 | "dependencies": { 725 | "core-js": { 726 | "version": "2.5.7", 727 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", 728 | "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", 729 | "dev": true 730 | } 731 | } 732 | }, 733 | "babel-runtime": { 734 | "version": "6.26.0", 735 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 736 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 737 | "requires": { 738 | "core-js": "2.5.7", 739 | "regenerator-runtime": "0.11.1" 740 | }, 741 | "dependencies": { 742 | "core-js": { 743 | "version": "2.5.7", 744 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", 745 | "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" 746 | } 747 | } 748 | }, 749 | "babel-template": { 750 | "version": "6.26.0", 751 | "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", 752 | "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", 753 | "dev": true, 754 | "requires": { 755 | "babel-runtime": "6.26.0", 756 | "babel-traverse": "6.26.0", 757 | "babel-types": "6.26.0", 758 | "babylon": "6.18.0", 759 | "lodash": "4.17.10" 760 | } 761 | }, 762 | "babel-traverse": { 763 | "version": "6.26.0", 764 | "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", 765 | "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", 766 | "dev": true, 767 | "requires": { 768 | "babel-code-frame": "6.26.0", 769 | "babel-messages": "6.23.0", 770 | "babel-runtime": "6.26.0", 771 | "babel-types": "6.26.0", 772 | "babylon": "6.18.0", 773 | "debug": "2.6.9", 774 | "globals": "9.18.0", 775 | "invariant": "2.2.4", 776 | "lodash": "4.17.10" 777 | } 778 | }, 779 | "babel-types": { 780 | "version": "6.26.0", 781 | "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", 782 | "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", 783 | "dev": true, 784 | "requires": { 785 | "babel-runtime": "6.26.0", 786 | "esutils": "2.0.2", 787 | "lodash": "4.17.10", 788 | "to-fast-properties": "1.0.3" 789 | } 790 | }, 791 | "babylon": { 792 | "version": "6.18.0", 793 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", 794 | "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", 795 | "dev": true 796 | }, 797 | "balanced-match": { 798 | "version": "1.0.0", 799 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 800 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 801 | "dev": true 802 | }, 803 | "brace-expansion": { 804 | "version": "1.1.11", 805 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 806 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 807 | "dev": true, 808 | "requires": { 809 | "balanced-match": "1.0.0", 810 | "concat-map": "0.0.1" 811 | } 812 | }, 813 | "browserslist": { 814 | "version": "2.11.3", 815 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", 816 | "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", 817 | "dev": true, 818 | "requires": { 819 | "caniuse-lite": "1.0.30000877", 820 | "electron-to-chromium": "1.3.58" 821 | } 822 | }, 823 | "caniuse-lite": { 824 | "version": "1.0.30000877", 825 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000877.tgz", 826 | "integrity": "sha512-h04kV/lcuhItU1CZTJOxUEk/9R+1XeJqgc67E+XC8J9TjPM8kzVgOn27ZtRdDUo8O5F8U4QRCzDWJrVym3w3Cg==", 827 | "dev": true 828 | }, 829 | "chalk": { 830 | "version": "1.1.3", 831 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 832 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 833 | "dev": true, 834 | "requires": { 835 | "ansi-styles": "2.2.1", 836 | "escape-string-regexp": "1.0.5", 837 | "has-ansi": "2.0.0", 838 | "strip-ansi": "3.0.1", 839 | "supports-color": "2.0.0" 840 | } 841 | }, 842 | "classnames": { 843 | "version": "2.2.6", 844 | "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", 845 | "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" 846 | }, 847 | "concat-map": { 848 | "version": "0.0.1", 849 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 850 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 851 | "dev": true 852 | }, 853 | "convert-source-map": { 854 | "version": "1.5.1", 855 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", 856 | "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", 857 | "dev": true 858 | }, 859 | "core-js": { 860 | "version": "1.2.7", 861 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 862 | "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" 863 | }, 864 | "debug": { 865 | "version": "2.6.9", 866 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 867 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 868 | "dev": true, 869 | "requires": { 870 | "ms": "2.0.0" 871 | } 872 | }, 873 | "detect-indent": { 874 | "version": "4.0.0", 875 | "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", 876 | "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", 877 | "dev": true, 878 | "requires": { 879 | "repeating": "2.0.1" 880 | } 881 | }, 882 | "dom-helpers": { 883 | "version": "3.3.1", 884 | "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz", 885 | "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg==" 886 | }, 887 | "electron-to-chromium": { 888 | "version": "1.3.58", 889 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.58.tgz", 890 | "integrity": "sha512-AGJxlBEn2wOohxqWZkISVsOjZueKTQljfEODTDSEiMqSpH0S+xzV+/5oEM9AGaqhu7DzrpKOgU7ocQRjj0nJmg==", 891 | "dev": true 892 | }, 893 | "encoding": { 894 | "version": "0.1.12", 895 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 896 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 897 | "requires": { 898 | "iconv-lite": "0.4.23" 899 | } 900 | }, 901 | "escape-string-regexp": { 902 | "version": "1.0.5", 903 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 904 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 905 | "dev": true 906 | }, 907 | "esutils": { 908 | "version": "2.0.2", 909 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 910 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 911 | "dev": true 912 | }, 913 | "fbjs": { 914 | "version": "0.8.17", 915 | "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", 916 | "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", 917 | "requires": { 918 | "core-js": "1.2.7", 919 | "isomorphic-fetch": "2.2.1", 920 | "loose-envify": "1.4.0", 921 | "object-assign": "4.1.1", 922 | "promise": "7.3.1", 923 | "setimmediate": "1.0.5", 924 | "ua-parser-js": "0.7.18" 925 | } 926 | }, 927 | "globals": { 928 | "version": "9.18.0", 929 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 930 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", 931 | "dev": true 932 | }, 933 | "has-ansi": { 934 | "version": "2.0.0", 935 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 936 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 937 | "dev": true, 938 | "requires": { 939 | "ansi-regex": "2.1.1" 940 | } 941 | }, 942 | "history": { 943 | "version": "4.7.2", 944 | "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", 945 | "integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==", 946 | "requires": { 947 | "invariant": "2.2.4", 948 | "loose-envify": "1.4.0", 949 | "resolve-pathname": "2.2.0", 950 | "value-equal": "0.4.0", 951 | "warning": "3.0.0" 952 | } 953 | }, 954 | "hoist-non-react-statics": { 955 | "version": "2.5.5", 956 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", 957 | "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" 958 | }, 959 | "home-or-tmp": { 960 | "version": "2.0.0", 961 | "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", 962 | "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", 963 | "dev": true, 964 | "requires": { 965 | "os-homedir": "1.0.2", 966 | "os-tmpdir": "1.0.2" 967 | } 968 | }, 969 | "iconv-lite": { 970 | "version": "0.4.23", 971 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 972 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 973 | "requires": { 974 | "safer-buffer": "2.1.2" 975 | } 976 | }, 977 | "invariant": { 978 | "version": "2.2.4", 979 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 980 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 981 | "requires": { 982 | "loose-envify": "1.4.0" 983 | } 984 | }, 985 | "is-finite": { 986 | "version": "1.0.2", 987 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", 988 | "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", 989 | "dev": true, 990 | "requires": { 991 | "number-is-nan": "1.0.1" 992 | } 993 | }, 994 | "is-stream": { 995 | "version": "1.1.0", 996 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 997 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 998 | }, 999 | "isarray": { 1000 | "version": "0.0.1", 1001 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 1002 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 1003 | }, 1004 | "isomorphic-fetch": { 1005 | "version": "2.2.1", 1006 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 1007 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 1008 | "requires": { 1009 | "node-fetch": "1.7.3", 1010 | "whatwg-fetch": "2.0.4" 1011 | } 1012 | }, 1013 | "js-tokens": { 1014 | "version": "4.0.0", 1015 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1016 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1017 | }, 1018 | "jsesc": { 1019 | "version": "0.5.0", 1020 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", 1021 | "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", 1022 | "dev": true 1023 | }, 1024 | "json5": { 1025 | "version": "0.5.1", 1026 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", 1027 | "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", 1028 | "dev": true 1029 | }, 1030 | "keycode": { 1031 | "version": "2.2.0", 1032 | "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", 1033 | "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" 1034 | }, 1035 | "lodash": { 1036 | "version": "4.17.10", 1037 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 1038 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" 1039 | }, 1040 | "lodash-es": { 1041 | "version": "4.17.10", 1042 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", 1043 | "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" 1044 | }, 1045 | "loose-envify": { 1046 | "version": "1.4.0", 1047 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1048 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1049 | "requires": { 1050 | "js-tokens": "4.0.0" 1051 | } 1052 | }, 1053 | "minimatch": { 1054 | "version": "3.0.4", 1055 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1056 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1057 | "dev": true, 1058 | "requires": { 1059 | "brace-expansion": "1.1.11" 1060 | } 1061 | }, 1062 | "minimist": { 1063 | "version": "0.0.8", 1064 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1065 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1066 | "dev": true 1067 | }, 1068 | "mkdirp": { 1069 | "version": "0.5.1", 1070 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1071 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1072 | "dev": true, 1073 | "requires": { 1074 | "minimist": "0.0.8" 1075 | } 1076 | }, 1077 | "ms": { 1078 | "version": "2.0.0", 1079 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1080 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 1081 | "dev": true 1082 | }, 1083 | "node-fetch": { 1084 | "version": "1.7.3", 1085 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 1086 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 1087 | "requires": { 1088 | "encoding": "0.1.12", 1089 | "is-stream": "1.1.0" 1090 | } 1091 | }, 1092 | "number-is-nan": { 1093 | "version": "1.0.1", 1094 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1095 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 1096 | "dev": true 1097 | }, 1098 | "object-assign": { 1099 | "version": "4.1.1", 1100 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1101 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1102 | }, 1103 | "os-homedir": { 1104 | "version": "1.0.2", 1105 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1106 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", 1107 | "dev": true 1108 | }, 1109 | "os-tmpdir": { 1110 | "version": "1.0.2", 1111 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1112 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1113 | "dev": true 1114 | }, 1115 | "path-is-absolute": { 1116 | "version": "1.0.1", 1117 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1118 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1119 | "dev": true 1120 | }, 1121 | "path-to-regexp": { 1122 | "version": "1.7.0", 1123 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 1124 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 1125 | "requires": { 1126 | "isarray": "0.0.1" 1127 | } 1128 | }, 1129 | "private": { 1130 | "version": "0.1.8", 1131 | "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", 1132 | "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", 1133 | "dev": true 1134 | }, 1135 | "promise": { 1136 | "version": "7.3.1", 1137 | "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", 1138 | "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", 1139 | "requires": { 1140 | "asap": "2.0.6" 1141 | } 1142 | }, 1143 | "prop-types": { 1144 | "version": "15.6.2", 1145 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", 1146 | "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", 1147 | "requires": { 1148 | "loose-envify": "1.4.0", 1149 | "object-assign": "4.1.1" 1150 | } 1151 | }, 1152 | "prop-types-extra": { 1153 | "version": "1.1.0", 1154 | "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz", 1155 | "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==", 1156 | "requires": { 1157 | "react-is": "16.4.2", 1158 | "warning": "3.0.0" 1159 | } 1160 | }, 1161 | "react": { 1162 | "version": "16.4.2", 1163 | "resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz", 1164 | "integrity": "sha512-dMv7YrbxO4y2aqnvA7f/ik9ibeLSHQJTI6TrYAenPSaQ6OXfb+Oti+oJiy8WBxgRzlKatYqtCjphTgDSCEiWFg==", 1165 | "requires": { 1166 | "fbjs": "0.8.17", 1167 | "loose-envify": "1.4.0", 1168 | "object-assign": "4.1.1", 1169 | "prop-types": "15.6.2" 1170 | } 1171 | }, 1172 | "react-bootstrap": { 1173 | "version": "0.32.1", 1174 | "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.32.1.tgz", 1175 | "integrity": "sha512-RbfzKUbsukWsToWqGHfCCyMFq9QQI0TznutdyxyJw6dih2NvIne25Mrssg8LZsprqtPpyQi8bN0L0Fx3fUsL8Q==", 1176 | "requires": { 1177 | "babel-runtime": "6.26.0", 1178 | "classnames": "2.2.6", 1179 | "dom-helpers": "3.3.1", 1180 | "invariant": "2.2.4", 1181 | "keycode": "2.2.0", 1182 | "prop-types": "15.6.2", 1183 | "prop-types-extra": "1.1.0", 1184 | "react-overlays": "0.8.3", 1185 | "react-prop-types": "0.4.0", 1186 | "react-transition-group": "2.4.0", 1187 | "uncontrollable": "4.1.0", 1188 | "warning": "3.0.0" 1189 | } 1190 | }, 1191 | "react-dom": { 1192 | "version": "16.2.0", 1193 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.0.tgz", 1194 | "integrity": "sha512-zpGAdwHVn9K0091d+hr+R0qrjoJ84cIBFL2uU60KvWBPfZ7LPSrfqviTxGHWN0sjPZb2hxWzMexwrvJdKePvjg==", 1195 | "requires": { 1196 | "fbjs": "0.8.17", 1197 | "loose-envify": "1.4.0", 1198 | "object-assign": "4.1.1", 1199 | "prop-types": "15.6.2" 1200 | } 1201 | }, 1202 | "react-is": { 1203 | "version": "16.4.2", 1204 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.4.2.tgz", 1205 | "integrity": "sha512-rI3cGFj/obHbBz156PvErrS5xc6f1eWyTwyV4mo0vF2lGgXgS+mm7EKD5buLJq6jNgIagQescGSVG2YzgXt8Yg==" 1206 | }, 1207 | "react-lifecycles-compat": { 1208 | "version": "3.0.4", 1209 | "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", 1210 | "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" 1211 | }, 1212 | "react-overlays": { 1213 | "version": "0.8.3", 1214 | "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.8.3.tgz", 1215 | "integrity": "sha512-h6GT3jgy90PgctleP39Yu3eK1v9vaJAW73GOA/UbN9dJ7aAN4BTZD6793eI1D5U+ukMk17qiqN/wl3diK1Z5LA==", 1216 | "requires": { 1217 | "classnames": "2.2.6", 1218 | "dom-helpers": "3.3.1", 1219 | "prop-types": "15.6.2", 1220 | "prop-types-extra": "1.1.0", 1221 | "react-transition-group": "2.4.0", 1222 | "warning": "3.0.0" 1223 | } 1224 | }, 1225 | "react-prop-types": { 1226 | "version": "0.4.0", 1227 | "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz", 1228 | "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=", 1229 | "requires": { 1230 | "warning": "3.0.0" 1231 | } 1232 | }, 1233 | "react-redux": { 1234 | "version": "5.0.7", 1235 | "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", 1236 | "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", 1237 | "requires": { 1238 | "hoist-non-react-statics": "2.5.5", 1239 | "invariant": "2.2.4", 1240 | "lodash": "4.17.10", 1241 | "lodash-es": "4.17.10", 1242 | "loose-envify": "1.4.0", 1243 | "prop-types": "15.6.2" 1244 | } 1245 | }, 1246 | "react-router": { 1247 | "version": "4.3.1", 1248 | "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz", 1249 | "integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==", 1250 | "requires": { 1251 | "history": "4.7.2", 1252 | "hoist-non-react-statics": "2.5.5", 1253 | "invariant": "2.2.4", 1254 | "loose-envify": "1.4.0", 1255 | "path-to-regexp": "1.7.0", 1256 | "prop-types": "15.6.2", 1257 | "warning": "4.0.2" 1258 | }, 1259 | "dependencies": { 1260 | "warning": { 1261 | "version": "4.0.2", 1262 | "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", 1263 | "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", 1264 | "requires": { 1265 | "loose-envify": "1.4.0" 1266 | } 1267 | } 1268 | } 1269 | }, 1270 | "react-router-dom": { 1271 | "version": "4.3.1", 1272 | "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz", 1273 | "integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==", 1274 | "requires": { 1275 | "history": "4.7.2", 1276 | "invariant": "2.2.4", 1277 | "loose-envify": "1.4.0", 1278 | "prop-types": "15.6.2", 1279 | "react-router": "4.3.1", 1280 | "warning": "4.0.2" 1281 | }, 1282 | "dependencies": { 1283 | "warning": { 1284 | "version": "4.0.2", 1285 | "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz", 1286 | "integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==", 1287 | "requires": { 1288 | "loose-envify": "1.4.0" 1289 | } 1290 | } 1291 | } 1292 | }, 1293 | "react-transition-group": { 1294 | "version": "2.4.0", 1295 | "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.4.0.tgz", 1296 | "integrity": "sha512-Xv5d55NkJUxUzLCImGSanK8Cl/30sgpOEMGc5m86t8+kZwrPxPCPcFqyx83kkr+5Lz5gs6djuvE5By+gce+VjA==", 1297 | "requires": { 1298 | "dom-helpers": "3.3.1", 1299 | "loose-envify": "1.4.0", 1300 | "prop-types": "15.6.2", 1301 | "react-lifecycles-compat": "3.0.4" 1302 | } 1303 | }, 1304 | "redux": { 1305 | "version": "4.0.0", 1306 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", 1307 | "integrity": "sha512-NnnHF0h0WVE/hXyrB6OlX67LYRuaf/rJcbWvnHHEPCF/Xa/AZpwhs/20WyqzQae5x4SD2F9nPObgBh2rxAgLiA==", 1308 | "requires": { 1309 | "loose-envify": "1.4.0", 1310 | "symbol-observable": "1.2.0" 1311 | } 1312 | }, 1313 | "redux-thunk": { 1314 | "version": "2.3.0", 1315 | "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", 1316 | "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" 1317 | }, 1318 | "regenerate": { 1319 | "version": "1.4.0", 1320 | "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", 1321 | "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", 1322 | "dev": true 1323 | }, 1324 | "regenerator-runtime": { 1325 | "version": "0.11.1", 1326 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 1327 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 1328 | }, 1329 | "regenerator-transform": { 1330 | "version": "0.10.1", 1331 | "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", 1332 | "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", 1333 | "dev": true, 1334 | "requires": { 1335 | "babel-runtime": "6.26.0", 1336 | "babel-types": "6.26.0", 1337 | "private": "0.1.8" 1338 | } 1339 | }, 1340 | "regexpu-core": { 1341 | "version": "2.0.0", 1342 | "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", 1343 | "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", 1344 | "dev": true, 1345 | "requires": { 1346 | "regenerate": "1.4.0", 1347 | "regjsgen": "0.2.0", 1348 | "regjsparser": "0.1.5" 1349 | } 1350 | }, 1351 | "regjsgen": { 1352 | "version": "0.2.0", 1353 | "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", 1354 | "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", 1355 | "dev": true 1356 | }, 1357 | "regjsparser": { 1358 | "version": "0.1.5", 1359 | "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", 1360 | "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", 1361 | "dev": true, 1362 | "requires": { 1363 | "jsesc": "0.5.0" 1364 | } 1365 | }, 1366 | "repeating": { 1367 | "version": "2.0.1", 1368 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", 1369 | "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", 1370 | "dev": true, 1371 | "requires": { 1372 | "is-finite": "1.0.2" 1373 | } 1374 | }, 1375 | "resolve-pathname": { 1376 | "version": "2.2.0", 1377 | "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", 1378 | "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" 1379 | }, 1380 | "safer-buffer": { 1381 | "version": "2.1.2", 1382 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1383 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1384 | }, 1385 | "semver": { 1386 | "version": "5.5.0", 1387 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 1388 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", 1389 | "dev": true 1390 | }, 1391 | "setimmediate": { 1392 | "version": "1.0.5", 1393 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 1394 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" 1395 | }, 1396 | "slash": { 1397 | "version": "1.0.0", 1398 | "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", 1399 | "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", 1400 | "dev": true 1401 | }, 1402 | "source-map": { 1403 | "version": "0.5.7", 1404 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1405 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1406 | "dev": true 1407 | }, 1408 | "source-map-support": { 1409 | "version": "0.4.18", 1410 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 1411 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 1412 | "dev": true, 1413 | "requires": { 1414 | "source-map": "0.5.7" 1415 | } 1416 | }, 1417 | "strip-ansi": { 1418 | "version": "3.0.1", 1419 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1420 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1421 | "dev": true, 1422 | "requires": { 1423 | "ansi-regex": "2.1.1" 1424 | } 1425 | }, 1426 | "supports-color": { 1427 | "version": "2.0.0", 1428 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1429 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1430 | "dev": true 1431 | }, 1432 | "symbol-observable": { 1433 | "version": "1.2.0", 1434 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 1435 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" 1436 | }, 1437 | "to-fast-properties": { 1438 | "version": "1.0.3", 1439 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", 1440 | "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", 1441 | "dev": true 1442 | }, 1443 | "trim-right": { 1444 | "version": "1.0.1", 1445 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", 1446 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", 1447 | "dev": true 1448 | }, 1449 | "ua-parser-js": { 1450 | "version": "0.7.18", 1451 | "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", 1452 | "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" 1453 | }, 1454 | "uncontrollable": { 1455 | "version": "4.1.0", 1456 | "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-4.1.0.tgz", 1457 | "integrity": "sha1-4DWCkSUuGGUiLZCTmxny9J+Bwak=", 1458 | "requires": { 1459 | "invariant": "2.2.4" 1460 | } 1461 | }, 1462 | "value-equal": { 1463 | "version": "0.4.0", 1464 | "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", 1465 | "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" 1466 | }, 1467 | "warning": { 1468 | "version": "3.0.0", 1469 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", 1470 | "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", 1471 | "requires": { 1472 | "loose-envify": "1.4.0" 1473 | } 1474 | }, 1475 | "whatwg-fetch": { 1476 | "version": "2.0.4", 1477 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", 1478 | "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" 1479 | } 1480 | } 1481 | } 1482 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rm -rf dist .cache", 8 | "start": "npm run clean && parcel src/index.html", 9 | "dev": "npm run start" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "history": "^4.7.2", 16 | "react": "^16.4.2", 17 | "react-bootstrap": "^0.32.1", 18 | "react-dom": "^16.2.0", 19 | "react-redux": "^5.0.7", 20 | "react-router": "^4.3.1", 21 | "react-router-dom": "^4.3.1", 22 | "redux": "^4.0.0", 23 | "redux-thunk": "^2.3.0" 24 | }, 25 | "devDependencies": { 26 | "babel-core": "^6.26.3", 27 | "babel-plugin-transform-class-properties": "^6.24.1", 28 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 29 | "babel-preset-env": "^1.6.1", 30 | "babel-preset-react": "^6.24.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/actions/account.js: -------------------------------------------------------------------------------- 1 | import { ACCOUNT } from './types'; 2 | import { BACKEND } from '../config'; 3 | 4 | export const fetchFromAccount = ({ 5 | endpoint, 6 | options, 7 | FETCH_TYPE, 8 | ERROR_TYPE, 9 | SUCCESS_TYPE 10 | }) => dispatch => { 11 | dispatch({ type: FETCH_TYPE }); 12 | 13 | return fetch(`${BACKEND.ADDRESS}/account/${endpoint}`, options) 14 | .then(response => response.json()) 15 | .then(json => { 16 | if (json.type === 'error') { 17 | dispatch({ type: ERROR_TYPE, message: json.message }); 18 | } else { 19 | dispatch({ type: SUCCESS_TYPE, ...json }); 20 | } 21 | }) 22 | .catch(error => dispatch({ 23 | type: ERROR_TYPE, message: error.message 24 | })); 25 | } 26 | 27 | export const signup = ({ username, password }) => fetchFromAccount({ 28 | endpoint: 'signup', 29 | options: { 30 | method: 'POST', 31 | body: JSON.stringify({ username, password }), 32 | headers: { 'Content-Type': 'application/json' }, 33 | credentials: 'include' 34 | }, 35 | FETCH_TYPE: ACCOUNT.FETCH, 36 | ERROR_TYPE: ACCOUNT.FETCH_ERROR, 37 | SUCCESS_TYPE: ACCOUNT.FETCH_SUCCESS 38 | }); 39 | 40 | export const login = ({ username, password }) => fetchFromAccount({ 41 | endpoint: 'login', 42 | options: { 43 | method: 'POST', 44 | body: JSON.stringify({ username, password }), 45 | headers: { 'Content-Type': 'application/json' }, 46 | credentials: 'include' 47 | }, 48 | FETCH_TYPE: ACCOUNT.FETCH, 49 | ERROR_TYPE: ACCOUNT.FETCH_ERROR, 50 | SUCCESS_TYPE: ACCOUNT.FETCH_SUCCESS 51 | }); 52 | 53 | export const logout = () => fetchFromAccount({ 54 | endpoint: 'logout', 55 | options: { credentials: 'include' }, 56 | FETCH_TYPE: ACCOUNT.FETCH, 57 | ERROR_TYPE: ACCOUNT.FETCH_ERROR, 58 | SUCCESS_TYPE: ACCOUNT.FETCH_LOGOUT_SUCCESS 59 | }); 60 | 61 | export const fetchAuthenticated = () => fetchFromAccount({ 62 | endpoint: 'authenticated', 63 | options: { credentials: 'include' }, 64 | FETCH_TYPE: ACCOUNT.FETCH, 65 | ERROR_TYPE: ACCOUNT.FETCH_ERROR, 66 | SUCCESS_TYPE: ACCOUNT.FETCH_AUTHENTICATED_SUCCESS 67 | }); 68 | -------------------------------------------------------------------------------- /frontend/src/actions/accountDragons.js: -------------------------------------------------------------------------------- 1 | import { ACCOUNT_DRAGONS } from './types'; 2 | import { fetchFromAccount } from './account'; 3 | 4 | export const fetchAccountDragons = () => fetchFromAccount({ 5 | endpoint: 'dragons', 6 | options: { credentials: 'include' }, 7 | FETCH_TYPE: ACCOUNT_DRAGONS.FETCH, 8 | ERROR_TYPE: ACCOUNT_DRAGONS.FETCH_ERROR, 9 | SUCCESS_TYPE: ACCOUNT_DRAGONS.FETCH_SUCCESS 10 | }); -------------------------------------------------------------------------------- /frontend/src/actions/accountInfo.js: -------------------------------------------------------------------------------- 1 | import { fetchFromAccount } from './account'; 2 | import { ACCOUNT_INFO } from './types'; 3 | 4 | export const fetchAccountInfo = () => fetchFromAccount({ 5 | endpoint: 'info', 6 | options: { credentials: 'include' }, 7 | FETCH_TYPE: ACCOUNT_INFO.FETCH, 8 | ERROR_TYPE: ACCOUNT_INFO.FETCH_ERROR, 9 | SUCCESS_TYPE: ACCOUNT_INFO.FETCH_SUCCESS 10 | }); -------------------------------------------------------------------------------- /frontend/src/actions/dragon.js: -------------------------------------------------------------------------------- 1 | import { DRAGON } from './types'; 2 | import { BACKEND } from '../config'; 3 | 4 | export const fetchDragon = () => dispatch => { 5 | dispatch({ type: DRAGON.FETCH }); 6 | 7 | return fetch(`${BACKEND.ADDRESS}/dragon/new`, { 8 | credentials: 'include' 9 | }).then(response => response.json()) 10 | .then(json => { 11 | if (json.type === 'error') { 12 | dispatch({ type: DRAGON.FETCH_ERROR, message: json.message }); 13 | } else { 14 | dispatch({ type: DRAGON.FETCH_SUCCESS, dragon: json.dragon }); 15 | } 16 | }) 17 | .catch(error => dispatch({ type: DRAGON.FETCH_ERROR, message: error.message })); 18 | }; -------------------------------------------------------------------------------- /frontend/src/actions/generation.js: -------------------------------------------------------------------------------- 1 | import { GENERATION } from './types'; 2 | import { BACKEND } from '../config'; 3 | 4 | export const fetchGeneration = () => dispatch => { 5 | dispatch({ type: GENERATION.FETCH }); 6 | 7 | return fetch(`${BACKEND.ADDRESS}/generation`) 8 | .then(response => response.json()) 9 | .then(json => { 10 | if (json.type === 'error') { 11 | dispatch({ 12 | type: GENERATION.FETCH_ERROR, 13 | message: json.message 14 | }); 15 | } else { 16 | dispatch({ 17 | type: GENERATION.FETCH_SUCCESS, 18 | generation: json.generation 19 | }); 20 | } 21 | }) 22 | .catch(error => dispatch({ 23 | type: GENERATION.FETCH_ERROR, 24 | message: error.message 25 | })); 26 | }; -------------------------------------------------------------------------------- /frontend/src/actions/publicDragons.js: -------------------------------------------------------------------------------- 1 | import { PUBLIC_DRAGONS } from './types'; 2 | import { BACKEND } from '../config'; 3 | 4 | export const fetchPublicDragons = () => dispatch => { 5 | dispatch({ type: PUBLIC_DRAGONS.FETCH }); 6 | 7 | return fetch(`${BACKEND.ADDRESS}/dragon/public-dragons`) 8 | .then(response => response.json()) 9 | .then(json => { 10 | if (json.type === 'error') { 11 | dispatch({ type: PUBLIC_DRAGONS.FETCH_ERROR, message: json.message }); 12 | } else { 13 | dispatch({ type: PUBLIC_DRAGONS.FETCH_SUCCESS, dragons: json.dragons }); 14 | } 15 | }) 16 | .catch(error => dispatch({ type: PUBLIC_DRAGONS.FETCH_ERROR, message: error.message })); 17 | } -------------------------------------------------------------------------------- /frontend/src/actions/types.js: -------------------------------------------------------------------------------- 1 | const GENERATION = { 2 | FETCH: 'GENERATION_FETCH', 3 | FETCH_ERROR: 'GENERATION_FETCH_ERROR', 4 | FETCH_SUCCESS: 'GENERATION_FETCH_SUCCESS' 5 | }; 6 | 7 | const DRAGON = { 8 | FETCH: 'DRAGON_FETCH', 9 | FETCH_ERROR: 'DRAGON_FETCH_ERROR', 10 | FETCH_SUCCESS: 'DRAGON_FETCH_SUCCESS' 11 | }; 12 | 13 | const ACCOUNT = { 14 | FETCH: 'ACCOUNT_FETCH', 15 | FETCH_ERROR: 'ACCOUNT_FETCH_ERROR', 16 | FETCH_SUCCESS: 'ACCOUNT_FETCH_SUCCESS', 17 | FETCH_LOGOUT_SUCCESS: 'ACCOUNT_FETCH_LOGOUT_SUCCESS', 18 | FETCH_AUTHENTICATED_SUCCESS: 'ACCOUNT_FETCH_AUTHENTICATED_SUCCESS' 19 | }; 20 | 21 | const ACCOUNT_DRAGONS = { 22 | FETCH: 'ACCOUNT_DRAGON_FETCH', 23 | FETCH_ERROR: 'ACCOUNT_DRAGON_FETCH_ERROR', 24 | FETCH_SUCCESS: 'ACCOUNT_DRAGON_FETCH_SUCCESS' 25 | }; 26 | 27 | const ACCOUNT_INFO = { 28 | FETCH: 'ACCOUNT_INFO_FETCH', 29 | FETCH_ERROR: 'ACCOUNT_INFO_FETCH_ERROR', 30 | FETCH_SUCCESS: 'ACCOUNT_INFO_FETCH_SUCCESS' 31 | }; 32 | 33 | const PUBLIC_DRAGONS = { 34 | FETCH: 'PUBLIC_DRAGONS_FETCH', 35 | FETCH_ERROR: 'PUBLIC_DRAGONS_FETCH_ERROR', 36 | FETCH_SUCCESS: 'PUBLIC_DRAGONS_FETCH_SUCCESS' 37 | }; 38 | 39 | export { 40 | GENERATION, 41 | DRAGON, 42 | ACCOUNT, 43 | ACCOUNT_DRAGONS, 44 | ACCOUNT_INFO, 45 | PUBLIC_DRAGONS 46 | }; -------------------------------------------------------------------------------- /frontend/src/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/.DS_Store -------------------------------------------------------------------------------- /frontend/src/assets/index.js: -------------------------------------------------------------------------------- 1 | import skinny from './skinny.png'; 2 | import slender from './slender.png'; 3 | import sporty from './sporty.png'; 4 | import stocky from './stocky.png'; 5 | import patchy from './patchy.png'; 6 | import plain from './plain.png'; 7 | import spotted from './spotted.png'; 8 | import striped from './striped.png'; 9 | 10 | export { skinny, slender, sporty, stocky, patchy, plain, spotted, striped }; -------------------------------------------------------------------------------- /frontend/src/assets/patchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/patchy.png -------------------------------------------------------------------------------- /frontend/src/assets/plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/plain.png -------------------------------------------------------------------------------- /frontend/src/assets/skinny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/skinny.png -------------------------------------------------------------------------------- /frontend/src/assets/slender.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/slender.png -------------------------------------------------------------------------------- /frontend/src/assets/sporty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/sporty.png -------------------------------------------------------------------------------- /frontend/src/assets/spotted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/spotted.png -------------------------------------------------------------------------------- /frontend/src/assets/stocky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/stocky.png -------------------------------------------------------------------------------- /frontend/src/assets/striped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/15Dkatz/dragonstack-guides/1e9f1cd776e87426a05711a3a1463f5a6ccbe752/frontend/src/assets/striped.png -------------------------------------------------------------------------------- /frontend/src/components/AccountDragonRow.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'react-bootstrap'; 3 | import DragonAvatar from './DragonAvatar'; 4 | import { BACKEND } from '../config'; 5 | 6 | class AccountDragonRow extends Component { 7 | state = { 8 | nickname: this.props.dragon.nickname, 9 | isPublic: this.props.dragon.isPublic, 10 | saleValue: this.props.dragon.saleValue, 11 | sireValue: this.props.dragon.sireValue, 12 | edit: false 13 | }; 14 | 15 | updateNickname = event => { 16 | this.setState({ nickname: event.target.value }); 17 | } 18 | 19 | updateSaleValue = event => { 20 | this.setState({ saleValue: event.target.value }); 21 | } 22 | 23 | updateSireValue = event => { 24 | this.setState({ sireValue: event.target.value }); 25 | } 26 | 27 | updateIsPublic = event => { 28 | this.setState({ isPublic: event.target.checked }); 29 | } 30 | 31 | toggleEdit = () => { 32 | this.setState({ edit: !this.state.edit }); 33 | } 34 | 35 | save = () => { 36 | fetch(`${BACKEND.ADDRESS}/dragon/update`, { 37 | method: 'PUT', 38 | headers: { 'Content-Type': 'application/json' }, 39 | body: JSON.stringify({ 40 | dragonId: this.props.dragon.dragonId, 41 | nickname: this.state.nickname, 42 | isPublic: this.state.isPublic, 43 | saleValue: this.state.saleValue, 44 | sireValue: this.state.sireValue 45 | }) 46 | }).then(response => response.json()) 47 | .then(json => { 48 | if (json.type === 'error') { 49 | alert(json.message); 50 | } else { 51 | this.toggleEdit(); 52 | } 53 | }) 54 | .catch(error => alert(error.message)); 55 | } 56 | 57 | get SaveButton() { 58 | return ; 59 | } 60 | 61 | get EditButton() { 62 | return ; 63 | } 64 | 65 | render() { 66 | return ( 67 |
68 | 74 |
75 | 76 |
77 | 78 | Sale Value:{' '} 79 | 86 | {' '} 87 | 88 | Sire Value:{' '} 89 | 96 | {' '} 97 | 98 | Public:{' '} 99 | 105 | 106 | { 107 | this.state.edit ? this.SaveButton : this.EditButton 108 | } 109 |
110 |
111 | ) 112 | } 113 | } 114 | 115 | export default AccountDragonRow; -------------------------------------------------------------------------------- /frontend/src/components/AccountDragons.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router-dom'; 4 | import { fetchAccountDragons } from '../actions/accountDragons'; 5 | import AccountDragonRow from './AccountDragonRow'; 6 | 7 | class AccountDragons extends Component { 8 | componentDidMount() { 9 | this.props.fetchAccountDragons(); 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |

Account Dragons

16 | { 17 | this.props.accountDragons.dragons.map(dragon => { 18 | return ( 19 |
20 | 21 |
22 |
23 | ) 24 | }) 25 | } 26 | Home 27 |
28 | ); 29 | } 30 | } 31 | 32 | export default connect( 33 | ({ accountDragons }) => ({ accountDragons }), 34 | { fetchAccountDragons } 35 | )(AccountDragons); -------------------------------------------------------------------------------- /frontend/src/components/AccountInfo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchAccountInfo } from '../actions/accountInfo'; 4 | 5 | class AccountInfo extends Component { 6 | componentDidMount() { 7 | this.props.fetchAccountInfo(); 8 | } 9 | 10 | render() { 11 | return ( 12 |
13 |

Account Info

14 |
Username: {this.props.accountInfo.username}
15 |
Balance: {this.props.accountInfo.balance}
16 |
17 | ) 18 | } 19 | } 20 | 21 | export default connect( 22 | ({ accountInfo }) => ({ accountInfo }), 23 | { fetchAccountInfo } 24 | )(AccountInfo); -------------------------------------------------------------------------------- /frontend/src/components/AuthForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Button, FormGroup, FormControl } from 'react-bootstrap'; 4 | import { signup, login } from '../actions/account'; 5 | import fetchStates from '../reducers/fetchStates'; 6 | 7 | class AuthForm extends Component { 8 | state = { username: '', password: '', buttonClicked: false }; 9 | 10 | updateUsername = event => { 11 | this.setState({ username: event.target.value }); 12 | } 13 | 14 | updatePassword = event => { 15 | this.setState({ password: event.target.value }); 16 | } 17 | 18 | signup = () => { 19 | this.setState({ buttonClicked: true }); 20 | 21 | const { username, password } = this.state; 22 | 23 | this.props.signup({ username, password }); 24 | } 25 | 26 | login = () => { 27 | this.setState({ buttonClicked: true }); 28 | 29 | const { username, password } = this.state; 30 | 31 | this.props.login({ username, password }); 32 | } 33 | 34 | get Error() { 35 | if ( 36 | this.state.buttonClicked && 37 | this.props.account.status === fetchStates.error 38 | ) { 39 | return
{this.props.account.message}
40 | } 41 | } 42 | 43 | render() { 44 | return ( 45 |
46 |

Dragon Stack

47 | 48 | 54 | 55 | 56 | 62 | 63 |
64 | 65 | or 66 | 67 |
68 |
69 | {this.Error} 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default connect( 76 | ({ account }) => ({ account }), 77 | { signup, login } 78 | )(AuthForm); -------------------------------------------------------------------------------- /frontend/src/components/Dragon.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Button } from 'react-bootstrap'; 4 | import DragonAvatar from './DragonAvatar'; 5 | import { fetchDragon } from '../actions/dragon'; 6 | import fetchStates from '../reducers/fetchStates'; 7 | 8 | class Dragon extends Component { 9 | get DragonView() { 10 | const { dragon } = this.props; 11 | 12 | if (dragon.status === fetchStates.error) return {dragon.message}; 13 | 14 | return ; 15 | } 16 | 17 | render() { 18 | return ( 19 |
20 | 21 |
22 | { this.DragonView } 23 |
24 | ) 25 | } 26 | } 27 | 28 | export default connect( 29 | ({ dragon }) => ({ dragon }), 30 | { fetchDragon } 31 | )(Dragon); -------------------------------------------------------------------------------- /frontend/src/components/DragonAvatar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { skinny, slender, sporty, stocky, patchy, plain, spotted, striped } from '../assets'; 3 | 4 | const propertyMap = { 5 | backgroundColor: { 6 | black: '#263238', 7 | white: '#cfd8dc', 8 | green: '#a5d6a7', 9 | blue: '#0277bd' 10 | }, 11 | build: { slender, stocky, sporty, skinny }, 12 | pattern: { plain, striped, spotted, patchy }, 13 | size: { small: 100, medium: 140, large: 180, enormous: 220 } 14 | }; 15 | 16 | class DragonAvatar extends Component { 17 | get DragonImage() { 18 | const dragonPropertyMap = {}; 19 | 20 | this.props.dragon.traits.forEach(trait => { 21 | const { traitType, traitValue } = trait; 22 | 23 | dragonPropertyMap[traitType] = propertyMap[traitType][traitValue]; 24 | }); 25 | 26 | const { backgroundColor, build, pattern, size } = dragonPropertyMap; 27 | 28 | const sizing = { width: size, height: size }; 29 | 30 | return ( 31 |
32 |
33 | 34 | 35 |
36 | ); 37 | } 38 | 39 | render() { 40 | const { generationId, dragonId, traits } = this.props.dragon; 41 | 42 | if (!dragonId) return
; 43 | 44 | return ( 45 |
46 | G{generationId}. 47 | I{dragonId}. 48 | { traits.map(trait => trait.traitValue).join(', ') } 49 | { this.DragonImage } 50 |
51 | ) 52 | } 53 | } 54 | 55 | export default DragonAvatar; -------------------------------------------------------------------------------- /frontend/src/components/Generation.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchGeneration } from '../actions/generation'; 4 | import fetchStates from '../reducers/fetchStates'; 5 | 6 | const MINIMUM_DELAY = 3000; 7 | 8 | class Generation extends Component { 9 | timer = null; 10 | 11 | componentDidMount() { 12 | this.fetchNextGeneration(); 13 | } 14 | 15 | componentWillUnmount() { 16 | clearTimeout(this.timer); 17 | } 18 | 19 | fetchNextGeneration = () => { 20 | this.props.fetchGeneration(); 21 | 22 | let delay = new Date(this.props.generation.expiration).getTime() - 23 | new Date().getTime(); 24 | 25 | if (delay < MINIMUM_DELAY) { 26 | delay = MINIMUM_DELAY; 27 | }; 28 | 29 | this.timer = setTimeout(() => this.fetchNextGeneration(), delay); 30 | } 31 | 32 | render() { 33 | console.log('this.props', this.props); 34 | 35 | const { generation } = this.props; 36 | 37 | if (generation.status === fetchStates.error) { 38 | return
{generation.message}
; 39 | } 40 | 41 | return ( 42 |
43 |

Generation {generation.generationId}. Expires on:

44 |

{new Date(generation.expiration).toString()}

45 |
46 | ) 47 | } 48 | } 49 | 50 | const mapStateToProps = state => { 51 | const generation = state.generation; 52 | 53 | return { generation }; 54 | }; 55 | 56 | const componentConnector = connect( 57 | mapStateToProps, 58 | { fetchGeneration } 59 | ); 60 | 61 | export default componentConnector(Generation); -------------------------------------------------------------------------------- /frontend/src/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Button } from 'react-bootstrap'; 4 | import { Link } from 'react-router-dom' 5 | import Generation from './Generation'; 6 | import Dragon from './Dragon'; 7 | import AccountInfo from './AccountInfo'; 8 | import { logout } from '../actions/account'; 9 | 10 | class Home extends Component { 11 | render() { 12 | return ( 13 |
14 | 17 |

Dragon Stack

18 | 19 | 20 |
21 | 22 |
23 | Account Dragons 24 |
25 | Public Dragons 26 |
27 | ); 28 | } 29 | } 30 | 31 | export default connect(null, { logout })(Home); -------------------------------------------------------------------------------- /frontend/src/components/MatingOptions.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Button } from 'react-bootstrap'; 4 | import { BACKEND } from '../config'; 5 | import history from '../history'; 6 | 7 | class MatingOptions extends Component { 8 | mate = ({ matronDragonId, patronDragonId }) => () => { 9 | fetch(`${BACKEND.ADDRESS}/dragon/mate`, { 10 | method: 'POST', 11 | credentials: 'include', 12 | headers: { 'Content-Type': 'application/json' }, 13 | body: JSON.stringify({ matronDragonId, patronDragonId }) 14 | }).then(response => response.json()) 15 | .then(json => { 16 | alert(json.message); 17 | 18 | if (json.type !== 'error') { 19 | history.push('/account-dragons'); 20 | } 21 | }) 22 | .catch(error => alert(error.message)); 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 |

Pick one of your dragons to mate with:

29 | { 30 | this.props.accountDragons.dragons.map(dragon => { 31 | const { dragonId, generationId, nickname } = dragon; 32 | 33 | return ( 34 | 35 | 43 | {' '} 44 | 45 | ) 46 | }) 47 | } 48 |
49 | ) 50 | } 51 | } 52 | 53 | export default connect( 54 | ({ accountDragons }) => ({ accountDragons }), 55 | null 56 | )(MatingOptions); -------------------------------------------------------------------------------- /frontend/src/components/PublicDragonRow.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'react-bootstrap'; 3 | import DragonAvatar from './DragonAvatar'; 4 | import MatingOptions from './MatingOptions'; 5 | import { BACKEND } from '../config'; 6 | import history from '../history'; 7 | 8 | class PublicDragonRow extends Component { 9 | state = { displayMatingOptions: false }; 10 | 11 | toggleDisplayMatingOptions = () => { 12 | this.setState({ 13 | displayMatingOptions: !this.state.displayMatingOptions 14 | }); 15 | } 16 | 17 | buy = () => { 18 | const { dragonId, saleValue } = this.props.dragon; 19 | 20 | fetch(`${BACKEND.ADDRESS}/dragon/buy`, { 21 | method: 'POST', 22 | credentials: 'include', 23 | headers: { 'Content-Type': 'application/json' }, 24 | body: JSON.stringify({ dragonId, saleValue }) 25 | }).then(response => response.json()) 26 | .then(json => { 27 | alert(json.message); 28 | 29 | if (json.type !== 'error') { 30 | history.push('/account-dragons'); 31 | } 32 | }) 33 | .catch(error => alert(error.message)); 34 | } 35 | 36 | render() { 37 | return ( 38 |
39 |
{this.props.dragon.nickname}
40 | 41 |
42 | Sale Value: {this.props.dragon.saleValue}{' | '} 43 | Sire Value: {this.props.dragon.sireValue} 44 |
45 |
46 | {' '} 47 | 48 |
49 | { 50 | this.state.displayMatingOptions ? 51 | : 52 |
53 | } 54 |
55 | ) 56 | } 57 | } 58 | 59 | export default PublicDragonRow; -------------------------------------------------------------------------------- /frontend/src/components/PublicDragons.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchPublicDragons } from '../actions/publicDragons'; 4 | import { fetchAccountDragons } from '../actions/accountDragons'; 5 | import { Link } from 'react-router-dom'; 6 | import PublicDragonRow from './PublicDragonRow'; 7 | 8 | class PublicDragons extends Component { 9 | componentDidMount() { 10 | this.props.fetchPublicDragons(); 11 | this.props.fetchAccountDragons(); 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 |

Public Dragons

18 | { 19 | this.props.publicDragons.dragons.map(dragon => { 20 | return ( 21 |
22 | 23 |
24 |
25 | ) 26 | }) 27 | } 28 | Home 29 |
30 | ) 31 | } 32 | } 33 | 34 | export default connect( 35 | ({ publicDragons }) => ({ publicDragons }), 36 | { fetchPublicDragons, fetchAccountDragons } 37 | )(PublicDragons); -------------------------------------------------------------------------------- /frontend/src/components/Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Home from './Home'; 4 | import AuthForm from './AuthForm'; 5 | 6 | class Root extends Component { 7 | render() { 8 | return ( 9 | this.props.account.loggedIn ? : 10 | ) 11 | } 12 | }; 13 | 14 | export default connect( 15 | ({ account }) => ({ account }), 16 | null 17 | )(Root); -------------------------------------------------------------------------------- /frontend/src/config.js: -------------------------------------------------------------------------------- 1 | const BACKEND = { 2 | ADDRESS: 'http://localhost:3000' 3 | }; 4 | 5 | export { BACKEND }; -------------------------------------------------------------------------------- /frontend/src/history.js: -------------------------------------------------------------------------------- 1 | import createBrowserHistory from 'history/createBrowserHistory'; 2 | 3 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | margin: 10%; 4 | font-size: 20px; 5 | } 6 | 7 | .logout-button { 8 | position: absolute; 9 | right: 30px; 10 | top: 30px; 11 | } 12 | 13 | .account-dragon-row-input { 14 | width: 100px; 15 | height: 20px; 16 | margin-right: 15px; 17 | } 18 | 19 | .dragon-avatar-image-wrapper { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | 25 | .dragon-avatar-image-pattern { 26 | position: absolute; 27 | } 28 | 29 | .dragon-avatar-image-background { 30 | position: absolute; 31 | } 32 | 33 | .dragon-avatar-image { 34 | z-index: 0; 35 | } -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import { Provider } from 'react-redux'; 4 | import { Router, Switch, Route, Redirect } from 'react-router-dom'; 5 | import { render } from 'react-dom'; 6 | import thunk from 'redux-thunk'; 7 | import rootReducer from './reducers'; 8 | import history from './history'; 9 | import Root from './components/Root'; 10 | import AccountDragons from './components/AccountDragons'; 11 | import PublicDragons from './components/PublicDragons'; 12 | import { fetchAuthenticated } from './actions/account'; 13 | import './index.css'; 14 | 15 | const store = createStore( 16 | rootReducer, 17 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), 18 | applyMiddleware(thunk) 19 | ); 20 | 21 | const AuthRoute = props => { 22 | if (!store.getState().account.loggedIn) { 23 | return 24 | } 25 | 26 | const { component, path } = props; 27 | 28 | return ; 29 | } 30 | 31 | store.dispatch(fetchAuthenticated()) 32 | .then(() => { 33 | render( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | , 43 | document.getElementById('root') 44 | ); 45 | }); 46 | -------------------------------------------------------------------------------- /frontend/src/reducers/account.js: -------------------------------------------------------------------------------- 1 | import { ACCOUNT } from '../actions/types'; 2 | import fetchStates from './fetchStates'; 3 | 4 | const DEFAULT_ACCOUNT = { loggedIn: false }; 5 | 6 | const account = (state = DEFAULT_ACCOUNT, action) => { 7 | switch(action.type) { 8 | case ACCOUNT.FETCH: 9 | return { ...state, status: fetchStates.fetching }; 10 | case ACCOUNT.FETCH_ERROR: 11 | return { ...state, status: fetchStates.error, message: action.message } 12 | case ACCOUNT.FETCH_SUCCESS: 13 | return { 14 | ...state, 15 | status: fetchStates.success, 16 | message: action.message, 17 | loggedIn: true 18 | }; 19 | case ACCOUNT.FETCH_LOGOUT_SUCCESS: 20 | return { 21 | ...state, 22 | status: fetchStates.success, 23 | message: action.message, 24 | loggedIn: false 25 | }; 26 | case ACCOUNT.FETCH_AUTHENTICATED_SUCCESS: 27 | return { 28 | ...state, 29 | status: fetchStates.success, 30 | message: action.message, 31 | loggedIn: action.authenticated 32 | }; 33 | default: 34 | return state; 35 | } 36 | }; 37 | 38 | export default account; -------------------------------------------------------------------------------- /frontend/src/reducers/accountDragons.js: -------------------------------------------------------------------------------- 1 | import { ACCOUNT_DRAGONS } from '../actions/types'; 2 | import fetchStates from './fetchStates'; 3 | 4 | const DEFAULT_ACCOUNT_DRAGONS = { dragons: [] }; 5 | 6 | const accountDragons = (state = DEFAULT_ACCOUNT_DRAGONS, action) => { 7 | switch(action.type) { 8 | case ACCOUNT_DRAGONS.FETCH: 9 | return { ...state, status: fetchStates.fetching }; 10 | case ACCOUNT_DRAGONS.FETCH_ERROR: 11 | return { ...state, status: fetchStates.error, message: action.message }; 12 | case ACCOUNT_DRAGONS.FETCH_SUCCESS: 13 | return { 14 | ...state, 15 | status: fetchStates.success, 16 | message: action.message, 17 | dragons: action.dragons 18 | }; 19 | default: 20 | return state; 21 | } 22 | } 23 | 24 | export default accountDragons; -------------------------------------------------------------------------------- /frontend/src/reducers/accountInfo.js: -------------------------------------------------------------------------------- 1 | import { ACCOUNT_INFO } from '../actions/types'; 2 | import fetchStates from './fetchStates'; 3 | 4 | const accountInfo = (state = {}, action) => { 5 | switch (action.type) { 6 | case ACCOUNT_INFO.FETCH: 7 | return { ...state, status: fetchStates.fetching }; 8 | case ACCOUNT_INFO.FETCH_ERROR: 9 | return { ...state, status: fetchStates.error, message: action.message }; 10 | case ACCOUNT_INFO.FETCH_SUCCESS: 11 | return { 12 | ...state, 13 | status: fetchStates.success, 14 | message: action.message, 15 | ...action.info 16 | }; 17 | default: 18 | return state; 19 | } 20 | } 21 | 22 | export default accountInfo; 23 | -------------------------------------------------------------------------------- /frontend/src/reducers/dragon.js: -------------------------------------------------------------------------------- 1 | import { DRAGON } from '../actions/types'; 2 | import fetchStates from './fetchStates'; 3 | 4 | const DEFAULT_DRAGON = { 5 | generationId: '', 6 | dragonId: '', 7 | nickname: '', 8 | birthdate: '', 9 | traits: [] 10 | }; 11 | 12 | const dragon = (state = DEFAULT_DRAGON, action) => { 13 | switch(action.type) { 14 | case DRAGON.FETCH: 15 | return { ...state, status: fetchStates.fetching }; 16 | case DRAGON.FETCH_ERROR: 17 | return { ...state, status: fetchStates.error, message: action.message }; 18 | case DRAGON.FETCH_SUCCESS: 19 | return { ...state, status: fetchStates.success, ...action.dragon }; 20 | default: 21 | return state; 22 | }; 23 | }; 24 | 25 | export default dragon; -------------------------------------------------------------------------------- /frontend/src/reducers/fetchStates.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fetching: 'fetching', 3 | error: 'error', 4 | success: 'success' 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/reducers/generation.js: -------------------------------------------------------------------------------- 1 | import { GENERATION } from '../actions/types'; 2 | import fetchStates from './fetchStates'; 3 | 4 | const DEFAULT_GENERATION = { generationId: '', expiration: '' }; 5 | 6 | const generationReducer = (state = DEFAULT_GENERATION, action) => { 7 | switch(action.type) { 8 | case GENERATION.FETCH: 9 | return { ...state, status: fetchStates.fetching }; 10 | case GENERATION.FETCH_ERROR: 11 | return { ...state, status: fetchStates.error, message: action.message }; 12 | case GENERATION.FETCH_SUCCESS: 13 | return { ...state, status: fetchStates.success, ...action.generation }; 14 | default: 15 | return state; 16 | } 17 | } 18 | 19 | export default generationReducer; -------------------------------------------------------------------------------- /frontend/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import generation from './generation'; 3 | import dragon from './dragon'; 4 | import account from './account'; 5 | import accountDragons from './accountDragons'; 6 | import accountInfo from './accountInfo'; 7 | import publicDragons from './publicDragons'; 8 | 9 | export default combineReducers({ 10 | account, 11 | dragon, 12 | generation, 13 | accountDragons, 14 | accountInfo, 15 | publicDragons 16 | }); -------------------------------------------------------------------------------- /frontend/src/reducers/publicDragons.js: -------------------------------------------------------------------------------- 1 | import { PUBLIC_DRAGONS } from '../actions/types'; 2 | import fetchStates from './fetchStates'; 3 | 4 | const DEFAULT_PUBLIC_DRAGONS = { dragons: [] }; 5 | 6 | const publicDragons = (state = DEFAULT_PUBLIC_DRAGONS, action) => { 7 | switch(action.type) { 8 | case PUBLIC_DRAGONS.FETCH: 9 | return { ...state, status: fetchStates.fetching }; 10 | case PUBLIC_DRAGONS.FETCH_ERROR: 11 | return { ...state, status: fetchStates.error, message: action.message }; 12 | case PUBLIC_DRAGONS.FETCH_SUCCESS: 13 | return { ...state, status: fetchStates.success, dragons: action.dragons }; 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | export default publicDragons; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragonstack", 3 | "version": "1.0.0", 4 | "description": "The experimentation code for the *Master Full-Stack Development | Node, SQL, React, and More* course on Udemy by David Joseph Katz.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nf start", 9 | "backend": "nodemon backend/bin/server", 10 | "frontend": "parcel frontend/src/index.html" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/15Dkatz/dragonstack-scripts.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/15Dkatz/dragonstack-scripts/issues" 21 | }, 22 | "homepage": "https://github.com/15Dkatz/dragonstack-scripts#readme", 23 | "devDependencies": { 24 | "nodemon": "^1.18.4" 25 | } 26 | } 27 | --------------------------------------------------------------------------------