├── .DS_Store ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── bin ├── compile.js ├── dev-server.js └── io.js ├── config ├── environments.config.js ├── karma.config.js ├── project.config.js └── webpack.config.js ├── db ├── database.js ├── friendsAndFamilySeed.js ├── seed.js └── simpleseed.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── CodeBattle.png ├── Ming.jpg ├── MitchHeadShot.jpg ├── Patryk.jpg ├── background.jpg ├── favicon.ico ├── headshot_johnyom.jpg ├── humans.txt ├── quickPlay.png └── robots.txt ├── server ├── dockerTest.js ├── main.js ├── routes │ ├── clients.js │ ├── code.js │ ├── fights.js │ ├── index.js │ ├── join.js │ ├── questions.js │ └── users.js ├── runDocker.js └── sockets │ └── socketCallbacks.js ├── src ├── .DS_Store ├── components │ ├── Header │ │ ├── Header.js │ │ ├── Header.scss │ │ └── index.js │ └── Login │ │ ├── Login.jsx │ │ └── Login.scss ├── containers │ └── AppContainer.js ├── index.html ├── layouts │ └── CoreLayout │ │ ├── CoreLayout.js │ │ ├── CoreLayout.scss │ │ └── index.js ├── main.js ├── routes │ ├── About │ │ ├── About.js │ │ └── About.scss │ ├── BattlePage │ │ ├── .DS_Store │ │ ├── assets │ │ │ └── jurass01.mp3 │ │ ├── components │ │ │ ├── CountdownClock.js │ │ │ ├── Modal.js │ │ │ ├── Problem.jsx │ │ │ ├── Problem.scss │ │ │ ├── ProgressBar.js │ │ │ └── ProgressBar.scss │ │ ├── containers │ │ │ ├── BattlePageContainer.jsx │ │ │ └── BattlePageContainer.scss │ │ └── index.js │ ├── CodeEditor │ │ ├── components │ │ │ ├── CodeEditor.js │ │ │ └── CodeEditor.scss │ │ ├── container │ │ │ └── CodeEditorContainer.js │ │ └── index.js │ ├── Counter │ │ ├── components │ │ │ └── Counter.js │ │ ├── containers │ │ │ └── CounterContainer.js │ │ ├── index.js │ │ └── modules │ │ │ └── counter.js │ ├── GameFinishedPage │ │ └── components │ │ │ ├── GameFinishedPage.js │ │ │ └── GameFinishedPage.scss │ ├── GameLobby │ │ ├── components │ │ │ ├── GameLobby.jsx │ │ │ └── GameLobby.scss │ │ └── containers │ │ │ └── GameLobbyContainer.js │ ├── GameWonPage │ │ ├── .DS_Store │ │ ├── assets │ │ │ └── fireworks.gif │ │ └── components │ │ │ ├── GameWonPage.js │ │ │ └── GameWonPage.scss │ ├── Home │ │ ├── .DS_Store │ │ ├── assets │ │ │ ├── Duck.jpg │ │ │ ├── Logo.png │ │ │ ├── ninja_attack.png │ │ │ └── ninja_bowing.jpg │ │ ├── components │ │ │ ├── Achievements │ │ │ │ ├── Achievements.js │ │ │ │ ├── Achievements.scss │ │ │ │ └── index.js │ │ │ ├── HomeView.js │ │ │ ├── HomeView.scss │ │ │ ├── Leaderboard │ │ │ │ ├── Leaderboard.js │ │ │ │ ├── index.js │ │ │ │ └── leaderboard.scss │ │ │ ├── MatchHistory.js │ │ │ ├── MatchHistory.jsx │ │ │ ├── Matchhistory.scss │ │ │ └── homePage │ │ │ │ ├── homePage.js │ │ │ │ ├── homePage.scss │ │ │ │ └── index.js │ │ └── index.js │ ├── InvitePage │ │ └── components │ │ │ └── InvitePage.jsx │ ├── JoinLobby │ │ └── JoinLobby.jsx │ ├── JoinPage │ │ └── components │ │ │ └── JoinPage.jsx │ ├── MainLobbyList │ │ ├── components │ │ │ └── MainLobby.jsx │ │ └── containers │ │ │ └── MainLobbyContainer.jsx │ ├── Profile │ │ ├── components │ │ │ └── Profile.jsx │ │ └── profile.scss │ ├── ReportABug │ │ ├── ReportABug.scss │ │ └── components │ │ │ └── ReportABug.jsx │ ├── Signup │ │ ├── Signup.scss │ │ └── components │ │ │ └── Signup.jsx │ └── index.js ├── store │ ├── CodeEditorStore.js │ ├── client.js │ ├── createStore.js │ ├── gamelobby.js │ ├── location.js │ ├── match.js │ ├── matchresult.js │ ├── profile.js │ ├── reducers.js │ └── user.js └── styles │ ├── _base.scss │ ├── core.scss │ └── styles.css └── tests ├── .eslintrc ├── layouts └── CoreLayout.spec.js ├── routes └── Counter │ ├── components │ └── Counter.spec.js │ ├── index.spec.js │ └── modules │ └── counter.spec.js ├── store ├── client.spec.js ├── createStore.spec.js ├── gamelobby.spec.js ├── location.spec.js ├── match.spec.js ├── matchresult.spec.js ├── profile.spec.js └── user.spec.js └── test-bundler.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | npm-debug.log.* 4 | src/routes/.DS_Store 5 | bower_components 6 | coverage/* 7 | dist 8 | .env -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Some basic conventions for contributing to this project. 4 | 5 | ### General 6 | 7 | Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork. 8 | 9 | * Non-trivial changes should be discussed in an issue first 10 | * Develop in a topic branch, not master 11 | * Squash your commits 12 | 13 | ### Linting 14 | 15 | Please check your code using `npm run lint` before submitting your pull requests, as the CI build will fail if `eslint` fails. 16 | 17 | ### Commit Message Format 18 | 19 | Each commit message should include a **type**, a **scope** and a **subject**: 20 | 21 | ``` 22 | (): 23 | ``` 24 | 25 | Lines should not exceed 100 characters. This allows the message to be easier to read on github as well as in various git tools and produces a nice, neat commit log ie: 26 | 27 | ``` 28 | #271 feat(standard): add style config and refactor to match 29 | #270 fix(config): only override publicPath when served by webpack 30 | #269 feat(eslint-config-defaults): replace eslint-config-airbnb 31 | #268 feat(config): allow user to configure webpack stats output 32 | ``` 33 | 34 | #### Type 35 | 36 | Must be one of the following: 37 | 38 | * **feat**: A new feature 39 | * **fix**: A bug fix 40 | * **docs**: Documentation only changes 41 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing 42 | semi-colons, etc) 43 | * **refactor**: A code change that neither fixes a bug or adds a feature 44 | * **test**: Adding missing tests 45 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation 46 | generation 47 | 48 | #### Scope 49 | 50 | The scope could be anything specifying place of the commit change. For example `webpack`, 51 | `babel`, `redux` etc... 52 | 53 | #### Subject 54 | 55 | The subject contains succinct description of the change: 56 | 57 | * use the imperative, present tense: "change" not "changed" nor "changes" 58 | * don't capitalize first letter 59 | * no dot (.) at the end 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Zukowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Battle 2 | 3 | Want to learn to code while having fun? Challenge your friends to a Code Battle! Whoever completes the coding challenges fastest is rewarded with a win and advances in rank. CodeBattle is designed to prepare students for success in the rigorous Fullstack Code Academy program. 4 | 5 | 6 | ## Stack 7 | 8 | CodeBattle uses the NERP stack as well as Docker on the back-end and Sockets.io to deliver a seamless experience to its users. 9 | 10 | 11 | Deployed at www.codebattle.win 12 | -------------------------------------------------------------------------------- /bin/compile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const webpack = require('webpack') 3 | const debug = require('debug')('app:bin:compile') 4 | const webpackConfig = require('../config/webpack.config') 5 | const project = require('../config/project.config') 6 | 7 | // Wrapper around webpack to promisify its compiler and supply friendly logging 8 | const webpackCompiler = (webpackConfig) => 9 | new Promise((resolve, reject) => { 10 | const compiler = webpack(webpackConfig) 11 | 12 | compiler.run((err, stats) => { 13 | if (err) { 14 | debug('Webpack compiler encountered a fatal error.', err) 15 | return reject(err) 16 | } 17 | 18 | const jsonStats = stats.toJson() 19 | debug('Webpack compile completed.') 20 | debug(stats.toString(project.compiler_stats)) 21 | 22 | if (jsonStats.errors.length > 0) { 23 | debug('Webpack compiler encountered errors.') 24 | debug(jsonStats.errors.join('\n')) 25 | return reject(new Error('Webpack compiler encountered errors')) 26 | } else if (jsonStats.warnings.length > 0) { 27 | debug('Webpack compiler encountered warnings.') 28 | debug(jsonStats.warnings.join('\n')) 29 | } else { 30 | debug('No errors or warnings encountered.') 31 | } 32 | resolve(jsonStats) 33 | }) 34 | }) 35 | 36 | const compile = () => { 37 | debug('Starting compiler.') 38 | return Promise.resolve() 39 | .then(() => webpackCompiler(webpackConfig)) 40 | .then(stats => { 41 | if (stats.warnings.length && project.compiler_fail_on_warning) { 42 | throw new Error('Config set to fail on warning, exiting with status code "1".') 43 | } 44 | debug('Copying static assets to dist folder.') 45 | fs.copySync(project.paths.public(), project.paths.dist()) 46 | }) 47 | .then(() => { 48 | debug('Compilation completed successfully.') 49 | }) 50 | .catch((err) => { 51 | debug('Compiler encountered an error.', err) 52 | process.exit(1) 53 | }) 54 | } 55 | 56 | compile() 57 | -------------------------------------------------------------------------------- /bin/dev-server.js: -------------------------------------------------------------------------------- 1 | const project = require('../config/project.config'); 2 | const server = require('../server/main'); 3 | const httpServer = server.listen(project.server_port) 4 | require('./io')(httpServer) 5 | const debug = require('debug')('app:bin:dev-server'); 6 | const socketCallbacks = require('../server/sockets/socketCallbacks'); 7 | 8 | debug(`Server is now running at http://localhost:${project.server_port}.`) 9 | 10 | -------------------------------------------------------------------------------- /bin/io.js: -------------------------------------------------------------------------------- 1 | const socketio = require('socket.io'); 2 | const socketCallbacks = require('../server/sockets/socketCallbacks'); 3 | const Question = require('../db/database').Question; 4 | const Sequelize = require('sequelize'); 5 | const User = require('../db/database').User; 6 | const Fight = require('../db/database').Fight 7 | let io = null; 8 | 9 | module.exports = function(server) { 10 | if (io) return io; 11 | io = socketio(server); 12 | const quickPlayQueue = []; 13 | io.on('connection', function(socket) { 14 | socket.on('setUser', payload => { 15 | payload.user.socketId = socket.id; 16 | socket.user = payload.user; 17 | socket.join('MainLobby'); 18 | io.emit('reload'); 19 | }); 20 | 21 | io.emit('reload'); 22 | //when a user wants to find an opponent 23 | socket.on('quickPlay', (data) => { 24 | 25 | function makeid() 26 | { 27 | var text = ""; 28 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 29 | for( var i=0; i < 6; i++ ) 30 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 31 | 32 | return text; 33 | } 34 | 35 | 36 | quickPlayQueue.push(socket); 37 | // console.log("quickPlayQueue:", quickPlayQueue); 38 | if(quickPlayQueue.length >= 2){ 39 | console.log("inside if check for quickPlayQueue"); 40 | const quickGameRoom = makeid(); 41 | var socket1 = quickPlayQueue.shift(); 42 | var socket2 = quickPlayQueue.shift(); 43 | console.log("quickPlayQueue length after 2 shifts: ", quickPlayQueue.length) 44 | io.to(socket1.id).emit('gameReady', quickGameRoom); 45 | io.to(socket2.id).emit('gameReady', quickGameRoom); 46 | } 47 | }) 48 | //joining and creating a game lobby 49 | socket.on('joinGameLobby', (data) => { 50 | //if the room doesn't exist 51 | if (io.sockets.adapter.rooms[data] === undefined) { 52 | socket.join(data); 53 | } else { 54 | //if there is one player already in the room we need to grab the questions from the DB 55 | //since the game is ready to start and we will have 2 players in the room 56 | if (io.sockets.adapter.rooms[data].length === 1) { 57 | socket.join(data); 58 | if(io.sockets.adapter.rooms[data].length > 1) io.in(data).emit('startGame', data); 59 | //get the random questions from the database 60 | Question.findAll({ 61 | //limit it to the number of questions you want to get 62 | limit: 2, 63 | //gets random rows from the questions table 64 | // order: [ 65 | // [Sequelize.fn('RANDOM')] 66 | // ] 67 | // order: [["id", "ASC"]] 68 | }) 69 | .then((setOfQuestions) => { 70 | //arrOfQuestionObj holds objects that contain each questions ID, Name, and Text 71 | const arrOfQuestionObjs = setOfQuestions.map((question) => { 72 | return { 73 | questionID: question.id, 74 | questionName: question.name, 75 | questionText: question.questionText 76 | } 77 | }) 78 | //grab the socket Id of all the connected sockets in the room 79 | 80 | const room = io.sockets.adapter.rooms[data]; 81 | let users = Object.keys(room.sockets).map(id => { 82 | const user = io.sockets.connected[id].user; 83 | return user; 84 | }) 85 | 86 | // console.log('user', users); 87 | 88 | //const arrOfSocketIDs = Object.keys(io.sockets.adapter.rooms[data].sockets); 89 | 90 | const gameData = { 91 | // assign player one to the socket that connected first to the room 92 | player1: users[0], 93 | //assign player two to the socket that connected second to the room 94 | player2: users[1], 95 | //stores the array of the question objects created above 96 | questions: arrOfQuestionObjs 97 | } 98 | // console.log("emit this question data", gameData); 99 | 100 | //emits the game data object to all the connected sockets in the room 101 | io.in(data).emit('sending Questions', gameData); 102 | }); 103 | //if someone else attempts to join the room and it already has 2 people in it 104 | } else if (io.sockets.adapter.rooms[data].length < 2) socket.join(data); 105 | else { 106 | //this emit will tell the person who is attempting to join a full room, that is already full 107 | socket.emit('game is full', 'The game lobby is already full'); 108 | } 109 | } 110 | 111 | }) 112 | 113 | socket.on('gameOver', (data) => { 114 | // console.log("gameover event ", data); 115 | 116 | Fight.create({ 117 | winnerId: data.winnerUserID, 118 | loserId: data.loserUserID, 119 | winnerDuration: data.time 120 | }) 121 | .then((savedFight => { 122 | User.findById(data.winnerUserID) 123 | .then((winningUser) => { 124 | winningUser.update({wins: (winningUser.wins + 1), points: (winningUser.points + 25)}) 125 | 126 | }) 127 | User.findById(data.loserUserID) 128 | .then((losingUser) => { 129 | losingUser.update({losses: (losingUser.losses + 1), points: (losingUser.points - 25)}) 130 | }) 131 | })) 132 | 133 | 134 | io.in(data.roomID).emit('gameWinningState', data); 135 | }) 136 | 137 | var currentClients = io.sockets.adapter.rooms["MainLobby"]; 138 | 139 | socket.on('correct response', socketCallbacks.updatePlayerProgress); 140 | 141 | socket.on('sending attack', (data) => { 142 | var room = io.sockets.adapter.rooms[data]; 143 | let users = Object.keys(room.sockets).map(id => { 144 | let user = io.sockets.connected[id].user; 145 | return user; 146 | }) 147 | if (socket.id === users[0].socketId){ 148 | io.to(data).emit('receive attack', users[1].socketId) 149 | } else { 150 | io.to(data).emit('receive attack', users[0].socketId) 151 | } 152 | 153 | }) 154 | 155 | socket.on('disconnect', () => socketCallbacks.reloadLobby(io)) 156 | }); 157 | 158 | return io; 159 | } 160 | -------------------------------------------------------------------------------- /config/environments.config.js: -------------------------------------------------------------------------------- 1 | // Here is where you can define configuration overrides based on the execution environment. 2 | // Supply a key to the default export matching the NODE_ENV that you wish to target, and 3 | // the base configuration will apply your overrides before exporting itself. 4 | module.exports = { 5 | // ====================================================== 6 | // Overrides when NODE_ENV === 'development' 7 | // ====================================================== 8 | // NOTE: In development, we use an explicit public path when the assets 9 | // are served webpack by to fix this issue: 10 | // http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809 11 | development : (config) => ({ 12 | compiler_public_path : `http://${config.server_host}:${config.server_port}/` 13 | }), 14 | 15 | // ====================================================== 16 | // Overrides when NODE_ENV === 'production' 17 | // ====================================================== 18 | production : (config) => ({ 19 | compiler_public_path : '/', 20 | compiler_fail_on_warning : false, 21 | compiler_hash_type : 'chunkhash', 22 | compiler_devtool : null, 23 | compiler_stats : { 24 | chunks : true, 25 | chunkModules : true, 26 | colors : true 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /config/karma.config.js: -------------------------------------------------------------------------------- 1 | const argv = require('yargs').argv 2 | const project = require('./project.config') 3 | const webpackConfig = require('./webpack.config') 4 | const debug = require('debug')('app:config:karma') 5 | 6 | debug('Creating configuration.') 7 | const karmaConfig = { 8 | basePath : '../', // project root in relation to bin/karma.js 9 | files : [ 10 | { 11 | pattern : `./${project.dir_test}/test-bundler.js`, 12 | watched : false, 13 | served : true, 14 | included : true 15 | } 16 | ], 17 | singleRun : !argv.watch, 18 | frameworks : ['mocha'], 19 | reporters : ['mocha'], 20 | preprocessors : { 21 | [`${project.dir_test}/test-bundler.js`] : ['webpack'] 22 | }, 23 | browsers : ['PhantomJS'], 24 | webpack : { 25 | devtool : 'cheap-module-source-map', 26 | resolve : Object.assign({}, webpackConfig.resolve, { 27 | alias : Object.assign({}, webpackConfig.resolve.alias, { 28 | sinon : 'sinon/pkg/sinon.js' 29 | }) 30 | }), 31 | plugins : webpackConfig.plugins, 32 | module : { 33 | noParse : [ 34 | /\/sinon\.js/ 35 | ], 36 | loaders : webpackConfig.module.loaders.concat([ 37 | { 38 | test : /sinon(\\|\/)pkg(\\|\/)sinon\.js/, 39 | loader : 'imports?define=>false,require=>false' 40 | } 41 | ]) 42 | }, 43 | // Enzyme fix, see: 44 | // https://github.com/airbnb/enzyme/issues/47 45 | externals : Object.assign({}, webpackConfig.externals, { 46 | 'react/addons' : true, 47 | 'react/lib/ExecutionEnvironment' : true, 48 | 'react/lib/ReactContext' : 'window' 49 | }), 50 | sassLoader : webpackConfig.sassLoader 51 | }, 52 | webpackMiddleware : { 53 | noInfo : true 54 | }, 55 | coverageReporter : { 56 | reporters : project.coverage_reporters 57 | } 58 | } 59 | 60 | if (project.globals.__COVERAGE__) { 61 | karmaConfig.reporters.push('coverage') 62 | karmaConfig.webpack.module.preLoaders = [{ 63 | test : /\.(js|jsx)$/, 64 | include : new RegExp(project.dir_client), 65 | exclude : /node_modules/, 66 | loader : 'babel', 67 | query : Object.assign({}, project.compiler_babel, { 68 | plugins : (project.compiler_babel.plugins || []).concat('istanbul') 69 | }) 70 | }] 71 | } 72 | 73 | module.exports = (cfg) => cfg.set(karmaConfig) 74 | -------------------------------------------------------------------------------- /config/project.config.js: -------------------------------------------------------------------------------- 1 | /* eslint key-spacing:0 spaced-comment:0 */ 2 | const path = require('path') 3 | const debug = require('debug')('app:config:project') 4 | const argv = require('yargs').argv 5 | const ip = require('ip') 6 | 7 | debug('Creating default configuration.') 8 | // ======================================================== 9 | // Default Configuration 10 | // ======================================================== 11 | const config = { 12 | env : process.env.NODE_ENV || 'development', 13 | 14 | // ---------------------------------- 15 | // Project Structure 16 | // ---------------------------------- 17 | path_base : path.resolve(__dirname, '..'), 18 | dir_client : 'src', 19 | dir_dist : 'dist', 20 | dir_public : 'public', 21 | dir_server : 'server', 22 | dir_test : 'tests', 23 | 24 | // ---------------------------------- 25 | // Server Configuration 26 | // ---------------------------------- 27 | server_host : 'localhost', // use string 'localhost' to prevent exposure on local network 28 | server_port : process.env.PORT || 3000, 29 | 30 | 31 | 32 | // ---------------------------------- 33 | // Compiler Configuration 34 | // ---------------------------------- 35 | compiler_babel : { 36 | cacheDirectory : true, 37 | plugins : ['transform-runtime'], 38 | presets : ['es2015', 'react', 'stage-0'] 39 | }, 40 | compiler_devtool : 'source-map', 41 | compiler_hash_type : 'hash', 42 | compiler_fail_on_warning : false, 43 | compiler_quiet : false, 44 | compiler_public_path : '/', 45 | compiler_stats : { 46 | chunks : false, 47 | chunkModules : false, 48 | colors : true 49 | }, 50 | compiler_vendors : [ 51 | 'react', 52 | 'react-redux', 53 | 'react-router', 54 | 'redux' 55 | ], 56 | 57 | // ---------------------------------- 58 | // Test Configuration 59 | // ---------------------------------- 60 | coverage_reporters : [ 61 | { type : 'text-summary' }, 62 | { type : 'lcov', dir : 'coverage' } 63 | ] 64 | } 65 | 66 | /************************************************ 67 | ------------------------------------------------- 68 | 69 | All Internal Configuration Below 70 | Edit at Your Own Risk 71 | 72 | ------------------------------------------------- 73 | ************************************************/ 74 | 75 | // ------------------------------------ 76 | // Environment 77 | // ------------------------------------ 78 | // N.B.: globals added here must _also_ be added to .eslintrc 79 | config.globals = { 80 | 'process.env' : { 81 | 'NODE_ENV' : JSON.stringify(config.env) 82 | }, 83 | 'NODE_ENV' : config.env, 84 | '__DEV__' : config.env === 'development', 85 | '__PROD__' : config.env === 'production', 86 | '__TEST__' : config.env === 'test', 87 | '__COVERAGE__' : !argv.watch && config.env === 'test', 88 | '__BASENAME__' : JSON.stringify(process.env.BASENAME || '') 89 | } 90 | 91 | // ------------------------------------ 92 | // Validate Vendor Dependencies 93 | // ------------------------------------ 94 | const pkg = require('../package.json') 95 | 96 | config.compiler_vendors = config.compiler_vendors 97 | .filter((dep) => { 98 | if (pkg.dependencies[dep]) return true 99 | 100 | debug( 101 | `Package "${dep}" was not found as an npm dependency in package.json; ` + 102 | `it won't be included in the webpack vendor bundle. 103 | Consider removing it from \`compiler_vendors\` in ~/config/index.js` 104 | ) 105 | }) 106 | 107 | // ------------------------------------ 108 | // Utilities 109 | // ------------------------------------ 110 | function base () { 111 | const args = [config.path_base].concat([].slice.call(arguments)) 112 | return path.resolve.apply(path, args) 113 | } 114 | 115 | config.paths = { 116 | base : base, 117 | client : base.bind(null, config.dir_client), 118 | public : base.bind(null, config.dir_public), 119 | dist : base.bind(null, config.dir_dist) 120 | } 121 | 122 | // ======================================================== 123 | // Environment Configuration 124 | // ======================================================== 125 | debug(`Looking for environment overrides for NODE_ENV "${config.env}".`) 126 | const environments = require('./environments.config') 127 | const overrides = environments[config.env] 128 | if (overrides) { 129 | debug('Found overrides, applying to default configuration.') 130 | Object.assign(config, overrides(config)) 131 | } else { 132 | debug('No environment overrides found, defaults will be used.') 133 | } 134 | 135 | module.exports = config 136 | -------------------------------------------------------------------------------- /config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const argv = require('yargs').argv 2 | const webpack = require('webpack') 3 | const cssnano = require('cssnano') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 6 | const project = require('./project.config') 7 | const debug = require('debug')('app:config:webpack') 8 | 9 | const __DEV__ = project.globals.__DEV__ 10 | const __PROD__ = project.globals.__PROD__ 11 | const __TEST__ = project.globals.__TEST__ 12 | 13 | debug('Creating configuration.') 14 | const webpackConfig = { 15 | name : 'client', 16 | target : 'web', 17 | devtool : project.compiler_devtool, 18 | resolve : { 19 | root : project.paths.client(), 20 | extensions : ['', '.js', '.jsx', '.json'] 21 | }, 22 | module : {} 23 | } 24 | // ------------------------------------ 25 | // Entry Points 26 | // ------------------------------------ 27 | const APP_ENTRY = project.paths.client('main.js') 28 | 29 | webpackConfig.entry = { 30 | app : __DEV__ 31 | ? [APP_ENTRY].concat(`webpack-hot-middleware/client?path=${project.compiler_public_path}__webpack_hmr`) 32 | : [APP_ENTRY], 33 | vendor : project.compiler_vendors 34 | } 35 | 36 | // ------------------------------------ 37 | // Bundle Output 38 | // ------------------------------------ 39 | webpackConfig.output = { 40 | filename : `[name].[${project.compiler_hash_type}].js`, 41 | path : project.paths.dist(), 42 | publicPath : project.compiler_public_path 43 | } 44 | 45 | // ------------------------------------ 46 | // Externals 47 | // ------------------------------------ 48 | webpackConfig.externals = {} 49 | webpackConfig.externals['react/lib/ExecutionEnvironment'] = true 50 | webpackConfig.externals['react/lib/ReactContext'] = true 51 | webpackConfig.externals['react/addons'] = true 52 | 53 | // ------------------------------------ 54 | // Plugins 55 | // ------------------------------------ 56 | webpackConfig.plugins = [ 57 | new webpack.DefinePlugin(project.globals), 58 | new HtmlWebpackPlugin({ 59 | template : project.paths.client('index.html'), 60 | hash : false, 61 | favicon : project.paths.public('favicon.ico'), 62 | filename : 'index.html', 63 | inject : 'body', 64 | minify : { 65 | collapseWhitespace : true 66 | } 67 | }) 68 | ] 69 | 70 | // Ensure that the compiler exits on errors during testing so that 71 | // they do not get skipped and misreported. 72 | if (__TEST__ && !argv.watch) { 73 | webpackConfig.plugins.push(function () { 74 | this.plugin('done', function (stats) { 75 | if (stats.compilation.errors.length) { 76 | // Pretend no assets were generated. This prevents the tests 77 | // from running making it clear that there were warnings. 78 | throw new Error( 79 | stats.compilation.errors.map(err => err.message || err) 80 | ) 81 | } 82 | }) 83 | }) 84 | } 85 | 86 | if (__DEV__) { 87 | debug('Enabling plugins for live development (HMR, NoErrors).') 88 | webpackConfig.plugins.push( 89 | new webpack.HotModuleReplacementPlugin(), 90 | new webpack.NoErrorsPlugin() 91 | ) 92 | } else if (__PROD__) { 93 | debug('Enabling plugins for production (OccurenceOrder, Dedupe & UglifyJS).') 94 | webpackConfig.plugins.push( 95 | new webpack.optimize.OccurrenceOrderPlugin(), 96 | new webpack.optimize.DedupePlugin(), 97 | new webpack.optimize.UglifyJsPlugin({ 98 | compress : { 99 | unused : true, 100 | dead_code : true, 101 | warnings : false 102 | } 103 | }), 104 | new webpack.optimize.AggressiveMergingPlugin() 105 | ) 106 | } 107 | 108 | // Don't split bundles during testing, since we only want import one bundle 109 | if (!__TEST__) { 110 | webpackConfig.plugins.push( 111 | new webpack.optimize.CommonsChunkPlugin({ 112 | names : ['vendor'] 113 | }) 114 | ) 115 | } 116 | 117 | // ------------------------------------ 118 | // Loaders 119 | // ------------------------------------ 120 | // JavaScript / JSON 121 | webpackConfig.module.loaders = [{ 122 | test : /\.(js|jsx)$/, 123 | exclude : /node_modules/, 124 | loader : 'babel', 125 | query : project.compiler_babel 126 | }, { 127 | test : /\.json$/, 128 | loader : 'json' 129 | }] 130 | 131 | // ------------------------------------ 132 | // Style Loaders 133 | // ------------------------------------ 134 | // We use cssnano with the postcss loader, so we tell 135 | // css-loader not to duplicate minimization. 136 | const BASE_CSS_LOADER = 'css?sourceMap&-minimize' 137 | 138 | webpackConfig.module.loaders.push({ 139 | test : /\.scss$/, 140 | exclude : null, 141 | loaders : [ 142 | 'style', 143 | BASE_CSS_LOADER, 144 | 'postcss', 145 | 'sass?sourceMap' 146 | ] 147 | }) 148 | webpackConfig.module.loaders.push({ 149 | test : /\.css$/, 150 | exclude : null, 151 | loaders : [ 152 | 'style', 153 | BASE_CSS_LOADER, 154 | 'postcss', 155 | 'sass?sourceMap' 156 | ] 157 | }) 158 | 159 | webpackConfig.sassLoader = { 160 | includePaths : project.paths.client('styles') 161 | } 162 | 163 | webpackConfig.postcss = [ 164 | cssnano({ 165 | autoprefixer : { 166 | add : true, 167 | remove : true, 168 | browsers : ['last 2 versions'] 169 | }, 170 | discardComments : { 171 | removeAll : true 172 | }, 173 | discardUnused : false, 174 | mergeIdents : false, 175 | reduceIdents : false, 176 | safe : true, 177 | sourcemap : true 178 | }) 179 | ] 180 | 181 | // File loaders 182 | /* eslint-disable */ 183 | webpackConfig.module.loaders.push( 184 | { test: /\.woff(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' }, 185 | { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' }, 186 | { test: /\.otf(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' }, 187 | { test: /\.ttf(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' }, 188 | { test: /\.eot(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]' }, 189 | { test: /\.svg(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' }, 190 | { test: /\.(png|jpg)$/, loader: 'url?limit=8192' } 191 | ) 192 | /* eslint-enable */ 193 | 194 | // ------------------------------------ 195 | // Finalize Configuration 196 | // ------------------------------------ 197 | // when we don't know the public path (we know it only when HMR is enabled [in development]) we 198 | // need to use the extractTextPlugin to fix this issue: 199 | // http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809 200 | if (!__DEV__) { 201 | debug('Applying ExtractTextPlugin to CSS loaders.') 202 | webpackConfig.module.loaders.filter((loader) => 203 | loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0])) 204 | ).forEach((loader) => { 205 | const first = loader.loaders[0] 206 | const rest = loader.loaders.slice(1) 207 | loader.loader = ExtractTextPlugin.extract(first, rest.join('!')) 208 | delete loader.loaders 209 | }) 210 | 211 | webpackConfig.plugins.push( 212 | new ExtractTextPlugin('[name].[contenthash].css', { 213 | allChunks : true 214 | }) 215 | ) 216 | } 217 | 218 | 219 | module.exports = webpackConfig 220 | -------------------------------------------------------------------------------- /db/database.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require('sequelize'); 2 | require('dotenv').config(); 3 | 4 | const db = new Sequelize(process.env["DATABASE_URL"], { 5 | logging: false, 6 | dialect: 'postgres', 7 | dialectOptions: { 8 | ssl: true 9 | } 10 | }); 11 | 12 | const Bug = db.define('bugs', { 13 | bugName: { 14 | type: Sequelize.STRING 15 | }, 16 | bugDescription: { 17 | type: Sequelize.TEXT 18 | }, 19 | name: { 20 | type: Sequelize.STRING, 21 | } 22 | }) 23 | 24 | const User = db.define('users', { 25 | username: { 26 | type: Sequelize.STRING, 27 | unique: true 28 | }, 29 | password: { 30 | type: Sequelize.STRING, 31 | }, 32 | name: { 33 | type: Sequelize.STRING, 34 | }, 35 | wins: { 36 | type: Sequelize.INTEGER, 37 | defaultValue: 0 38 | }, 39 | losses: { 40 | type: Sequelize.INTEGER, 41 | defaultValue: 0 42 | }, 43 | points: { 44 | type: Sequelize.INTEGER, 45 | defaultValue: 1000 46 | }, 47 | 48 | email: { 49 | type: Sequelize.STRING, 50 | validate: { 51 | isEmail: true, 52 | notEmpty: true, 53 | } 54 | } 55 | }) 56 | 57 | const Question = db.define('questions', { 58 | name: { 59 | type: Sequelize.STRING, 60 | }, 61 | questionText: { 62 | type: Sequelize.TEXT 63 | }, 64 | tests: { 65 | type: Sequelize.TEXT 66 | } 67 | }) 68 | 69 | const Fight = db.define('fights', { 70 | winnerDuration: { 71 | type: Sequelize.FLOAT, 72 | } 73 | }) 74 | 75 | Fight.belongsTo(User, {as: 'winner'}); 76 | Fight.belongsTo(User, {as: 'loser'}); 77 | 78 | module.exports = { 79 | db, 80 | User, 81 | Question, 82 | Fight, 83 | Bug 84 | }; 85 | -------------------------------------------------------------------------------- /db/simpleseed.js: -------------------------------------------------------------------------------- 1 | //Just wipes the db and syncs up new models 2 | require('./database').db.sync({force: true}).then(() => console.log('db dropped')); 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-starter-kit", 3 | "version": "3.0.0-alpha.2", 4 | "description": "Get started with React, Redux, and React-Router!", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=4.5.0", 8 | "npm": "^3.0.0" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf dist", 12 | "compile": "better-npm-run compile", 13 | "lint": "eslint bin build config server src tests", 14 | "lint:fix": "npm run lint -- --fix", 15 | "dev": "better-npm-run dev", 16 | "test": "better-npm-run test", 17 | "test:dev": "npm run test -- --watch", 18 | "deploy": "better-npm-run deploy", 19 | "deploy:dev": "better-npm-run deploy:dev", 20 | "deploy:prod": "better-npm-run deploy:prod", 21 | "codecov": "cat coverage/*/lcov.info | codecov", 22 | "seed": "node db/seed.js", 23 | "simple": "node db/simpleseed", 24 | "start": "npm run seed && npm run deploy:prod && nodemon server/main.js" 25 | }, 26 | "betterScripts": { 27 | "compile": { 28 | "command": "node bin/compile", 29 | "env": { 30 | "DEBUG": "app:*" 31 | } 32 | }, 33 | "dev": { 34 | "command": "nodemon bin/dev-server --ignore dist --ignore coverage --ignore tests --ignore src", 35 | "env": { 36 | "NODE_ENV": "development", 37 | "DEBUG": "app:*" 38 | } 39 | }, 40 | "deploy": { 41 | "command": "npm run clean && npm run compile", 42 | "env": { 43 | "DEBUG": "app:*" 44 | } 45 | }, 46 | "deploy:dev": { 47 | "command": "npm run deploy", 48 | "env": { 49 | "NODE_ENV": "development", 50 | "DEBUG": "app:*" 51 | } 52 | }, 53 | "deploy:prod": { 54 | "command": "npm run deploy", 55 | "env": { 56 | "NODE_ENV": "production", 57 | "DEBUG": "app:*" 58 | } 59 | }, 60 | "start": { 61 | "command": "node bin/dev-server", 62 | "env": { 63 | "DEBUG": "app:*" 64 | } 65 | }, 66 | "test": { 67 | "command": "node ./node_modules/karma/bin/karma start config/karma.config", 68 | "env": { 69 | "NODE_ENV": "test", 70 | "DEBUG": "app:*" 71 | } 72 | } 73 | }, 74 | "repository": { 75 | "type": "git", 76 | "url": "git+https://github.com/davezuko/react-redux-starter-kit.git" 77 | }, 78 | "author": "David Zukowski (http://zuko.me)", 79 | "license": "MIT", 80 | "dependencies": { 81 | "axios": "^0.15.3", 82 | "babel-core": "^6.17.0", 83 | "babel-loader": "^6.2.5", 84 | "babel-plugin-transform-runtime": "^6.15.0", 85 | "babel-preset-es2015": "^6.14.0", 86 | "babel-preset-react": "^6.11.1", 87 | "babel-preset-stage-0": "^6.3.13", 88 | "babel-runtime": "^6.11.6", 89 | "bcrypt-nodejs": "0.0.3", 90 | "bcryptjs": "^2.4.3", 91 | "better-npm-run": "0.0.13", 92 | "bluebird": "^3.4.7", 93 | "body-parser": "^1.16.0", 94 | "compression": "^1.6.2", 95 | "connect-history-api-fallback": "^1.3.0", 96 | "cookie-parser": "^1.4.3", 97 | "cookie-session": "^2.0.0-beta.3", 98 | "css-loader": "^0.26.0", 99 | "cssnano": "^3.7.4", 100 | "debug": "^2.2.0", 101 | "dockerode": "^2.3.1", 102 | "dotenv": "^4.0.0", 103 | "express-session": "^1.15.0", 104 | "extract-text-webpack-plugin": "^1.0.0", 105 | "file-loader": "^0.9.0", 106 | "fs-extra": "^1.0.0", 107 | "html-webpack-plugin": "^2.22.0", 108 | "imports-loader": "^0.7.0", 109 | "ip": "^1.1.2", 110 | "json-loader": "^0.5.4", 111 | "node-sass": "^4.3.0", 112 | "normalize.css": "^5.0.0", 113 | "passport": "^0.3.2", 114 | "passport-local": "^1.0.0", 115 | "pg": "^6.1.2", 116 | "postcss-loader": "^1.1.0", 117 | "public-ip": "^2.4.0", 118 | "react": "^15.0.0", 119 | "react-bootstrap": "^0.30.7", 120 | "react-codemirror": "^0.3.0", 121 | "react-copy-to-clipboard": "^4.2.3", 122 | "react-countdown-clock": "^1.0.9", 123 | "react-dom": "^15.0.0", 124 | "react-howler": "^3.5.0", 125 | "react-modal": "^1.6.5", 126 | "react-notify-toast": "^0.1.4", 127 | "react-redux": "^5.0.1", 128 | "react-router": "^3.0.0", 129 | "react-router-bootstrap": "^0.23.1", 130 | "redux": "^3.6.0", 131 | "redux-logger": "^2.7.4", 132 | "redux-thunk": "^2.0.0", 133 | "rimraf": "^2.5.4", 134 | "sass-loader": "^4.0.0", 135 | "sequelize": "^3.30.0", 136 | "socket.io": "^1.7.2", 137 | "stream-buffers": "^3.0.1", 138 | "style-loader": "^0.13.1", 139 | "toastr": "^2.1.2", 140 | "url-loader": "^0.5.6", 141 | "webpack": "^1.12.14", 142 | "yargs": "^6.3.0" 143 | }, 144 | "devDependencies": { 145 | "babel-eslint": "^7.1.0", 146 | "babel-plugin-istanbul": "^3.0.0", 147 | "chai": "^3.4.1", 148 | "chai-as-promised": "^6.0.0", 149 | "chai-enzyme": "^0.6.1", 150 | "cheerio": "^0.22.0", 151 | "codecov": "^1.0.1", 152 | "enzyme": "^2.0.0", 153 | "eslint": "^3.0.1", 154 | "eslint-config-standard": "^6.0.0", 155 | "eslint-config-standard-react": "^4.0.0", 156 | "eslint-plugin-babel": "^4.0.0", 157 | "eslint-plugin-promise": "^3.0.0", 158 | "eslint-plugin-react": "^6.0.0", 159 | "eslint-plugin-standard": "^2.0.0", 160 | "express": "^4.14.0", 161 | "karma": "^1.0.0", 162 | "karma-coverage": "^1.0.0", 163 | "karma-mocha": "^1.0.1", 164 | "karma-mocha-reporter": "^2.0.0", 165 | "karma-phantomjs-launcher": "^1.0.2", 166 | "karma-webpack-with-fast-source-maps": "^1.9.2", 167 | "mocha": "^3.0.1", 168 | "nodemon": "^1.10.2", 169 | "phantomjs-prebuilt": "^2.1.12", 170 | "react-addons-test-utils": "^15.0.0", 171 | "redbox-react": "^1.2.10", 172 | "sinon": "^1.17.5", 173 | "sinon-chai": "^2.8.0", 174 | "volleyball": "^1.4.1", 175 | "webpack-dev-middleware": "^1.6.1", 176 | "webpack-hot-middleware": "^2.12.2" 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /public/CodeBattle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/CodeBattle.png -------------------------------------------------------------------------------- /public/Ming.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/Ming.jpg -------------------------------------------------------------------------------- /public/MitchHeadShot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/MitchHeadShot.jpg -------------------------------------------------------------------------------- /public/Patryk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/Patryk.jpg -------------------------------------------------------------------------------- /public/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/background.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/favicon.ico -------------------------------------------------------------------------------- /public/headshot_johnyom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/headshot_johnyom.jpg -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # Check it out: http://humanstxt.org/ 2 | 3 | # TEAM 4 | 5 | -- -- 6 | 7 | # THANKS 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/quickPlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/public/quickPlay.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /server/dockerTest.js: -------------------------------------------------------------------------------- 1 | const DockerRunner = require('./runDocker'); 2 | const docker = new DockerRunner(); 3 | 4 | var testCode = `function combine(arrToCombine){ 5 | 6 | var combinedArr = [1]; 7 | for(i = 0; i < arrToCombine.length -1; i++){ 8 | combinedArr.push(arrToCombine[i] + arrToCombine[i+1]); 9 | } 10 | 11 | combinedArr.push(1); 12 | return combinedArr; 13 | } 14 | 15 | function pascalsTriangle(n) { 16 | 17 | var arr = [[1]]; 18 | if(n >= 2) arr.push([1,1]); 19 | for(var i = 2; i < n; i++){ 20 | arr.push(combine(arr[i -1])) 21 | } 22 | return [].concat.apply([], arr); 23 | } 24 | 25 | pascalsTriangle(4)`; 26 | 27 | 28 | console.log("\""); 29 | 30 | docker.runCommand(testCode) 31 | .then((results) => console.log('this is the output:', results)) 32 | -------------------------------------------------------------------------------- /server/main.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const debug = require('debug')('app:server') 3 | const path = require('path') 4 | const webpack = require('webpack') 5 | const webpackConfig = require('../config/webpack.config') 6 | const project = require('../config/project.config') 7 | const compress = require('compression') 8 | const bodyParser = require('body-parser'); 9 | const publicIp = require('public-ip'); 10 | 11 | const history = require('connect-history-api-fallback'); 12 | 13 | const passport = require('passport'); 14 | const LocalStrategy = require('passport-local'); 15 | const bcrypt = require('bcryptjs'); 16 | 17 | const app = express(); 18 | 19 | const { User, Bug } = require('../db/database'); 20 | 21 | app.enable('trust proxy'); 22 | 23 | // Apply gzip compression 24 | app.use(compress()) 25 | 26 | // Use BodyParser 27 | app.use(bodyParser.urlencoded({extended:true})); 28 | app.use(bodyParser.json()); 29 | 30 | app.use(require('cookie-parser')()); 31 | app.use(require('cookie-session')({ secret: 'battlecode', resave: false, saveUninitialized: false })); 32 | 33 | 34 | passport.use(new LocalStrategy(function(username, pass, cb){ 35 | var hashedPass = bcrypt.hashSync(pass, 10); 36 | User.findOne({ where: { username: username } }) 37 | .then(function(user, err){ 38 | if (err) return cb(err); 39 | if (!user) return cb(null, false); 40 | if (!bcrypt.compareSync(pass, user.password)) return cb(null, false); 41 | 42 | return cb(null, user); 43 | }); 44 | })) 45 | 46 | app.use(passport.initialize()); 47 | app.use(passport.session()); 48 | 49 | passport.serializeUser(function(user, cb) { 50 | cb(null, user.id); 51 | }); 52 | 53 | passport.deserializeUser(function(id, cb) { 54 | User.findById(id).then(function (user) { 55 | cb(null, user); 56 | }); 57 | }); 58 | 59 | app.post("/signin", passport.authenticate('local'), (req, res) => res.send(req.user)); 60 | 61 | app.post("/signup", function(req, res, next){ 62 | User 63 | .findOne({ where: { username: req.body.signupUsername } }) 64 | .then(function(user){ 65 | if(!user){ 66 | User.create({ 67 | username: req.body.signupUsername, 68 | password: bcrypt.hashSync(req.body.signupPassword, 10), 69 | name: req.body.name, 70 | email: req.body.email 71 | }) 72 | .then(() => res.sendStatus(200)) 73 | .catch(error => res.send(error.message.split(': ')[1])); 74 | } else res.send("user exists"); 75 | }) 76 | }) 77 | 78 | app.post("/report-a-bug", function(req, res, next){ 79 | Bug.create({ 80 | bugName: req.body.bugName, 81 | bugDescription: req.body.bugDescription, 82 | name: req.body.name 83 | }) 84 | }) 85 | 86 | app.get('/signout', (req, res, next) => { 87 | req.logout(); 88 | res.sendStatus(200); 89 | }) 90 | 91 | app.get('/user', (req, res, next) => { 92 | res.send(req.user) 93 | }); 94 | app.get('/failure', (req, res, next) => res.send(null)); 95 | 96 | app.use('/api', require('./routes/')); 97 | app.use('/join', require('./routes/join.js')); 98 | 99 | 100 | app.use(history()); 101 | // ------------------------------------ 102 | // Apply Webpack HMR Middleware 103 | // ------------------------------------ 104 | if (project.env === 'development') { 105 | const compiler = webpack(webpackConfig) 106 | 107 | debug('Enabling webpack dev and HMR middleware') 108 | app.use(require('webpack-dev-middleware')(compiler, { 109 | publicPath : webpackConfig.output.publicPath, 110 | contentBase : project.paths.client(), 111 | hot : true, 112 | quiet : project.compiler_quiet, 113 | noInfo : project.compiler_quiet, 114 | lazy : false, 115 | stats : project.compiler_stats 116 | })) 117 | app.use(require('webpack-hot-middleware')(compiler, { 118 | path: '/__webpack_hmr' 119 | })) 120 | 121 | // Serve static assets from ~/public since Webpack is unaware of 122 | // these files. This middleware doesn't need to be enabled outside 123 | // of development since this directory will be copied into ~/dist 124 | // when the application is compiled. 125 | app.use(express.static(project.paths.public())) 126 | 127 | // This rewrites all routes requests to the root /index.html file 128 | // (ignoring file requests). If you want to implement universal 129 | // rendering, you'll want to remove this middleware. 130 | // app.use('*', function (req, res, next) { 131 | // const filename = path.join(compiler.outputPath, 'index.html') 132 | // compiler.outputFileSystem.readFile(filename, (err, result) => { 133 | // if (err) { 134 | // return next(err) 135 | // } 136 | // res.set('content-type', 'text/html') 137 | // res.send(result) 138 | // res.end() 139 | // }) 140 | // }) 141 | // } else { 142 | // debug( 143 | // 'Server is being run outside of live development mode, meaning it will ' + 144 | // 'only serve the compiled application bundle in ~/dist. Generally you ' + 145 | // 'do not need an application server for this and can instead use a web ' + 146 | // 'server such as nginx to serve your static files. See the "deployment" ' + 147 | // 'section in the README for more information on deployment strategies.' 148 | // ) 149 | 150 | // Serving ~/dist by default. Ideally these files should be served by 151 | // the web server and not the app server, but this helps to demo the 152 | // server in production. 153 | app.use(express.static(project.paths.dist())) 154 | } else { 155 | app.use(express.static('dist')); 156 | app.use(express.static('public')); 157 | } 158 | 159 | const io = require('../bin/io.js')(app.listen(process.env.PORT || 3000, console.log("I'm listening on port 3000"))); 160 | 161 | app.get('*', (req, res, next) => res.sendFile(path.resolve(__dirname + '/../dist/index.html'))); 162 | 163 | module.exports = app 164 | -------------------------------------------------------------------------------- /server/routes/clients.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const io = require('../../bin/io'); 3 | 4 | 5 | router.get('/all', (req, res, next) => { 6 | const ioObj = io(); 7 | const mainLobby = ioObj.sockets.adapter.rooms["MainLobby"]; 8 | 9 | //console.log(mainLobby.sockets); 10 | let clients = []; 11 | if(mainLobby) { 12 | clients = Object.keys(mainLobby.sockets).map(id => ioObj.sockets.connected[id].user) 13 | } 14 | 15 | res.send(clients); 16 | }) 17 | 18 | router.get('/room/:roomid', (req, res, next) => { 19 | const ioObj = io(); 20 | const roomId = req.params.roomid; 21 | const clients = Object.keys(ioObj.sockets.adapter.rooms[roomId].sockets); 22 | res.send(clients); 23 | }) 24 | 25 | module.exports = router; 26 | -------------------------------------------------------------------------------- /server/routes/code.js: -------------------------------------------------------------------------------- 1 | const createDocker = require('../runDocker'); 2 | const router = require('express').Router(); 3 | const Sequelize = require('sequelize'); 4 | const Question = require('../../db/database').Question; 5 | const io = require('../../bin/io'); 6 | const axios = require('axios'); 7 | 8 | router.post('/', (req, res, next) => { 9 | const ioObj = io(); 10 | const docker = new createDocker(); 11 | const codeToRun = `${req.body.code}`; 12 | 13 | //socket room that is passed in from the post request 14 | const room = req.body.room; 15 | //the socket ID from the user that posted the code to test 16 | const socketID = req.body.socketID; 17 | //an array of IDs from all the sockets in the room 18 | const arrOfSocketIDs = Object.keys(ioObj.sockets.adapter.rooms[room].sockets); 19 | //ID of the question the user is trying to test 20 | const currentQuestionID = req.body.currentQuestionID; 21 | 22 | //In the Question table find the row that matches the current questions ID 23 | Question.findById(currentQuestionID) 24 | .then((question) => { 25 | //grab the tests associated with the selected question 26 | const tests = question.tests 27 | //create a new docker container and run the users code through the tests 28 | axios.post("http://code.myrandomcode.com:3000/", { code: codeToRun, tests }) 29 | .then(res => res.data) 30 | .then(results => { 31 | //turns the results into a usable string 32 | let resultString = JSON.stringify(results); 33 | //since player 1 is associated with the socket that is at index 0 and player 2 at index 1, 34 | //we know which socket should update their current question 35 | let playerToUpdate = arrOfSocketIDs.indexOf(socketID); 36 | 37 | //if the result string does not contain the words failing (they failed at least 1 test) or SyntaxError (their code throws a syntax error) 38 | if(resultString.indexOf('failing') === -1 && resultString.indexOf('SyntaxError') === -1 && resultString.indexOf('FATAL ERROR') === -1 && resultString.toLowerCase().indexOf('syntax') === -1){ 39 | //emit to the room the player that needs to be updated along with the player's socket ID 40 | ioObj.in(room).emit('updatePlayerScore', {playerToUpdate: `Player${playerToUpdate+1}`, currentPlayer: socketID, roomID: room, code: codeToRun}); 41 | //send the result string back to the frontend 42 | res.send(resultString); 43 | } 44 | else{ 45 | //if the code fails a test or any other way the room will emit that the specific player failed and the reason why 46 | ioObj.in(room).emit('failedSub', {playerToUpdate: `Player${playerToUpdate+1}`, reason: resultString}); 47 | res.send(resultString); 48 | } 49 | }) 50 | }).catch(error => res.send(error.message)); 51 | }) 52 | 53 | module.exports = router; 54 | 55 | -------------------------------------------------------------------------------- /server/routes/fights.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {Fight} = require('../../db/database.js'); 4 | 5 | module.exports = require('express').Router() 6 | .get('/', (req, res, next) => { 7 | Fight.findAll() 8 | .then(fights => res.json(fights)) 9 | .catch(next) 10 | }) -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | 3 | router.use('/code', require('./code')); 4 | router.use('/users', require('./users')); 5 | router.use('/questions', require('./questions')); 6 | router.use('/fights', require('./fights')); 7 | router.use('/join', require('./join')); 8 | router.use('/clients', require('./clients')); 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/routes/join.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const io = require('../../bin/io'); 3 | const socketCallbacks = require('../sockets/socketCallbacks'); 4 | 5 | function makeid() 6 | { 7 | var text = ""; 8 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 9 | for( var i=0; i < 6; i++ ) 10 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 11 | 12 | return text; 13 | } 14 | 15 | 16 | router.get('/:invCode', (req, res, next) =>{ 17 | 18 | const ioObj = io(); 19 | //console.log("this is the ioObj.id", ioObj.id); 20 | const gameRoomId = req.params.invCode; 21 | console.log("new game room", ioObj.sockets.adapter.rooms[gameRoomId].sockets); 22 | console.log(req.params.invCode); 23 | res.send(200); 24 | 25 | }) 26 | console.log(makeid()); 27 | module.exports = router; 28 | -------------------------------------------------------------------------------- /server/routes/questions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {Question} = require('../../db/database.js'); 4 | 5 | module.exports = require('express').Router() 6 | .get('/', (req, res, next) => { 7 | Question.findAll() 8 | .then(questions => res.json(questions)) 9 | .catch(next) 10 | // res.json('hello')); 11 | }) -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const db = require('../../db/database'); 4 | const User = db.User; 5 | const Fight = db.Fight; 6 | 7 | module.exports = require('express').Router() 8 | .get('/leaderboard', (req, res, next) => { 9 | User.findAll({ 10 | limit: 10, 11 | order: [['points', 'DESC']] 12 | }) 13 | .then(users => res.json(users)) 14 | .catch(next) 15 | }) 16 | .get('/allMatches', (req, res, next) => { 17 | Fight.findAll({ 18 | limit:10, 19 | order: [['createdAt', 'DESC']], 20 | include: [{ all: true }] 21 | }) 22 | .then(matches => res.json(matches)) 23 | .catch(next) 24 | }) 25 | .get('/matches/:userId', (req, res, next) => { 26 | const userId = +req.params.userId; 27 | Fight.findAll({ 28 | where: {$or: [{winnerId: userId}, {loserId: userId}]}, 29 | limit: 20, 30 | order: [['createdAt', 'DESC']], 31 | include: [{ all: true }] 32 | }) 33 | .then(matches => res.send(matches)) 34 | .catch(next) 35 | }) 36 | .get('/user/:username', (req, res, next) => { 37 | User.findOne({where: {username: req.params.username}}) 38 | .then(user => res.send(user)) 39 | .catch(next) 40 | }) 41 | .get('/', (req, res, next) => { 42 | User.findAll() 43 | .then(users => res.json(users)) 44 | .catch(next) 45 | }) 46 | -------------------------------------------------------------------------------- /server/runDocker.js: -------------------------------------------------------------------------------- 1 | const Docker = require('dockerode'); 2 | 3 | const streamBuffers = require('stream-buffers'); 4 | const Bluebird = require('bluebird'); 5 | 6 | function createDocker () { 7 | this.docker = new Docker(); 8 | } 9 | 10 | 11 | 12 | createDocker.prototype.runCommand = function (userCode, testCode, scenario) { 13 | 14 | let userCodeEdited = userCode.replace(/\"\'\"/gm, '\"\\\'\"').replace(/\'\"\'/gm, '\"\\\\\"\"').replace(/([^\\])'/gm, (match, p1) => p1 + '"'); 15 | 16 | // 17 | 18 | 19 | //const userCodeEdited = userCode; 20 | //const runUserCodeCommand = `node -p '${userCode}' && exit`; 21 | 22 | userCodeEdited = ' \"use strict\"; var assert = require("assert");' + userCodeEdited + testCode; 23 | 24 | const runUserCodeCommand = `touch test.js && echo $'${userCodeEdited}' > test.js && babel test.js --out-file test-compiled.js && mocha -R spec test-compiled.js && exit`; 25 | const stdoutStream = new streamBuffers.WritableStreamBuffer(); 26 | console.log(runUserCodeCommand); 27 | const finishedPromise = new Bluebird((resolve, reject) => { 28 | 29 | this.docker.run('mocha-chai-node', ['bash', '-c', runUserCodeCommand], stdoutStream, function(err, data, container) { 30 | if (err) return reject(err); 31 | 32 | const results = stdoutStream.getContentsAsString('utf8'); 33 | container.remove(function (error, response) { 34 | if (error) console.log(error); 35 | }); 36 | 37 | resolve(results.replace(/\[\d+m/gi, "")); 38 | }); 39 | }); 40 | 41 | return finishedPromise; 42 | 43 | }; 44 | 45 | module.exports = createDocker; 46 | -------------------------------------------------------------------------------- /server/sockets/socketCallbacks.js: -------------------------------------------------------------------------------- 1 | const hello = function (data) { 2 | console.log(data); 3 | } 4 | 5 | const onUserCodeSubmit = function(data) { 6 | return data; 7 | } 8 | 9 | const joinGameLobby = function(data){ 10 | this.join(data); 11 | } 12 | 13 | const reloadLobby = function(io) { 14 | io.emit('reload'); 15 | } 16 | 17 | const updatePlayerProgress = function(data){ 18 | // console.log('receiving correct response on back-end') 19 | data.playerProgress[data.playerNumber - 1]++; 20 | // console.log(data.playerProgress); 21 | this.emit('update progress', data.playerProgress); 22 | } 23 | 24 | module.exports = {hello, onUserCodeSubmit, joinGameLobby, updatePlayerProgress, reloadLobby} 25 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/.DS_Store -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import axios from 'axios'; 3 | import React from 'react'; 4 | import { DropdownButton, MenuItem, Navbar, Nav, NavItem } from 'react-bootstrap'; 5 | import { IndexLink, Link, browserHistory } from 'react-router'; 6 | import { connect } from 'react-redux'; 7 | import { LinkContainer } from 'react-router-bootstrap'; 8 | import Login from '../Login/Login'; 9 | import { fetchMatches } from '../../store/match' 10 | import { setUser } from '../../store/user'; 11 | import { setRoomId } from '../../store/gamelobby'; 12 | import './Header.scss'; 13 | import ReactModal from 'react-modal'; 14 | class Header extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | showModal: false 19 | }; 20 | this.handleOpenModal = this.handleOpenModal.bind(this); 21 | this.handleCloseModal = this.handleCloseModal.bind(this); 22 | this.logout = this.logout.bind(this); 23 | } 24 | 25 | componentDidMount(){ 26 | socket.on('gameReady', (data) => { 27 | this.props.setId(data); 28 | }) 29 | socket.on('startGame', data => { 30 | this.setState({ showModal: false }); 31 | browserHistory.push('/battlePage'); 32 | }) 33 | } 34 | 35 | handleOpenModal () { 36 | this.setState({ showModal: true }); 37 | socket.emit('quickPlay'); 38 | } 39 | 40 | handleCloseModal () { 41 | this.setState({ showModal: false }); 42 | } 43 | 44 | logout() { 45 | this.props.logoutUser(); 46 | } 47 | 48 | render() { 49 | const modalStyles = { 50 | overlay: { 51 | position: 'fixed', 52 | height: '475px', 53 | top: '50%', 54 | left: '50%', 55 | right: 'auto', 56 | bottom: 'auto', 57 | marginRight: '-50%', 58 | transform: 'translate(-50%, -50%)', 59 | zIndex: '5', 60 | width: '500px', 61 | backgroundColor: 'rgba(255, 255, 255, 0.75)', 62 | }, 63 | content: { 64 | color: 'white', 65 | border: '1px solid #ccc', 66 | background: '#000', 67 | overflow: 'auto', 68 | WebkitOverflowScrolling: 'touch', 69 | borderRadius: '4px', 70 | outline: 'none', 71 | top: '0px', 72 | bottom: '0px', 73 | left: '0px', 74 | right: '0px', 75 | padding: '0px', 76 | textAlign: 'center' 77 | } 78 | } 79 | const { user } = this.props; 80 | return ( 81 |
82 | 83 | 84 | 85 | Home 86 | 87 | 88 | 89 | 90 | 111 | { 112 | Object.keys(user).length 113 | ? 114 | ( 115 |
116 | 117 | browserHistory.push(`/profile/${user.username}`)}>Profile 118 | Settings 119 | Logout 120 | 121 |
122 | ) 123 | :
124 | } 125 |
126 |
127 | 133 |

Searching For Game...

134 | Ninja 139 | 142 |
143 |
144 | ); 145 | } 146 | } 147 | 148 | function makeid() { 149 | let text = ""; 150 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 151 | for(let i = 0; i < 6; i++ ) 152 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 153 | return text; 154 | } 155 | 156 | const mapStateToProps = (state) => { 157 | return {user: state.user.user}; 158 | } 159 | 160 | const mapDispatchToProps = (dispatch) => { 161 | return { 162 | logoutUser: function() { 163 | axios.get('/signout').then(() => { 164 | dispatch(setUser({})); 165 | dispatch(fetchMatches(null)); 166 | }) 167 | }, 168 | setId: function(id) { 169 | dispatch(setRoomId(id)); 170 | socket.emit('joinGameLobby', id); 171 | } 172 | } 173 | } 174 | 175 | export default connect(mapStateToProps, mapDispatchToProps)(Header); 176 | -------------------------------------------------------------------------------- /src/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | .navbar.navbar-inverse { 2 | margin-bottom: 2px; 3 | } 4 | 5 | .navbar-header { 6 | padding-left: 10px; 7 | } 8 | .AccountDropdown { 9 | padding: 10px; 10 | padding-right: 0px; 11 | form { 12 | button { 13 | padding-top: 3px; 14 | } 15 | } 16 | } 17 | 18 | ul.nav.navbar-nav { 19 | a { 20 | color: #777; 21 | background-color: #222; 22 | 23 | } 24 | } 25 | 26 | .navbar-brand { 27 | color: #777; 28 | } 29 | 30 | #quick-play-modal { 31 | // height: 475px !important; 32 | } 33 | 34 | // #quick-play { 35 | // background-color: rgb(34, 34, 34); 36 | // border: 0; 37 | // border-color: rgb(34, 34, 34); 38 | // } 39 | 40 | #create-lobby { 41 | background-color: dimgray; 42 | border-color: transparent; 43 | // margin-right: 5px; 44 | } 45 | 46 | #quick-play { 47 | background-color: white; 48 | border-color: transparent; 49 | margin-right: 5px; 50 | color: black; 51 | } 52 | 53 | .ninja-modal { 54 | display: block; 55 | max-width: 100%; 56 | margin: 0 auto; 57 | } 58 | 59 | 60 | .btn.btn-primary.btn-modal { 61 | background-color: dimgray; 62 | border-color: transparent; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header' 2 | 3 | export default Header 4 | -------------------------------------------------------------------------------- /src/components/Login/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import { setUser } from '../../store/user'; 4 | import { fetchMatches } from '../../store/match'; 5 | import { Link } from 'react-router'; 6 | import { connect } from 'react-redux'; 7 | import './Login.scss'; 8 | 9 | class Login extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | username: '', 14 | password: '' 15 | } 16 | 17 | this.onSubmitHandler = this.onSubmitHandler.bind(this); 18 | this.onChangeHandler = this.onChangeHandler.bind(this); 19 | } 20 | 21 | onSubmitHandler(event) { 22 | event.preventDefault(); 23 | axios.post('/signin', this.state) 24 | .then(user => this.props.setLoggedInUser(user.data)); 25 | } 26 | 27 | onChangeHandler(event) { 28 | this.setState({[event.target.id]: event.target.value}); 29 | } 30 | 31 | render() { 32 | return ( 33 |
34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 | ) 44 | } 45 | } 46 | 47 | const mapDispatchToProps = (dispatch) => { 48 | return { 49 | setLoggedInUser: function(user) { 50 | dispatch(setUser(user)); 51 | dispatch(fetchMatches(user)); 52 | } 53 | } 54 | } 55 | 56 | export default connect(null, mapDispatchToProps)(Login); 57 | -------------------------------------------------------------------------------- /src/components/Login/Login.scss: -------------------------------------------------------------------------------- 1 | #loginSubmit { 2 | background-color: dimgray; 3 | border-color: transparent; 4 | margin-right: 5px; 5 | } 6 | 7 | #username { 8 | height: 33px; 9 | margin-right: 5px; 10 | } 11 | 12 | #password { 13 | height: 33px; 14 | margin-right: 5px; 15 | } 16 | 17 | #signUp { 18 | background-color: white; 19 | border-color: transparent; 20 | margin-right: 5px; 21 | color: black; 22 | } 23 | -------------------------------------------------------------------------------- /src/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { browserHistory, Router, Route, IndexRoute } from 'react-router' 3 | import { Provider, connect } from 'react-redux'; 4 | import CoreLayout from '../layouts/CoreLayout'; 5 | import Home from '../routes/Home'; 6 | import CodeEditor from '../routes/CodeEditor'; 7 | import BattlePage from '../routes/BattlePage/containers/BattlePageContainer.jsx' 8 | import { fetchClients } from '../store/client'; 9 | 10 | 11 | class AppContainer extends Component { 12 | constructor(props){ 13 | super(props); 14 | } 15 | shouldComponentUpdate() { 16 | return false; 17 | } 18 | render () { 19 | const { store, routes } = this.props; 20 | 21 | socket.on('reload', () => { 22 | store.dispatch(fetchClients()) 23 | }); 24 | 25 | 26 | 27 | return ( 28 | 29 |
30 | 31 |
32 |
33 | ); 34 | } 35 | } 36 | 37 | export default AppContainer; 38 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Code Battle 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/CoreLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from '../../components/Header' 3 | import './CoreLayout.scss' 4 | 5 | export const CoreLayout = ({ children }) => ( 6 |
7 |
8 |
9 |
10 |
11 | {children} 12 |
13 |
14 | ) 15 | 16 | CoreLayout.propTypes = { 17 | children : React.PropTypes.element.isRequired 18 | } 19 | 20 | 21 | export default CoreLayout; 22 | 23 | //
24 | //
25 | //
26 | //
27 | //
28 | // {children} 29 | //
30 | //
31 | //
32 | //
33 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/CoreLayout.scss: -------------------------------------------------------------------------------- 1 | .corelay { 2 | padding: 0px; 3 | background-color: #303133; 4 | color: #111; 5 | text-align: center; 6 | height: 100%; 7 | min-height: 100vh; 8 | width: 100%; 9 | padding-bottom: 50px; 10 | } 11 | 12 | #children { 13 | width: 100%; 14 | padding-left: 0px; 15 | padding-right: 0px; 16 | } 17 | -------------------------------------------------------------------------------- /src/layouts/CoreLayout/index.js: -------------------------------------------------------------------------------- 1 | import CoreLayout from './CoreLayout' 2 | 3 | export default CoreLayout 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import createStore from './store/createStore' 4 | import AppContainer from './containers/AppContainer' 5 | 6 | // ======================================================== 7 | // Store Instantiation 8 | // ======================================================== 9 | const initialState = window.___INITIAL_STATE__ 10 | const store = createStore(initialState) 11 | 12 | // ======================================================== 13 | // Render Setup 14 | // ======================================================== 15 | const MOUNT_NODE = document.getElementById('root') 16 | 17 | let render = () => { 18 | const routes = require('./routes').default(store) 19 | 20 | ReactDOM.render( 21 | , 22 | MOUNT_NODE 23 | ) 24 | } 25 | 26 | // This code is excluded from production bundle 27 | if (__DEV__) { 28 | if (module.hot) { 29 | // Development render functions 30 | const renderApp = render 31 | const renderError = (error) => { 32 | const RedBox = require('redbox-react').default 33 | 34 | ReactDOM.render(, MOUNT_NODE) 35 | } 36 | 37 | // Wrap render in try/catch 38 | render = () => { 39 | try { 40 | renderApp() 41 | } catch (error) { 42 | console.error(error) 43 | renderError(error) 44 | } 45 | } 46 | 47 | // Setup hot module replacement 48 | module.hot.accept('./routes/index', () => 49 | setImmediate(() => { 50 | ReactDOM.unmountComponentAtNode(MOUNT_NODE) 51 | render() 52 | }) 53 | ) 54 | } 55 | } 56 | 57 | // ======================================================== 58 | // Go! 59 | // ======================================================== 60 | render() 61 | -------------------------------------------------------------------------------- /src/routes/About/About.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './About.scss'; 3 | 4 | const About = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 | Mitch Image 11 |
12 |
13 |

Mitch Kosowski

14 |
15 |
16 |
17 |
18 | Patryk Image 19 |
20 |
21 |

Patryk Chmura

22 |
23 |
24 |
25 |
26 | Generic placeholder thumbnail 27 |
28 |
29 |

Ming Tung Wong

30 |
31 |
32 |
33 |
34 | Generic placeholder thumbnail 35 |
36 |
37 |

John Yom

38 |
39 |
40 |
41 |
42 | ) 43 | } 44 | 45 | export default About; 46 | 47 | 48 | // return ( 49 | //
50 | //
51 | //
52 | // 53 | // john 54 | // 55 | //
56 | //
57 | // Hello, Info About Mitch 58 | //
59 | //
60 | 61 | //
62 | //
63 | // 64 | // john 65 | // 66 | //
67 | //
68 | // Hello, Info About Patryk 69 | //
70 | //
71 | 72 | //
73 | //
74 | // 75 | // john 76 | // 77 | //
78 | //
79 | // Hello, Info About Ming 80 | //
81 | //
82 | 83 | //
84 | //
85 | // 86 | // john 87 | // 88 | //
89 | //
90 | // Hello, Info About John 91 | //
92 | //
93 | 94 | //
95 | // ); 96 | -------------------------------------------------------------------------------- /src/routes/About/About.scss: -------------------------------------------------------------------------------- 1 | #aboutContainer { 2 | height: 100%; 3 | margin-top: 20px; 4 | margin-bottom: 10px; 5 | padding-top: 30px; 6 | padding-bottom: 15px; 7 | padding-right: 15px; 8 | padding-left: 15px; 9 | margin-left: auto; 10 | margin-right: auto; 11 | background-color: rgba(0,0,0,.2); 12 | } 13 | 14 | #aboutRow { 15 | height: 100%; 16 | } 17 | 18 | .thumbnail { 19 | height: 100%; 20 | width: 100%; 21 | background-color: #303133; 22 | overflow-y: hidden; 23 | } 24 | 25 | .caption { 26 | text-align: center; 27 | font-size: 18px; 28 | color: white; 29 | } 30 | -------------------------------------------------------------------------------- /src/routes/BattlePage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/BattlePage/.DS_Store -------------------------------------------------------------------------------- /src/routes/BattlePage/assets/jurass01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/BattlePage/assets/jurass01.mp3 -------------------------------------------------------------------------------- /src/routes/BattlePage/components/CountdownClock.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactCountdownClock from 'react-countdown-clock' 3 | 4 | export const CountdownClock = () => ( 5 |
6 | 11 |
12 | ) 13 | 14 | export default CountdownClock; 15 | 16 | // set callback function to clock via this option 17 | // onComplete={myCallback} -------------------------------------------------------------------------------- /src/routes/BattlePage/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Modal from 'react-modal'; 4 | import ReactHowler from 'react-howler'; 5 | 6 | const customStyles = { 7 | overlay: { 8 | zIndex: '6' 9 | }, 10 | content : { 11 | top : '50%', 12 | left : '50%', 13 | right : 'auto', 14 | bottom : 'auto', 15 | marginRight : '-50%', 16 | transform : 'translate(-50%, -50%)' 17 | } 18 | }; 19 | 20 | 21 | class ExampleModal extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | // console.log(props); 25 | this.state = { 26 | modalIsOpen: this.props.modalIsOpen 27 | }; 28 | } 29 | 30 | render() { 31 | return ( 32 |
33 | 39 | Ah Ah Ah! You didnt say the magic word 43 | 44 | 49 | 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default ExampleModal; -------------------------------------------------------------------------------- /src/routes/BattlePage/components/Problem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Problem.scss'; 3 | 4 | 5 | 6 | export const Problem = (props) => { 7 | 8 | // get the current question that was passed in through props 9 | const currentQuestion = props.CurrentQuestion; 10 | 11 | //if the current question is defined 12 | if (currentQuestion){ 13 | //split the qurstion text by the next line tags 14 | var splitQuestion = currentQuestion.questionText.split("\n") 15 | //map over each "line" of text and wrap them in

tags 16 | var pTags = splitQuestion.map((questionLine, i) => ( 17 |

{questionLine}

18 | )) 19 | } 20 | return ( 21 |
22 |
23 |
24 |
25 |
26 | Questions 27 |
28 |
29 |
30 |

{currentQuestion && currentQuestion.questionName}

31 |
{currentQuestion && pTags}
32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | 39 | export default Problem; 40 | -------------------------------------------------------------------------------- /src/routes/BattlePage/components/Problem.scss: -------------------------------------------------------------------------------- 1 | #problemsContainer { 2 | margin-left: auto; 3 | margin-right: auto; 4 | max-width: 100%; 5 | padding: 1em; 6 | height: 100%; 7 | } 8 | 9 | p { 10 | text-align: left; 11 | color: #777; 12 | } 13 | 14 | #problems { 15 | background-color: transparent; 16 | color: #777; 17 | } 18 | 19 | #problemsRow { 20 | background-color: rgba(0,0,0,0.2);; 21 | height: 100%; 22 | min-height: 700px; 23 | // padding-left: 10px; 24 | width: 100%; 25 | } 26 | 27 | h4 { 28 | padding-top: 10px; 29 | color: #777; 30 | } 31 | 32 | #questionsBox { 33 | background-color: rgba(0,0,0,0.2); 34 | padding-left: 10px; 35 | text-align: left; 36 | width: 100%; 37 | padding-right: 0; 38 | height: 100%; 39 | } 40 | 41 | #questionsArray { 42 | padding-left: 10px; 43 | } 44 | // background-color: rgba(0,0,0,0.2); 45 | // color: #777; 46 | // height: 100%; 47 | // min-height: 300px; 48 | // padding-bottom: 15px; 49 | // margin-left: 10px; 50 | // padding-left: 10px; 51 | // padding-right: 10px; 52 | // padding-top: 15px; 53 | -------------------------------------------------------------------------------- /src/routes/BattlePage/components/ProgressBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './ProgressBar.scss'; 3 | 4 | const ProgressBar = (props) => { 5 | const { Progress } = props.Progress; 6 | const { QuestionsArr } = props.QuestionsArr; 7 | let progressPercentage = (props.Progress/props.QuestionsArr.length) * 100; 8 | return ( 9 |
10 |
11 |
19 |
20 |
21 | ); 22 | } 23 | 24 | export default ProgressBar; 25 | -------------------------------------------------------------------------------- /src/routes/BattlePage/components/ProgressBar.scss: -------------------------------------------------------------------------------- 1 | .progress { 2 | background: rgba(242, 228, 228, 1); 3 | background: -webkit-linear-gradient(top, rgba(138, 124, 124, 1) 0%, rgba(242, 228, 228, 1) 100%); 4 | background: linear-gradient(to bottom, rgba(138, 124, 124, 1) 0%, rgba(242, 228, 228, 1) 100%); 5 | border: 0px solid rgba(122, 120, 120, 1); 6 | border-radius: 8px; 7 | height: 19px; 8 | } 9 | 10 | .progress-bar-custom { 11 | background: rgba(158, 162, 166, 1); 12 | background: -webkit-linear-gradient(top, rgba(29, 30, 31, 1) 0%, rgba(158, 162, 166, 1) 100%); 13 | background: linear-gradient(to bottom, rgba(29, 30, 31, 1) 0%, rgba(158, 162, 166, 1) 100%); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/routes/BattlePage/containers/BattlePageContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { browserHistory } from 'react-router'; 4 | import './BattlePageContainer.scss'; 5 | import Problem from '../components/Problem' 6 | import CodeEditor from '../../CodeEditor/components/CodeEditor' 7 | import ProgressBar from '../components/ProgressBar.js' 8 | import axios from 'axios'; 9 | import Notifications, {notify} from 'react-notify-toast'; 10 | import ExampleModal from '../components/Modal'; 11 | import GameWonPage from '../../GameWonPage/components/GameWonPage'; 12 | import { setOpponentAnswers, setOwnAnswers } from '../../../store/matchresult'; 13 | 14 | class BattlePage extends Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | //player object holds the socket id and the number of questions answered correctly 19 | player1: { 20 | 21 | id:'Player One', 22 | progress: 0, 23 | username: 'Player One', 24 | userID: '', 25 | powerUpNum: 0, 26 | code: [] 27 | }, 28 | player2: { 29 | id:'Player Two', 30 | progress: 0, 31 | username: 'Player Two', 32 | userID: '', 33 | powerUpNum: 0, 34 | code: [] 35 | 36 | }, 37 | //holds the question objects 38 | questionsArr: [], 39 | //the index of the questionArr 40 | currentQuestion: 0, 41 | timeElapsed: 0, 42 | startingTime: null, 43 | numberOfQuestions: 2, 44 | roomID: this.props.roomID.id, 45 | gameWon: false, 46 | modalIsOpen: false 47 | } 48 | this.sendAttack = this.sendAttack.bind(this); 49 | } 50 | 51 | sendAttack() { 52 | if (socket.id === this.state.player1.id && this.state.player1.powerUpNum > 0) { 53 | this.setState({ 54 | player1: { 55 | id: this.state.player1.id, 56 | progress: this.state.player1.progress, 57 | username: this.state.player1.username, 58 | userID: this.state.player1.userID, 59 | powerUpNum: (this.state.player1.powerUpNum - 1), 60 | code: this.state.player1.code 61 | } 62 | }); 63 | socket.emit('sending attack', this.props.roomID.id); 64 | } 65 | if (socket.id === this.state.player2.id && this.state.player2.powerUpNum > 0) { 66 | this.setState({ 67 | player2: { 68 | id: this.state.player2.id, 69 | progress: this.state.player2.progress, 70 | username: this.state.player2.username, 71 | userID: this.state.player2.userID, 72 | powerUpNum: (this.state.player2.powerUpNum - 1), 73 | code: this.state.player2.code 74 | } 75 | }); 76 | socket.emit('sending attack', this.props.roomID.id); 77 | } 78 | } 79 | 80 | // send powerUpNum every time you set state 81 | 82 | componentDidMount() { 83 | //set the player ids to their socket ids 84 | //get the questions getting sent from the backend and store them in questionsArr 85 | const notifyTimer = 5000; 86 | const startingTime = Date.now(); 87 | let p1username = 'Player One'; 88 | let p2username = 'Player Two'; 89 | socket.on('sending Questions', (data) => { 90 | // console.log('frontend data', data); 91 | p1username = data.player1.username; 92 | p2username = data.player2.username; 93 | this.setState({ 94 | player1: { 95 | id:data.player1.socketId, 96 | progress: 0, 97 | username: p1username, 98 | userID: data.player1.id, 99 | powerUpNum: this.state.player1.powerUpNum, 100 | code: this.state.player1.code 101 | }, 102 | player2: { 103 | id: data.player2.socketId, 104 | progress: 0, 105 | username: p2username, 106 | userID: data.player2.id, 107 | powerUpNum: this.state.player2.powerUpNum, 108 | code: this.state.player2.code 109 | }, 110 | questionsArr: data.questions, 111 | 112 | startingTime: startingTime}) 113 | }) 114 | 115 | 116 | //when either player gets a question correct 117 | socket.on('updatePlayerScore', (data) => { 118 | // console.log("socket data",data); 119 | //string will hold the player who got the question correct 120 | let playerToUpdate = data.playerToUpdate; 121 | if (playerToUpdate === 'Player1'){ 122 | // console.log("player 1 state", this.state.player1); 123 | //if the the clients socket ID matches the socket ID of player 1 124 | if (socket.id === data.currentPlayer){ 125 | //change player 1's progress and their current question 126 | console.log(data.code); 127 | let newCode = this.state.player1.code; 128 | newCode.push(data.code); 129 | this.setState( { 130 | player1: { 131 | id: this.state.player1.id, 132 | progress: (this.state.player1.progress + 1), 133 | username: p1username, 134 | userID: this.state.player1.userID, 135 | powerUpNum: this.state.player1.powerUpNum, 136 | code: newCode 137 | }, 138 | player2: { 139 | id: this.state.player2.id, 140 | progress: this.state.player2.progress, 141 | username: p2username, 142 | userID: this.state.player2.userID, 143 | powerUpNum: (this.state.player2.powerUpNum + 1), 144 | code: this.state.player2.code 145 | }, 146 | currentQuestion: (this.state.currentQuestion + 1), 147 | roomID: data.roomID}, 148 | () => { 149 | this.props.setOwnAnswers(this.state.player1.code); 150 | if (this.state.player1.progress === this.state.numberOfQuestions && this.state.gameWon === false){ 151 | notify.show('You won the game!', 'success', notifyTimer); 152 | // console.log("inside player 1 win check") 153 | // console.log(this.state); 154 | socket.emit('gameOver', { 155 | roomID: this.state.roomID, 156 | winnerID: this.state.player1.id, 157 | username: p1username, 158 | winnerUserID: this.state.player1.userID, 159 | loserUserID: this.state.player2.userID, 160 | score: [this.state.player1.progress, this.state.player2.progress], 161 | time: (Date.now() - this.state.startingTime)/1000 162 | }); 163 | 164 | setTimeout(() => { 165 | browserHistory.push('/gameWon'); 166 | }, notifyTimer); 167 | } 168 | else if (this.state.player1.progress === this.state.numberOfQuestions && this.state.gameWon === true) { 169 | notify.show('You got all the answers! But your opponent was faster. :(', 'success', notifyTimer); 170 | setTimeout(() => { 171 | browserHistory.push('/gameFinished'); 172 | }, notifyTimer); 173 | } 174 | else { 175 | notify.show('You got an answer correct!', 'success', notifyTimer); 176 | } 177 | }); 178 | 179 | // console.log('Player 1 progress updated and the question should have changed', this.state.player1.progress) 180 | } 181 | else { 182 | //change player 1's progress to update the score 183 | console.log(data.code); 184 | let newCode = this.state.player1.code; 185 | newCode.push(data.code); 186 | this.setState( { 187 | player1: { 188 | id: this.state.player1.id, 189 | progress: (this.state.player1.progress + 1), 190 | username: p1username, 191 | userID: this.state.player1.userID, 192 | powerUpNum: this.state.player1.powerUpNum, 193 | code: newCode 194 | }, 195 | roomID: data.roomID, 196 | player2: { 197 | id: this.state.player2.id, 198 | progress: this.state.player2.progress, 199 | username: p2username, 200 | userID: this.state.player2.userID, 201 | powerUpNum: (this.state.player2.powerUpNum + 1), 202 | code: this.state.player2.code 203 | } 204 | }, () => {this.props.setOpponentAnswers(this.state.player1.code)}) 205 | // console.log('Player 1 progress updated', this.state.player1.progress) 206 | 207 | if (this.state.player1.progress === this.state.numberOfQuestions){ 208 | notify.show('Player 1 answered question #' + this.state.player1.progress + ' and won! You can still keep going, though :)', 'error', notifyTimer); 209 | } else { 210 | notify.show('Player 1 submitted a correct answer to question #' + this.state.player1.progress +'!', 'warning', notifyTimer); 211 | } 212 | } 213 | } 214 | //player must be player 2 215 | else { 216 | // console.log("player 2 state",this.state.player2); 217 | //if the client is player 2 update their progress and change the score 218 | if(socket.id === data.currentPlayer){ 219 | console.log(data.code); 220 | let newCode = this.state.player2.code; 221 | newCode.push(data.code); 222 | this.setState( { 223 | player2: { 224 | id: this.state.player2.id, 225 | progress: (this.state.player2.progress + 1), 226 | username: p2username, 227 | userID: this.state.player2.userID, 228 | powerUpNum: this.state.player1.powerUpNum, 229 | code: newCode 230 | }, 231 | player1: { 232 | id: this.state.player1.id, 233 | progress: this.state.player1.progress, 234 | username: p1username, 235 | userID: this.state.player1.userID, 236 | powerUpNum: (this.state.player1.powerUpNum + 1), 237 | code: this.state.player1.code 238 | }, 239 | currentQuestion: (this.state.currentQuestion + 1), 240 | powerUpNum: this.state.player2.powerUpNum, 241 | roomID: data.roomID}, 242 | () => { 243 | this.props.setOwnAnswers(this.state.player2.code); 244 | if (this.state.player2.progress === this.state.numberOfQuestions && this.state.gameWon === false){ 245 | notify.show('You won the game!', 'success', notifyTimer); 246 | // console.log("inside player 2 win check") 247 | // console.log(this.state); 248 | socket.emit('gameOver', { 249 | roomID: this.state.roomID, 250 | winnerID: this.state.player1.id, 251 | username: p1username, 252 | winnerUserID: this.state.player2.userID, 253 | loserUserID: this.state.player1.userID, 254 | score: [this.state.player1.progress, this.state.player2.progress], 255 | time: (Date.now() - this.state.startingTime)/1000}); 256 | setTimeout(() => { 257 | browserHistory.push('/gameWon'); 258 | }, notifyTimer); 259 | } 260 | else if (this.state.player2.progress === this.state.numberOfQuestions && this.state.gameWon === true) { 261 | notify.show('You got all the answers! But your opponent was faster. :(', 'success', notifyTimer); 262 | setTimeout(() => { 263 | browserHistory.push('/gameFinished'); 264 | }, notifyTimer); 265 | } 266 | else { 267 | notify.show('You got an answer correct!', 'success', notifyTimer); 268 | } 269 | }); 270 | 271 | // console.log('Player 2 progress updated and the question should have changed', this.state.player2.progress) 272 | } 273 | else { 274 | //just change player 2's score 275 | let newCode = this.state.player2.code; 276 | newCode.push(data.code); 277 | this.setState( { 278 | player2: { 279 | id: this.state.player2.id, 280 | progress: (this.state.player2.progress + 1), 281 | username: p2username, 282 | userID: this.state.player2.userID, 283 | powerUpNum: this.state.player2.powerUpNum, 284 | code: this.state.player2.code 285 | }, 286 | roomID: data.roomID, 287 | player1: { 288 | id: this.state.player1.id, 289 | progress: this.state.player1.progress, 290 | username: p1username, 291 | userID: this.state.player1.userID, 292 | powerUpNum: (this.state.player1.powerUpNum + 1), 293 | code: this.state.player1.code 294 | } 295 | }, () => {this.props.setOpponentAnswers(this.state.player2.code)}) 296 | // console.log('Player 2 progress updated', this.state.player2.progress ) 297 | 298 | if (this.state.player2.progress === this.state.numberOfQuestions){ 299 | notify.show('Player 2 answered question #' + this.state.player2.progress + ' and won! You can still keep going, though :)', 'error', notifyTimer); 300 | } else { 301 | notify.show('Player 2 submitted a correct answer to question #' + this.state.player2.progress +'!', 'warning', notifyTimer); 302 | } 303 | } 304 | } 305 | // console.log(`${data.playerToUpdate} has completed a question`); 306 | 307 | }) 308 | 309 | //when a player fails an attempt 310 | socket.on('failedSub', (data) => { 311 | console.log(`${data.playerToUpdate} has failed an attempt because of ${data.reason}`); 312 | }) 313 | 314 | 315 | socket.on('gameWinningState', (data) => { 316 | this.setState({gameWon: true}); 317 | }) 318 | 319 | socket.on('receive attack', (data) => { 320 | console.log('receiving attack'); 321 | if (socket.id === data) { 322 | this.setState({ 323 | modalIsOpen: true, 324 | }); 325 | setTimeout(() => { 326 | this.setState({modalIsOpen: false}); 327 | }, 9700); 328 | } 329 | }) 330 | } 331 | 332 | render() { 333 | return ( 334 |
335 | 336 |
337 |
338 | 339 | 340 |
341 |
342 | 343 | 344 |
345 |
346 |
347 |
348 | 349 |
350 |
351 | 352 |
353 |
354 | 355 | 356 |
357 | ); 358 | } 359 | } 360 | 361 | const mapStateToProps = (state) => ( 362 | { roomID : state.gameLobby, 363 | ownAnswers : state.matchResults.ownAnswers, 364 | opponentAnswers : state.matchResults.opponentAnswers 365 | } 366 | ) 367 | 368 | const mapDispatchToProps = (dispatch) => ({ 369 | setOpponentAnswers: function(answers) { 370 | console.log(answers); 371 | dispatch(setOpponentAnswers(answers)); 372 | }, 373 | setOwnAnswers: function(answers) { 374 | console.log(answers); 375 | dispatch(setOwnAnswers(answers)); 376 | } 377 | }) 378 | 379 | export default connect(mapStateToProps, mapDispatchToProps)(BattlePage); 380 | -------------------------------------------------------------------------------- /src/routes/BattlePage/containers/BattlePageContainer.scss: -------------------------------------------------------------------------------- 1 | // #codeeditor { 2 | // width: 700px; 3 | // padding: 0px; 4 | // } 5 | 6 | #codeeditor { 7 | height: 100%; 8 | } 9 | 10 | .problemsText { 11 | font-size: 20px; 12 | margin-bottom: 10px; 13 | } 14 | 15 | #problemsContainer { 16 | height: 100%; 17 | width: 100%; 18 | padding-left: 15px; 19 | } 20 | 21 | // .row-fluid-battlepage { 22 | // background-color: blue; 23 | // } 24 | 25 | .container { 26 | height: 100%; 27 | min-height: 70vh; 28 | margin-top: auto; 29 | margin-bottom: auto; 30 | padding-top: 15px; 31 | padding-bottom: 15px; 32 | padding-right: 15px; 33 | padding-left: 15px; 34 | margin-left: auto; 35 | margin-right: auto; 36 | } 37 | 38 | #battlePageWrapper { 39 | min-height: 100%; 40 | min-height: 100%; 41 | } 42 | 43 | .row.row-battlepage { 44 | height: 100%; 45 | margin-left: 0px; 46 | margin-right: 0px; 47 | } 48 | 49 | #playerProgressLabel { 50 | color: white; 51 | } 52 | 53 | #submit-btn-attack { 54 | background-color: dimgray; 55 | border-color: transparent; 56 | margin-bottom: 10px; 57 | text-align: center; 58 | } 59 | -------------------------------------------------------------------------------- /src/routes/BattlePage/index.js: -------------------------------------------------------------------------------- 1 | import BattlePageContainer from './containers/BattlePageContainer' 2 | 3 | // Sync route definition 4 | export default { 5 | component : BattlePageContainer 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/CodeEditor/components/CodeEditor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CodeMirror from 'react-codemirror'; 3 | import axios from 'axios'; 4 | import './CodeEditor.scss'; 5 | import 'codemirror/lib/codemirror.css' 6 | require('codemirror/mode/javascript/javascript'); 7 | require('codemirror/theme/base16-dark.css'); 8 | 9 | class CodeEditor extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | code: '', 14 | results: '', 15 | numberOfQuestions: 2, 16 | currentQuestionID: 0 17 | } 18 | this.updateCode = this.updateCode.bind(this); 19 | this.handleSubmit = this.handleSubmit.bind(this); 20 | } 21 | 22 | updateCode(newCode) { 23 | this.setState ({ 24 | code: newCode 25 | }); 26 | } 27 | 28 | handleSubmit() { 29 | axios.post('/api/code', { 30 | code: this.state.code, 31 | currentQuestionID: this.props.currentQuestionID.questionID, 32 | room: this.props.roomID, 33 | socketID: socket.id 34 | }) 35 | .then(response => { 36 | this.setState({results: response.data}); 37 | if(response.data.indexOf('failing') === -1 && response.data.indexOf('SyntaxError') === -1 && response.data.toLowerCase().indexOf('syntax') === -1 && response.data.indexOf('FATAL ERROR') === -1){ 38 | this.setState({code: ''}); 39 | } 40 | }) 41 | 42 | } 43 | render() { 44 | var options = { 45 | mode: 'javascript', 46 | lineNumbers: true, 47 | theme: 'base16-dark', 48 | tabSize: 2, 49 | lineWrapping: true, 50 | showCursorWhenSelecting: true 51 | }; 52 | var optionsForAnswers = { 53 | mode: 'javascript', 54 | lineNumbers: true, 55 | theme: 'base16-dark', 56 | tabSize: 2, 57 | lineWrapping: true, 58 | showCursorWhenSelecting: true, 59 | readOnly: true 60 | }; 61 | return ( 62 |
63 |
64 |
65 |
Enter Code 66 |
67 | 72 | 73 | 74 |
75 |
76 |
77 |
78 |
79 | Answer 80 |
81 | 85 |
86 |
87 |
88 | ); 89 | } 90 | } 91 | 92 | export default CodeEditor; 93 | 94 | -------------------------------------------------------------------------------- /src/routes/CodeEditor/components/CodeEditor.scss: -------------------------------------------------------------------------------- 1 | #code-editor-text { 2 | height: 100%; 3 | } 4 | 5 | .codeMirror { 6 | height: 100%; 7 | width: 100%; 8 | overflow-y: scroll; 9 | background-color: #151512; 10 | } 11 | 12 | .playerProgress { 13 | font-size: 20px; 14 | margin-bottom: 5px; 15 | margin-left: 0px; 16 | margin-right: 0px; 17 | } 18 | 19 | #submit-btn { 20 | background-color: dimgray; 21 | border-color: transparent; 22 | margin-bottom: 10px; 23 | } 24 | 25 | .ReactCodeMirror { 26 | border: 2px solid black; 27 | text-align: left; 28 | height: auto; 29 | overflow-y: hidden; 30 | margin-bottom: 10px; 31 | } 32 | 33 | .container { 34 | margin-left: auto; 35 | margin-right: auto; 36 | max-width: 100%; 37 | padding: 1em; 38 | height: 100%; 39 | } 40 | 41 | #enterCodeText { 42 | background-color: rgba(0,0,0,0.2); 43 | padding-left: 10px; 44 | text-align: left; 45 | } 46 | 47 | .row.row-codeeditor { 48 | height: 100%; 49 | margin-left: 0px; 50 | margin-right: 0px; 51 | } 52 | 53 | .cm-invalidchar, .cm-s-default .cm-error { 54 | color: #151515 !important; 55 | } 56 | 57 | .CodeMirror-gutter-wrapper { 58 | left: -30px !important; 59 | } 60 | .CodeMirror-sizer { 61 | margin-left: 30px !important; 62 | min-width: 7px !important; 63 | } 64 | .CodeMirror-gutters{ 65 | left: 0 !important; 66 | } 67 | .CodeMirror-gutter.CodeMirror-linenumbers{ 68 | width: 29px !important; 69 | } 70 | 71 | .CodeMirror-linenumber.CodeMirror-gutter-elt { 72 | width: 21px !important; 73 | } 74 | 75 | // .CodeMirror-vscrollbar { 76 | // color: black !important; 77 | // } 78 | -------------------------------------------------------------------------------- /src/routes/CodeEditor/container/CodeEditorContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { connect } from 'react-redux'; 3 | import CodeEditor from '../components/CodeEditor'; 4 | 5 | const mapStateToProps = (state) => ({ 6 | code: state.code, 7 | results: state.results 8 | }) 9 | 10 | export default connect(mapStateToProps)(CodeEditor) 11 | -------------------------------------------------------------------------------- /src/routes/CodeEditor/index.js: -------------------------------------------------------------------------------- 1 | import CodeEditor from './components/CodeEditor' 2 | 3 | // Sync route definition 4 | export default { 5 | component : CodeEditor 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/Counter/components/Counter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Counter = (props) => ( 4 |
5 |

Counter: {props.counter}

6 | 9 | {' '} 10 | 13 |
14 | ) 15 | 16 | export default Counter; 17 | -------------------------------------------------------------------------------- /src/routes/Counter/containers/CounterContainer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { connect } from 'react-redux' 3 | import { increment, doubleAsync } from '../modules/counter' 4 | 5 | import Counter from '../components/Counter' 6 | 7 | const mapDispatchToProps = { 8 | increment : () => increment(1), 9 | doubleAsync 10 | } 11 | 12 | const mapStateToProps = (state) => ({ 13 | counter : state.counter 14 | }) 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(Counter) 17 | -------------------------------------------------------------------------------- /src/routes/Counter/index.js: -------------------------------------------------------------------------------- 1 | import { injectReducer } from '../../store/reducers' 2 | 3 | export default (store) => ({ 4 | path : 'counter', 5 | /* Async getComponent is only invoked when route matches */ 6 | getComponent (nextState, cb) { 7 | /* Webpack - use 'require.ensure' to create a split point 8 | and embed an async module loader (jsonp) when bundling */ 9 | require.ensure([], (require) => { 10 | /* Webpack - use require callback to define 11 | dependencies for bundling */ 12 | const Counter = require('./containers/CounterContainer').default 13 | const reducer = require('./modules/counter').default 14 | 15 | /* Add the reducer to the store on key 'counter' */ 16 | injectReducer(store, { key: 'counter', reducer }) 17 | 18 | /* Return getComponent */ 19 | cb(null, Counter) 20 | 21 | /* Webpack named bundle */ 22 | }, 'counter') 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/routes/Counter/modules/counter.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const COUNTER_INCREMENT = 'COUNTER_INCREMENT' 5 | export const COUNTER_DOUBLE_ASYNC = 'COUNTER_DOUBLE_ASYNC' 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | export function increment (value = 1) { 11 | return { 12 | type : COUNTER_INCREMENT, 13 | payload : value 14 | } 15 | } 16 | 17 | /* This is a thunk, meaning it is a function that immediately 18 | returns a function for lazy evaluation. It is incredibly useful for 19 | creating async actions, especially when combined with redux-thunk! */ 20 | 21 | export const doubleAsync = () => { 22 | return (dispatch, getState) => { 23 | return new Promise((resolve) => { 24 | setTimeout(() => { 25 | dispatch({ 26 | type : COUNTER_DOUBLE_ASYNC, 27 | payload : getState().counter 28 | }) 29 | resolve() 30 | }, 200) 31 | }) 32 | } 33 | } 34 | 35 | export const actions = { 36 | increment, 37 | doubleAsync 38 | } 39 | 40 | // ------------------------------------ 41 | // Action Handlers 42 | // ------------------------------------ 43 | const ACTION_HANDLERS = { 44 | [COUNTER_INCREMENT] : (state, action) => state + action.payload, 45 | [COUNTER_DOUBLE_ASYNC] : (state, action) => state * 2 46 | } 47 | 48 | // ------------------------------------ 49 | // Reducer 50 | // ------------------------------------ 51 | const initialState = 0 52 | export default function counterReducer (state = initialState, action) { 53 | const handler = ACTION_HANDLERS[action.type] 54 | 55 | return handler ? handler(state, action) : state 56 | } 57 | -------------------------------------------------------------------------------- /src/routes/GameFinishedPage/components/GameFinishedPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import './GameFinishedPage.scss'; 4 | import CodeMirror from 'react-codemirror'; 5 | 6 | class GameFinishedPage extends Component { 7 | constructor() { 8 | super() 9 | } 10 | render(){ 11 | const { ownAnswers, opponentAnswers } = this.props; 12 | var optionsForAnswers = { 13 | mode: 'javascript', 14 | lineNumbers: true, 15 | theme: 'base16-dark', 16 | tabSize: 2, 17 | lineWrapping: true, 18 | showCursorWhenSelecting: true, 19 | readOnly: true 20 | }; 21 | return ( 22 |
23 |
24 |
25 |
26 | At least you tried 31 | You Lost, But At Least You Tried ;D 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | My Answers: 40 |
41 | { 42 | ownAnswers && ownAnswers.map((answer, i = 1) => 43 |
44 |
45 | Question {++i}: 46 |
47 |
48 | 52 |
53 |
54 | ) 55 | } 56 |
57 |
58 |
59 | Opponent's Answers: 60 |
61 | { 62 | opponentAnswers && opponentAnswers.map((answer, i = 1) => 63 |
64 |
65 | Question {++i}: 66 |
67 |
68 | 72 |
73 |
74 | ) 75 | } 76 |
77 |
78 |
79 |
80 | ) 81 | } 82 | } 83 | 84 | function mapStateToProps(state){ 85 | return { 86 | ownAnswers: state.matchResults.ownAnswers, 87 | opponentAnswers: state.matchResults.opponentAnswers 88 | } 89 | } 90 | 91 | export default connect(mapStateToProps)(GameFinishedPage); 92 | -------------------------------------------------------------------------------- /src/routes/GameFinishedPage/components/GameFinishedPage.scss: -------------------------------------------------------------------------------- 1 | #gameFinished { 2 | background-color: rgba(0,0,0,.2); 3 | h1 { 4 | margin-top: 10px; 5 | color: #fff; 6 | padding-top: 0px; 7 | padding-bottom: 0px; 8 | } 9 | #simpsons { 10 | margin-bottom: 1em; 11 | .img-responsive { 12 | width: 100%; 13 | margin: 0 auto; 14 | padding: 0 auto; 15 | } 16 | } 17 | } 18 | 19 | #gameCompletePage { 20 | padding-top: 5px; 21 | background-color: rgba(0,0,0,.2); 22 | } 23 | 24 | #userAnswers { 25 | background-color: rgba(0,0,0,.2); 26 | color: white; 27 | padding-right: 0px; 28 | padding-left: 0px; 29 | word-wrap: break-word; 30 | font-size: 16px; 31 | max-height: 350px; 32 | overflow-y: scroll; 33 | border-radius: 10px 10px 10px 0px; 34 | #answersheader { 35 | width: 100%; 36 | margin-bottom: 5px; 37 | background-color: #191919; 38 | } 39 | } 40 | 41 | #headRow { 42 | height: 100%; 43 | width: 100%; 44 | } 45 | .heady { 46 | width: 740px; 47 | height: 130px; 48 | margin: 20px auto 10px auto; 49 | background: #777; 50 | border-radius: 50px 50px 10px 10px; 51 | box-shadow: 5px 8px 8px black; 52 | line-height: 130px; 53 | .simpsons { 54 | width: 150px; 55 | height: 100px; 56 | border-radius: 40px 30px 0px 0px; 57 | top: 0px; 58 | bottom: 0px; 59 | margin: auto; 60 | } 61 | .memo { 62 | font-size: 40px; 63 | float: right; 64 | font-family: 'Caveat Brush', cursive; 65 | margin-right: 10%; 66 | } 67 | } 68 | 69 | .boxy { 70 | margin: 20px auto; 71 | padding: 20px; 72 | width: 714px; 73 | height: 416px; 74 | background: #777; 75 | border-radius: 10px 10px 10px 50px; 76 | box-shadow: 5px 8px 8px black; 77 | #myanswer { 78 | background-color: rgba(0,0,0,.2); 79 | color: black; 80 | padding-right: 0px; 81 | padding-left: 0px; 82 | word-wrap: break-word; 83 | font-size: 12px; 84 | max-height: 350px; 85 | overflow-y: scroll; 86 | border-radius: 10px 10px 0px 10px; 87 | #answersheader { 88 | margin-bottom: 5px; 89 | font-size: 14px; 90 | color: white; 91 | background-color: #191919; 92 | } 93 | .ReactCodeMirror { 94 | border: 2px solid black; 95 | text-align: left; 96 | height: 150px; 97 | overflow-y: hidden; 98 | margin-bottom: 10px; 99 | } 100 | } 101 | #userAnswers { 102 | background-color: rgba(0,0,0,.2); 103 | color: black; 104 | padding-right: 0px; 105 | padding-left: 0px; 106 | word-wrap: break-word; 107 | font-size: 12px; 108 | max-height: 350px; 109 | overflow-y: scroll; 110 | border-radius: 10px 10px 10px 0px; 111 | #answersheader { 112 | margin-bottom: 5px; 113 | color: white; 114 | font-size: 14px; 115 | background-color: #191919; 116 | } 117 | .ReactCodeMirror { 118 | border: 2px solid black; 119 | text-align: left; 120 | height: 150px; 121 | overflow-y: hidden; 122 | margin-bottom: 10px; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/routes/GameLobby/components/GameLobby.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './GameLobby.scss'; 3 | import axios from 'axios'; 4 | import CopyToClipboard from 'react-copy-to-clipboard'; 5 | 6 | class GameLobby extends React.Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | copied: false, 12 | roomId: '' 13 | } 14 | } 15 | 16 | componentDidMount() { 17 | socket.on('startGame', data => { 18 | browserHistory.push('/battlePage'); 19 | }) 20 | const roomId = makeid(); 21 | socket.emit('joinGameLobby', roomId); 22 | this.setState({roomId}); 23 | } 24 | 25 | render() { 26 | const { user } = this.props; 27 | const { roomId } = this.state; 28 | return ( 29 |
30 | 31 | { 32 | Object.keys(user).length 33 | ? 34 |
35 | this.setState({ copied: false})} 40 | /> 41 | 42 | this.setState({copied: true})} 45 | > 46 | 47 | 48 | {this.state.copied ? Copied. : null} 49 |
50 | :
Please log in
51 | } 52 |
53 | ) 54 | } 55 | } 56 | 57 | const makeid = () => { 58 | let text = ""; 59 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 60 | for(let i = 0; i < 6; i++ ) 61 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 62 | return text; 63 | } 64 | 65 | export default GameLobby; 66 | -------------------------------------------------------------------------------- /src/routes/GameLobby/components/GameLobby.scss: -------------------------------------------------------------------------------- 1 | .join { 2 | margin-top: 10px; 3 | } 4 | 5 | #gameInvite { 6 | text-align: center; 7 | } 8 | 9 | .btn.btn-primary.btn-join { 10 | margin-top: 10px; 11 | } 12 | 13 | .form-control { 14 | text-align: center; 15 | } 16 | 17 | .input-group { 18 | text-align: center; 19 | } 20 | 21 | .input-group { 22 | .form-control { 23 | z-index: 0; 24 | } 25 | } 26 | 27 | .ninja-modal { 28 | margin-bottom: 5px; 29 | } 30 | -------------------------------------------------------------------------------- /src/routes/GameLobby/containers/GameLobbyContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import GameLobby from '../components/GameLobby'; 3 | 4 | const mapStateToProps = (state) => { 5 | return { 6 | roomid: state.gameLobby.id, 7 | user: state.user.user 8 | }; 9 | } 10 | 11 | export default connect(mapStateToProps)(GameLobby); 12 | -------------------------------------------------------------------------------- /src/routes/GameWonPage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/GameWonPage/.DS_Store -------------------------------------------------------------------------------- /src/routes/GameWonPage/assets/fireworks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/GameWonPage/assets/fireworks.gif -------------------------------------------------------------------------------- /src/routes/GameWonPage/components/GameWonPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import './GameWonPage.scss'; 4 | import CodeMirror from 'react-codemirror'; 5 | 6 | class GameWonPage extends Component { 7 | constructor(){ 8 | super() 9 | } 10 | render(){ 11 | const { ownAnswers, opponentAnswers } = this.props; 12 | var optionsForAnswers = { 13 | mode: 'javascript', 14 | lineNumbers: true, 15 | theme: 'base16-dark', 16 | tabSize: 2, 17 | lineWrapping: true, 18 | showCursorWhenSelecting: true, 19 | readOnly: true 20 | }; 21 | return ( 22 |
23 |
24 |
25 |
26 | 27 | Congratulations You Won! 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | My Answers: 36 |
37 | { 38 | ownAnswers && ownAnswers.map((answer, i = 1) => 39 |
40 |
41 | Question {++i}: 42 |
43 |
44 | 50 |
51 |
52 | ) 53 | } 54 |
55 |
56 |
57 | Opponent's Answers: 58 |
59 | { 60 | opponentAnswers && opponentAnswers.map((answer, i = 1) => 61 |
62 |
63 | Question {++i}: 64 |
65 |
66 | 71 |
72 |
73 | ) 74 | } 75 |
76 |
77 |
78 |
79 | ) 80 | } 81 | } 82 | 83 | function mapStateToProps(state) { 84 | return { 85 | ownAnswers: state.matchResults.ownAnswers, 86 | opponentAnswers: state.matchResults.opponentAnswers 87 | } 88 | } 89 | 90 | export default connect(mapStateToProps)(GameWonPage); 91 | -------------------------------------------------------------------------------- /src/routes/GameWonPage/components/GameWonPage.scss: -------------------------------------------------------------------------------- 1 | #gameFinished { 2 | background-color: rgba(0,0,0,.2); 3 | h1 { 4 | margin-top: 10px; 5 | color: #fff; 6 | padding-top: 0px; 7 | padding-bottom: 0px; 8 | } 9 | #fireworks { 10 | margin-bottom: 1em; 11 | .img-responsive { 12 | width: 100%; 13 | margin: 0 auto; 14 | padding: 0 auto; 15 | } 16 | } 17 | } 18 | 19 | #gameCompletePage { 20 | padding-top: 5px; 21 | background-color: rgba(0,0,0,.2); 22 | } 23 | 24 | #userAnswers { 25 | background-color: rgba(0,0,0,.2); 26 | color: white; 27 | padding-right: 0px; 28 | padding-left: 0px; 29 | word-wrap: break-word; 30 | font-size: 16px; 31 | max-height: 350px; 32 | overflow-y: scroll; 33 | border-radius: 10px 10px 10px 0px; 34 | #answersheader { 35 | width: 100%; 36 | margin-bottom: 5px; 37 | background-color: #191919; 38 | } 39 | } 40 | 41 | #headRow { 42 | height: 100%; 43 | width: 100%; 44 | } 45 | .heady { 46 | width: 740px; 47 | height: 130px; 48 | margin: 20px auto 10px auto; 49 | background: #777; 50 | border-radius: 50px 50px 10px 10px; 51 | box-shadow: 5px 8px 8px black; 52 | line-height: 130px; 53 | .fireworks { 54 | width: 150px; 55 | height: 100px; 56 | border-radius: 40px 30px 0px 0px; 57 | top: 0px; 58 | bottom: 0px; 59 | margin: auto; 60 | } 61 | .slogan { 62 | font-size: 40px; 63 | float: right; 64 | font-family: 'Caveat Brush', cursive; 65 | margin-right: 25%; 66 | } 67 | } 68 | 69 | .boxy { 70 | margin: 20px auto; 71 | padding: 20px; 72 | width: 714px; 73 | height: 416px; 74 | background: #777; 75 | border-radius: 10px 10px 10px 50px; 76 | box-shadow: 5px 8px 8px black; 77 | #myanswer { 78 | background-color: rgba(0,0,0,.2); 79 | color: black; 80 | padding-right: 0px; 81 | padding-left: 0px; 82 | word-wrap: break-word; 83 | font-size: 12px; 84 | max-height: 350px; 85 | overflow-y: scroll; 86 | border-radius: 10px 10px 0px 10px; 87 | #answersheader { 88 | margin-bottom: 5px; 89 | font-size: 14px; 90 | color: white; 91 | background-color: #191919; 92 | } 93 | .ReactCodeMirror { 94 | border: 2px solid black; 95 | text-align: left; 96 | height: 150px; 97 | overflow-y: hidden; 98 | margin-bottom: 10px; 99 | } 100 | } 101 | #userAnswers { 102 | background-color: rgba(0,0,0,.2); 103 | color: black; 104 | padding-right: 0px; 105 | padding-left: 0px; 106 | word-wrap: break-word; 107 | font-size: 12px; 108 | max-height: 350px; 109 | overflow-y: scroll; 110 | border-radius: 10px 10px 10px 0px; 111 | #answersheader { 112 | margin-bottom: 5px; 113 | color: white; 114 | font-size: 14px; 115 | background-color: #191919; 116 | } 117 | .ReactCodeMirror { 118 | border: 2px solid black; 119 | text-align: left; 120 | height: 150px; 121 | overflow-y: hidden; 122 | margin-bottom: 10px; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/routes/Home/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/Home/.DS_Store -------------------------------------------------------------------------------- /src/routes/Home/assets/Duck.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/Home/assets/Duck.jpg -------------------------------------------------------------------------------- /src/routes/Home/assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/Home/assets/Logo.png -------------------------------------------------------------------------------- /src/routes/Home/assets/ninja_attack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/Home/assets/ninja_attack.png -------------------------------------------------------------------------------- /src/routes/Home/assets/ninja_bowing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FSACodeBattle/GameNight/328cd9825ef66e65b9e1ec99a4c90ecd082512ee/src/routes/Home/assets/ninja_bowing.jpg -------------------------------------------------------------------------------- /src/routes/Home/components/Achievements/Achievements.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../HomeView.scss'; 3 | import './Achievements.scss'; 4 | 5 | export const Achievements = () => ( 6 |
7 |

Achievements

8 | 9 | 10 | 11 | 12 | 13 |
14 | ) 15 | 16 | export default Achievements; 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/routes/Home/components/Achievements/Achievements.scss: -------------------------------------------------------------------------------- 1 | #achievementsContainer { 2 | background-color: rgba(0,0,0,.2); 3 | h2 { 4 | color: #777; 5 | } 6 | #achievements { 7 | height: 120px; 8 | width: 120px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/Home/components/Achievements/index.js: -------------------------------------------------------------------------------- 1 | import Achievements from './Achievements.js'; 2 | 3 | export default Achievements; 4 | -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import './HomeView.scss'; 3 | import MatchHistory from './MatchHistory'; 4 | import Achievements from './Achievements'; 5 | import Leaderboard from './Leaderboard'; 6 | import { Jumbotron } from 'react-bootstrap'; 7 | import MainLobbyContainer from '../../MainLobbyList/containers/MainLobbyContainer'; 8 | import axios from 'axios'; 9 | import HomePage from './homePage'; 10 | 11 | class HomeView extends Component { 12 | 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | username: '', 17 | leaderboard: [] 18 | } 19 | } 20 | 21 | componentDidMount(){ 22 | axios.get('/api/users/leaderboard') 23 | .then(leaderboard => this.setState({leaderboard: leaderboard.data})); 24 | } 25 | 26 | render(){ 27 | return ( 28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 |
Leader Board
40 | 41 |
42 |
43 |
Match History
44 | 45 |
46 | {/**/} 47 |
48 | {/*
49 | 50 |
*/} 51 |
52 | 53 | ); 54 | } 55 | } 56 | 57 | export default HomeView; 58 | -------------------------------------------------------------------------------- /src/routes/Home/components/HomeView.scss: -------------------------------------------------------------------------------- 1 | h2 { 2 | text-align: center; 3 | color: white 4 | } 5 | 6 | // .container.homeView { 7 | // margin-left: auto; 8 | // margin-right: auto; 9 | // } 10 | 11 | 12 | #introaboutcode { 13 | margin-left: auto; 14 | margin-right: auto; 15 | } 16 | 17 | .container.container-jumbotron { 18 | height: 100%; 19 | margin-top: auto; 20 | margin-bottom: auto; 21 | // padding-top: 15px; 22 | // padding-bottom: 15px; 23 | // padding-right: 15px; 24 | // padding-left: 15px; 25 | margin-left: auto; 26 | margin-right: auto; 27 | } 28 | 29 | .jumbotron { 30 | padding: 0px; 31 | margin: 0px auto; 32 | background: #7986cb; 33 | height: 100%; 34 | min-height: 94vh; 35 | color: black; 36 | background-image: url("../../../../public/background.jpg"); 37 | background-repeat: no-repeat; 38 | background-size:100% 100%; 39 | .row.row-homeview { 40 | img { 41 | width: 45%; 42 | margin: 0 auto; 43 | padding: 0 auto; 44 | } 45 | } 46 | p { 47 | padding-top: 0px; 48 | text-align: center; 49 | font-size: 3em; 50 | margin-bottom: 0px; 51 | span { 52 | font: bold 24px/45px Helvetica, Sans-Serif; 53 | background: rgb(0, 0, 0); /* fallback color */ 54 | background: rgba(0, 0, 0, 0.2); 55 | } 56 | } 57 | } 58 | 59 | // .homepageleaderboard { 60 | // width: 80%; 61 | // margin: 50px auto; 62 | // div { 63 | // color: white; 64 | // } 65 | // } 66 | 67 | .row.matchleader { 68 | padding-left: 15px; 69 | padding-right: 15px; 70 | margin: 15px; 71 | #matchhistoryboard { 72 | margin-top: 20px; 73 | } 74 | #leader { 75 | margin-top: 20px; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/routes/Home/components/Leaderboard/Leaderboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import '../HomeView.scss'; 4 | import './leaderboard.scss'; 5 | 6 | export const Leaderboard = (props) => { 7 | const leaderboard = props.leaderboard; 8 | //if leaderboard is defined 9 | if (leaderboard){ 10 | 11 | var rows = leaderboard.map((user, i) => 12 | 13 | {++i} 14 | {user.username} 15 | {user.wins} 16 | {user.wins + user.losses ? (user.wins/(user.wins + user.losses)).toFixed(2) : 0} 17 | {user.points} 18 | 19 | ) 20 | } 21 | 22 | return ( 23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {rows} 37 | 38 |
#UsernameTotal WinsWinning PercentagePoints
39 |
40 |
41 | ) 42 | } 43 | 44 | export default Leaderboard; 45 | -------------------------------------------------------------------------------- /src/routes/Home/components/Leaderboard/index.js: -------------------------------------------------------------------------------- 1 | import Leaderboard from './Leaderboard.js'; 2 | 3 | export default Leaderboard; 4 | -------------------------------------------------------------------------------- /src/routes/Home/components/Leaderboard/leaderboard.scss: -------------------------------------------------------------------------------- 1 | #leaderboardComponent { 2 | background-color: #151515; 3 | color: #777; 4 | height: 305px; 5 | overflow-y: scroll; 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/Home/components/MatchHistory.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux'; 3 | import { Link } from 'react-router'; 4 | import './HomeView.scss' 5 | import './Matchhistory.scss'; 6 | 7 | export const MatchHistory = (props) => { 8 | const matches = props.userMatches || props.matches; 9 | const rows = matches.map(match => 10 | 11 | {match.createdAt.substring(0, 10)} 12 | {match.id} 13 | {match.winner.username} 14 | {match.loser.username} 15 | {match.winnerDuration.toFixed(1) + ' sec'} 16 | 17 | ) 18 | 19 | return ( 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {rows} 35 | 36 |
DateMatch IDWinning PlayerLosing PlayerWinning Time
37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | const mapStateToProps = (state) => ({ matches: state.match.matches }) 44 | 45 | export default connect(mapStateToProps)(MatchHistory); 46 | -------------------------------------------------------------------------------- /src/routes/Home/components/MatchHistory.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DuckImage from '../assets/Duck.jpg' 3 | import './HomeView.scss' 4 | 5 | export const MatchHistory = () => ( 6 |
7 |

Match History

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
#Match IDWin/LossOpponentStart TimeWinning Time
15WinCodeMaster3:40 PM 1/15/20175:45
162LossQueenCoder12:16 PM 1/17/20173:06
177WinCodeNoob9:49 PM 1/20/20175:45
15WinCodeMaster3:40 PM 1/15/20175:45
15WinCodeMaster3:40 PM 1/15/20175:45
62 |
63 | ) 64 | 65 | export default MatchHistory; 66 | -------------------------------------------------------------------------------- /src/routes/Home/components/Matchhistory.scss: -------------------------------------------------------------------------------- 1 | // #match-history-container { 2 | // background-color: #1F1F1F; 3 | // } 4 | #matchComponent { 5 | background-color: #151515; 6 | color: #777; 7 | height: 305px; 8 | overflow-y: scroll; 9 | } 10 | 11 | .table-responsive { 12 | color: #777; 13 | width: 100%; 14 | } 15 | 16 | h2 { 17 | text-align: center; 18 | } 19 | 20 | td { 21 | text-align: center; 22 | } 23 | 24 | th { 25 | text-align: center; 26 | } 27 | 28 | a { 29 | color: inherit; 30 | } 31 | -------------------------------------------------------------------------------- /src/routes/Home/components/homePage/homePage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import React from 'react'; 3 | import './homePage.scss'; 4 | import Logo from '../../assets/Logo.png' 5 | import NinjaBowing from '../../assets/ninja_bowing.jpg' 6 | import NinjaAttack from '../../assets/ninja_attack.png' 7 | 8 | const HomePage = () => ( 9 |
10 |
11 |
12 | 20 |
21 |
22 |

23 | Code Battle 24 |

25 |

26 | Challenge your friends to a Code Battle! Compete to see who can solve programming challenges the fastest in a competitive environment. Win to earn points and advance up the Leaderboard.

CodeBattle is designed with questions related to coding fundatmentals chosen to prepare students for success in the rigorous
Fullstack Code Academy program. 27 |

28 |
29 |
30 |
31 |
32 |

33 | Quick Play or Create a Lobby 34 |

35 |

36 | Want to get right into a match? Sign up or log in and select "Quick Play" and you will be matched with the first user available. Good luck!

Want to challenge a friend? Sign up or log in and select "Create a Lobby" and send the link to your friend. When they join you will hop right into a Code Battle! 37 |

38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 | 54 |
55 |
56 |

57 | Review Code 58 |

59 |

60 | After the battle, both players will have a chance to review their own code as well as see what their opponent submitted.

If you lost the Code Battle, review your opponent's methods to learn them and be more prepared for the next battle.

If you won, you can review your opponent's code to help them improve. 61 |

62 |
63 |
64 |
65 |
66 |

67 | Send Attacks! 68 |

69 |

70 | Is your opponent advancing too quickly through the Code Battle? Unleash a devastating attack to block their progress and disrupt their rhythm.

More powerful attacks are currently in the works! 71 |

72 |
73 |
74 | 81 |
82 |
83 | {/*
84 |
85 |

86 | PUT SOME IMAGE HERE?? 87 |

88 |
89 |
90 |

91 | CodeBattle 92 |

93 |

94 | Want to learn coding fundamentals while having fun?
Challenge your friends to a CodeBattle! Win to earn points
Unlock achievements as you learn the basics of JavaScript. CodeBattle is designed to prepare students for success in the rigorous Fullstack Code Academy program. 95 |

96 |
97 |
*/} 98 |
99 | ) 100 | export default HomePage; 101 | -------------------------------------------------------------------------------- /src/routes/Home/components/homePage/homePage.scss: -------------------------------------------------------------------------------- 1 | .row.row-code-battle { 2 | height: 100%; 3 | min-height: 250px; 4 | width: 100%; 5 | padding: 15px; 6 | margin-left: auto; 7 | margin-right: auto; 8 | background-color: #dedede; 9 | text-align: center; 10 | div { 11 | color: black; 12 | p { 13 | text-align: center; 14 | color: black; 15 | word-wrap: break-word; 16 | font-size: 18px; 17 | } 18 | } 19 | } 20 | 21 | .row.row-code-battle2 { 22 | height: 100%; 23 | min-height: 250px; 24 | width: 100%; 25 | padding: 15px; 26 | margin-left: auto; 27 | margin-right: auto; 28 | background-color: #d1d1d1; 29 | text-align: center; 30 | div { 31 | color: black; 32 | p { 33 | margin: 0px auto; 34 | text-align: center; 35 | color: black; 36 | word-wrap: break-word; 37 | font-size: 18px; 38 | } 39 | } 40 | } 41 | 42 | .row.row-code-battle3 { 43 | height: 100%; 44 | min-height: 250px; 45 | width: 100%; 46 | padding: 15px; 47 | margin-left: auto; 48 | margin-right: auto; 49 | background-color: #dedede; 50 | text-align: center; 51 | div { 52 | color: black; 53 | p { 54 | text-align: center; 55 | color: black; 56 | word-wrap: break-word; 57 | font-size: 18px; 58 | } 59 | } 60 | } 61 | 62 | .row.row-code-battle4 { 63 | height: 100%; 64 | min-height: 250px; 65 | width: 100%; 66 | padding: 15px; 67 | margin-left: auto; 68 | margin-right: auto; 69 | background-color: #d1d1d1; 70 | text-align: center; 71 | div { 72 | color: black; 73 | p { 74 | margin: 0px auto; 75 | text-align: center; 76 | color: black; 77 | word-wrap: break-word; 78 | font-size: 18px; 79 | } 80 | } 81 | } 82 | 83 | img-fluid { 84 | height: 120px; 85 | width: auto; 86 | } 87 | 88 | .img-cirlce { 89 | border-radius: 50%; 90 | height: auto; 91 | width: auto; 92 | max-width: 400px; 93 | max-height: 400px; 94 | } 95 | .img-quickplay { 96 | height: auto; 97 | width: auto; 98 | max-width: 300px; 99 | max-height: 300px; 100 | } 101 | -------------------------------------------------------------------------------- /src/routes/Home/components/homePage/index.js: -------------------------------------------------------------------------------- 1 | import HomePage from './homePage.js'; 2 | 3 | export default HomePage; 4 | -------------------------------------------------------------------------------- /src/routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomeView from './components/HomeView' 2 | 3 | // Sync route definition 4 | export default { 5 | component : HomeView 6 | } 7 | -------------------------------------------------------------------------------- /src/routes/InvitePage/components/InvitePage.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CodeMirror from 'react-codemirror'; 3 | import axios from 'axios'; 4 | 5 | 6 | 7 | 8 | class InvitePage extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | 13 | } 14 | this.updateCode = this.updateCode.bind(this); 15 | this.handleSubmit = this.handleSubmit.bind(this); 16 | } 17 | 18 | componentDidMount(){ 19 | 20 | } 21 | 22 | 23 | updateCode(newCode) { 24 | this.setState ({ 25 | code: newCode 26 | }); 27 | } 28 | 29 | handleSubmit() { 30 | console.log('handleSubmit works if this shows your code', this.state.code); 31 | const startingTime = this.state.startingTime; 32 | axios.post('/api/code', { 33 | code: this.state.code, 34 | timeElapsed: (Date.now() - startingTime)/1000 35 | }) 36 | .then(response => { 37 | this.setState({results: response.data}); 38 | console.log("response from running code: ", response.data ); 39 | console.log('saved successfully'); 40 | 41 | // if response.data is correct, then emit question is passed to server 42 | if(response.data.slice(0, 39) === "bash: line 21: babel: command not found"){ 43 | console.log('emitting correct response from front-end') 44 | // console.log(this.state.playerNumber); 45 | socket.emit('correct response', {playerNumber: this.state.playerNumber}); 46 | socket.on('update progress', (playerProgress) => { 47 | this.setState({ 48 | playerProgress: playerProgress 49 | }) 50 | }) 51 | // console.log(this.state.playerProgress); 52 | } 53 | 54 | }) 55 | 56 | } 57 | render() { 58 | function makeid() 59 | { 60 | var text = ""; 61 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 62 | for( var i=0; i < 6; i++ ) 63 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 64 | 65 | return text; 66 | } 67 | 68 | const inviteURL = 'http://localhost/joinGame/' + makeid(); 69 | console.log(inviteURL.length); 70 | return ( 71 |
72 |

THIS IS THE INVITE PAGE

73 | 74 |
75 | ); 76 | } 77 | } 78 | 79 | export default InvitePage 80 | -------------------------------------------------------------------------------- /src/routes/JoinLobby/JoinLobby.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import React from 'react'; 3 | 4 | class JoinLobby extends React.Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | roomId: '' 9 | } 10 | this.handleChange = this.handleChange.bind(this); 11 | this.handleJoin = this.handleJoin.bind(this); 12 | } 13 | 14 | componentWillMount() { 15 | socket.on('startGame', data => { 16 | browserHistory.push('/battlePage'); 17 | }) 18 | } 19 | 20 | handleChange(event) { 21 | this.setState({roomId: event.target.value}); 22 | } 23 | 24 | handleJoin() { 25 | socket.emit('joinGameLobby', this.state.roomId); 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 | 32 | 33 |
34 | ) 35 | } 36 | } 37 | 38 | export default connect(null, null)(JoinLobby); -------------------------------------------------------------------------------- /src/routes/JoinPage/components/JoinPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CodeMirror from 'react-codemirror'; 3 | import axios from 'axios'; 4 | require('codemirror/mode/javascript/javascript'); 5 | 6 | import io from 'socket.io-client' 7 | let socket = io(`http://localhost`) 8 | 9 | 10 | class CodeEditor extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | 15 | } 16 | this.updateCode = this.updateCode.bind(this); 17 | this.handleSubmit = this.handleSubmit.bind(this); 18 | } 19 | 20 | componentDidMount(){ 21 | } 22 | 23 | updateCode(newCode) { 24 | this.setState ({ 25 | code: newCode 26 | }); 27 | } 28 | 29 | handleSubmit() { 30 | console.log('handleSubmit works if this shows your code', this.state.code); 31 | const startingTime = this.state.startingTime; 32 | axios.post('/api/code', { 33 | code: this.state.code, 34 | timeElapsed: (Date.now() - startingTime)/1000 35 | }) 36 | .then(response => { 37 | this.setState({results: response.data}); 38 | console.log("response from running code: ", response.data ); 39 | console.log('saved successfully'); 40 | 41 | // if response.data is correct, then emit question is passed to server 42 | if(response.data.slice(0, 39) === "bash: line 21: babel: command not found"){ 43 | console.log('emitting correct response from front-end') 44 | // console.log(this.state.playerNumber); 45 | socket.emit('correct response', {playerNumber: this.state.playerNumber}); 46 | socket.on('update progress', (playerProgress) => { 47 | this.setState({ 48 | playerProgress: playerProgress 49 | }) 50 | }) 51 | // console.log(this.state.playerProgress); 52 | } 53 | 54 | }) 55 | 56 | } 57 | render() { 58 | var options = { 59 | mode: "javascript", 60 | lineNumbers: true 61 | }; 62 | 63 | return ( 64 |
65 | 66 |
67 | ); 68 | } 69 | } 70 | 71 | export default CodeEditor -------------------------------------------------------------------------------- /src/routes/MainLobbyList/components/MainLobby.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class MainLobby extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | render() { 9 | const clients = this.props.clients.clients; 10 | return ( 11 |
12 |

13 | Warriors 14 |

15 | { clients.length > 1 ? 16 | clients.filter(client => client.socketId !== socket.id).map((client, idx) =>
{client.username}
) 17 | : "None" 18 | } 19 |

20 | 21 |
22 | ) 23 | } 24 | } 25 | 26 | export default MainLobby; 27 | -------------------------------------------------------------------------------- /src/routes/MainLobbyList/containers/MainLobbyContainer.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import MainLobby from '../components/MainLobby'; 3 | import { fetchClients } from '../../../store/client'; 4 | 5 | const mapStateToProps = (state) => { 6 | return { 7 | clients: state.clients 8 | }; 9 | } 10 | 11 | const mapDispatchToProps = (dispatch) => { 12 | return {}; 13 | } 14 | 15 | export default connect(mapStateToProps, mapDispatchToProps)(MainLobby); 16 | -------------------------------------------------------------------------------- /src/routes/Profile/components/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import MatchHistory from '../../Home/components/MatchHistory'; 4 | import '../profile.scss'; 5 | 6 | 7 | class Profile extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | render() { 13 | const user = this.props.user; 14 | return ( 15 | Object.keys(user).length 16 | ? ( 17 |
18 |
19 | Username: {user.username} 20 |
21 |
22 | Member Since: {user.createdAt.substring(0, 10)} 23 |
24 |
25 | Wins: {user.wins} 26 |
27 |
28 | Losses: {user.losses} 29 |
30 |
31 | Points: {user.points} 32 |
33 |
34 |

Match History

35 | 36 |
37 |
38 | ) 39 | : (

Nonexistent User

) 40 | ) 41 | } 42 | } 43 | 44 | const mapStateToProps = (state) => ( 45 | { 46 | userMatches: state.profile.matches, 47 | user: state.profile.user 48 | } 49 | ) 50 | 51 | export default connect(mapStateToProps)(Profile); 52 | -------------------------------------------------------------------------------- /src/routes/Profile/profile.scss: -------------------------------------------------------------------------------- 1 | .profilePage { 2 | margin: 20px auto; 3 | color: white; 4 | font-size: 20px; 5 | } 6 | 7 | .profilePageMatchHistory { 8 | width: 80%; 9 | margin: 50px auto; 10 | div { 11 | color: white; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/routes/ReportABug/ReportABug.scss: -------------------------------------------------------------------------------- 1 | .bugForm { 2 | display: block; 3 | text-align: center; 4 | margin: 0 auto; 5 | margin-top: 10px; 6 | } 7 | 8 | #bugDescription { 9 | height: 200px; 10 | width: 350px; 11 | text-align: left; 12 | overflow: hidden; 13 | } 14 | 15 | #bugSubmit { 16 | background-color: dimgray; 17 | border-color: transparent; 18 | margin: 0 auto; 19 | margin-top: 20px; 20 | } 21 | 22 | h3 { 23 | color: white; 24 | } 25 | -------------------------------------------------------------------------------- /src/routes/ReportABug/components/ReportABug.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import { connect } from 'react-redux'; 4 | import { browserHistory } from 'react-router'; 5 | import '../ReportABug.scss'; 6 | 7 | class ReportABug extends React.Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | bugName: '', 14 | bugDescription: '', 15 | name: '', 16 | errorText: '', 17 | error: false, 18 | submittedBug: false 19 | } 20 | 21 | this.onSubmitHandler = this.onSubmitHandler.bind(this); 22 | this.onChangeHandler = this.onChangeHandler.bind(this); 23 | } 24 | 25 | onSubmitHandler(event) { 26 | event.preventDefault(); 27 | if(!this.state.bugName || !this.state.bugDescription || !this.state.name) 28 | { 29 | this.setState({error: true, errorText: "One or more invalid fields!"}); 30 | } 31 | else { 32 | axios.post('report-a-bug', this.state) 33 | .then(this.setState({ 34 | submittedBug: true 35 | })) 36 | } 37 | } 38 | 39 | onChangeHandler(event) { 40 | this.setState({[event.target.id]: event.target.value}); 41 | } 42 | 43 | render() { 44 | return ( 45 |
46 | { this.state.submittedBug ? 47 |
48 |

Thanks for helping to improve Code Battle!

49 | Leo saying thanks 53 |
54 | : 55 |
56 | { this.state.error 57 | ?
{ this.state.errorText }
58 | : null 59 | } 60 |
61 |

Bug Name

62 | 63 |

Bug Description

64 | 65 |

Your Name

66 | 67 | 68 |
69 |
70 | } 71 |
72 | ) 73 | } 74 | } 75 | 76 | export default ReportABug; 77 | -------------------------------------------------------------------------------- /src/routes/Signup/Signup.scss: -------------------------------------------------------------------------------- 1 | .loginForm { 2 | display: block; 3 | text-align: center; 4 | margin: 0px auto; 5 | margin-bottom: 3px; 6 | } 7 | 8 | #signupSubmit { 9 | background-color: dimgray; 10 | border-color: transparent; 11 | margin-top: 10px; 12 | } 13 | -------------------------------------------------------------------------------- /src/routes/Signup/components/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import { setUser } from '../../../store/user'; 4 | import { connect } from 'react-redux'; 5 | import { browserHistory } from 'react-router'; 6 | import '../Signup.scss'; 7 | 8 | class Signup extends React.Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | 13 | this.state = { 14 | signupUsername: '', 15 | signupPassword: '', 16 | confirmPassword: '', 17 | name: '', 18 | email: '', 19 | errorText: '', 20 | error: false 21 | } 22 | 23 | this.onSubmitHandler = this.onSubmitHandler.bind(this); 24 | this.onChangeHandler = this.onChangeHandler.bind(this); 25 | } 26 | 27 | onSubmitHandler(event) { 28 | event.preventDefault(); 29 | if(!this.state.signupUsername || !this.state.signupPassword || !this.state.confirmPassword 30 | || !this.state.name || !this.state.email) 31 | { 32 | this.setState({error: true, errorText: "One or more invalid fields!"}); 33 | } 34 | else if(this.state.signupPassword !== this.state.confirmPassword) { 35 | this.setState({error: true, errorText: "Passwords do not match!"}); 36 | } 37 | else { 38 | axios.post('/signup', this.state) 39 | .then(result => { 40 | const { data } = result; 41 | console.log('adjaksdfa', data); 42 | switch(data) { 43 | case 'user exists': this.setState({error: true, errorText: "User already exists"}); break; 44 | case 'Validation isEmail failed': this.setState({error: true, errorText: "Invalid email address"}); break; 45 | default: 46 | browserHistory.push('/'); 47 | break; 48 | } 49 | }); 50 | } 51 | } 52 | 53 | onChangeHandler(event) { 54 | this.setState({[event.target.id]: event.target.value}); 55 | } 56 | 57 | render() { 58 | return ( 59 |
60 | { this.state.error 61 | ?
{ this.state.errorText }
62 | : null 63 | } 64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 | ) 74 | } 75 | } 76 | 77 | const mapDispatchToProps = (dispatch) => { 78 | return { 79 | setLoggedInUser: function(user) { 80 | dispatch(setUser(user)); 81 | } 82 | } 83 | } 84 | 85 | export default connect(null, mapDispatchToProps)(Signup); 86 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Router, Route, browserHistory, IndexRoute } from 'react-router'; 3 | import { Provider, connect } from 'react-redux'; 4 | import axios from 'axios'; 5 | import CoreLayout from '../layouts/CoreLayout'; 6 | import Home from './Home/components/HomeView'; 7 | import CounterRoute from './Counter/containers/CounterContainer'; 8 | import CodeEditor from './CodeEditor/components/CodeEditor'; 9 | import BattlePage from './BattlePage/containers/BattlePageContainer'; 10 | import InvitePage from './InvitePage/components/InvitePage'; 11 | import About from './About/About.js'; 12 | import JoinLobby from './JoinLobby/JoinLobby.jsx'; 13 | import GameLobbyContainer from './GameLobby/containers/GameLobbyContainer'; 14 | import GameWonPage from './GameWonPage/components/GameWonPage' 15 | import GameFinishedPage from './GameFinishedPage/components/GameFinishedPage' 16 | import Login from '../components/Login/Login'; 17 | import Signup from './Signup/components/Signup'; 18 | import Profile from './Profile/components/Profile'; 19 | import ReportABug from './ReportABug/components/ReportABug'; 20 | 21 | import { fetchClients } from '../store/client' 22 | import { setRoomId } from '../store/gamelobby'; 23 | import { setUser } from '../store/user'; 24 | import { fetchMatches } from '../store/match'; 25 | import { fetchProfile } from '../store/profile'; 26 | 27 | function onJoinEnter(nextRouterState){ 28 | //console.log(nextRouterState.params.invId); 29 | const lobbyID = nextRouterState.params.invId; 30 | console.log("this is client side socket",socket); 31 | socket.emit('joinGameLobby', lobbyID); 32 | } 33 | 34 | function onPageEnter(store) { 35 | store.dispatch(fetchClients()); 36 | axios.get('/user') 37 | .then(loggedInUser => { 38 | const user = loggedInUser.data 39 | store.dispatch(setUser(user)); 40 | store.dispatch(fetchMatches(user)); 41 | }) 42 | } 43 | 44 | function onGameLobbyEnter(store) { 45 | store.dispatch(fetchClients()); 46 | axios.get('/user') 47 | .then(user => { 48 | store.dispatch(setUser(user.data)) 49 | }); 50 | } 51 | 52 | function onProfileEnter(nextRouterState, store) { 53 | const username = nextRouterState.params.username; 54 | store.dispatch(fetchProfile(username)); 55 | } 56 | 57 | export const createRoutes = (store) => ( 58 | onPageEnter(store)}> 59 | 60 | 61 | 62 | onGameLobbyEnter(store)}/> 63 | onProfileEnter(nextRouterState, store)}/> 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | ) 76 | 77 | export default createRoutes 78 | -------------------------------------------------------------------------------- /src/store/CodeEditorStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /******************************CONSTS***************************************/ 4 | export const UPDATE_CODE = 'UPDATE_CODE'; 5 | export const GET_RESULTS = 'GET_RESULTS'; 6 | export const HANDLE_SUBMIT = 'HANDLE_SUBMIT'; 7 | /******************************ACTIONS***********************************/ 8 | export function updateCode(code) { 9 | return { 10 | type: UPDATE_CODE, 11 | code 12 | } 13 | } 14 | 15 | export function getResults(results) { 16 | return { 17 | type: GET_RESULTS, 18 | results 19 | } 20 | } 21 | 22 | export function handleSubmit() { 23 | console.log('handleSubmit works if this shows your code', this.state.code); 24 | axios.post('/api/code', {code: this.state.code}) 25 | .then(response => { 26 | this.setState({results: response.data}); 27 | console.log("response from running code: ", response.data ); 28 | console.log('saved successfully'); 29 | }) 30 | } 31 | 32 | /*********************************ACTION CREATORS***************************/ 33 | const initialState = { 34 | code: '', 35 | results: '' 36 | } 37 | -------------------------------------------------------------------------------- /src/store/client.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | // ------------------------------------ 3 | // Constants 4 | // ------------------------------------ 5 | export const GET_CLIENTS = 'GET_CLIENTS'; 6 | 7 | // ------------------------------------ 8 | // Actions 9 | // ------------------------------------ 10 | 11 | export function setClients(clients) { 12 | return { 13 | type: GET_CLIENTS, 14 | clients 15 | } 16 | } 17 | 18 | // ------------------------------------ 19 | // Specialized Action Creator 20 | // ------------------------------------ 21 | export const fetchClients = () => { 22 | return (dispatch) => { 23 | axios.get('/api/clients/all') 24 | .then(clients => dispatch(setClients(clients.data))); 25 | } 26 | } 27 | // ------------------------------------ 28 | // Reducer 29 | // ------------------------------------ 30 | const inititalState = {clients: []} 31 | export default function clientsReducer(state = inititalState, action) { 32 | const newState = Object.assign({}, state); 33 | switch(action.type) { 34 | case GET_CLIENTS: 35 | newState.clients = action.clients; 36 | break; 37 | } 38 | 39 | return newState; 40 | } 41 | -------------------------------------------------------------------------------- /src/store/createStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { applyMiddleware, compose, createStore } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | import { browserHistory } from 'react-router'; 5 | import makeRootReducer from './reducers'; 6 | import { updateLocation } from './location'; 7 | import createLogger from 'redux-logger'; 8 | 9 | 10 | export default (initialState = {}) => { 11 | // ====================================================== 12 | // Middleware Configuration 13 | // ====================================================== 14 | const middleware = [thunk, createLogger()] 15 | 16 | // ====================================================== 17 | // Store Enhancers 18 | // ====================================================== 19 | const enhancers = [] 20 | 21 | let composeEnhancers = compose 22 | 23 | if (__DEV__) { 24 | const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 25 | if (typeof composeWithDevToolsExtension === 'function') { 26 | composeEnhancers = composeWithDevToolsExtension 27 | } 28 | } 29 | 30 | // ====================================================== 31 | // Store Instantiation and HMR Setup 32 | // ====================================================== 33 | const store = createStore( 34 | makeRootReducer(), 35 | initialState, 36 | composeEnhancers( 37 | applyMiddleware(...middleware), 38 | ...enhancers 39 | ) 40 | ) 41 | store.asyncReducers = {} 42 | 43 | // To unsubscribe, invoke `store.unsubscribeHistory()` anytime 44 | store.unsubscribeHistory = browserHistory.listen(updateLocation(store)) 45 | 46 | if (module.hot) { 47 | module.hot.accept('./reducers', () => { 48 | const reducers = require('./reducers').default 49 | store.replaceReducer(reducers(store.asyncReducers)) 50 | }) 51 | } 52 | 53 | return store 54 | } 55 | -------------------------------------------------------------------------------- /src/store/gamelobby.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const SET_ROOM_ID = 'SET_ROOM_ID'; 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | 10 | export function setRoomId(roomid) { 11 | return { 12 | type: SET_ROOM_ID, 13 | id: roomid 14 | } 15 | } 16 | 17 | // ------------------------------------ 18 | // Action Handlers 19 | // ------------------------------------ 20 | 21 | // ------------------------------------ 22 | // Reducer 23 | // ------------------------------------ 24 | const initialState = {id: ''}; 25 | 26 | export default function gameLobbyReducer(state = initialState, action) { 27 | const newState = Object.assign({}, state); 28 | 29 | switch(action.type) { 30 | case SET_ROOM_ID: 31 | newState.id = action.id; 32 | break; 33 | } 34 | 35 | return newState; 36 | } 37 | -------------------------------------------------------------------------------- /src/store/location.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const LOCATION_CHANGE = 'LOCATION_CHANGE' 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | export function locationChange (location = '/') { 10 | return { 11 | type : LOCATION_CHANGE, 12 | payload : location 13 | } 14 | } 15 | 16 | // ------------------------------------ 17 | // Specialized Action Creator 18 | // ------------------------------------ 19 | export const updateLocation = ({ dispatch }) => { 20 | return (nextLocation) => dispatch(locationChange(nextLocation)) 21 | } 22 | 23 | // ------------------------------------ 24 | // Reducer 25 | // ------------------------------------ 26 | const initialState = null; 27 | export default function locationReducer (state = initialState, action) { 28 | 29 | switch(action.type) { 30 | case LOCATION_CHANGE: 31 | return {pathname: action.payload}; 32 | break; 33 | } 34 | return state; 35 | } 36 | -------------------------------------------------------------------------------- /src/store/match.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const SET_MATCHES = 'SET_MATCHES'; 4 | 5 | export const setMatches = (matches) => { 6 | return { 7 | type: SET_MATCHES, 8 | matches 9 | } 10 | } 11 | 12 | export const fetchMatches = (user) => { 13 | return (dispatch) => { 14 | const promise = user 15 | ? axios.get(`/api/users/matches/${user.id}`) 16 | : axios.get('/api/users/allMatches') 17 | 18 | return promise.then(matches => dispatch(setMatches(matches.data))); 19 | } 20 | } 21 | 22 | const initialState = { matches: [] }; 23 | export default function matchesReducer(state = initialState, action) { 24 | const newState = Object.assign({}, state); 25 | switch(action.type) { 26 | case SET_MATCHES: 27 | newState.matches = action.matches; 28 | break; 29 | } 30 | 31 | return newState; 32 | } 33 | -------------------------------------------------------------------------------- /src/store/matchresult.js: -------------------------------------------------------------------------------- 1 | export const SET_OPPONENT_ANSWERS = 'SET_OPPONENT_ANSWERS'; 2 | export const SET_OWN_ANSWERS = 'SET_OWN_ANSWERS'; 3 | export const GET_ANSWERS = 'GET_ANSWERS'; 4 | 5 | export const getAnswers = () => { 6 | return { 7 | type: GET_ANSWERS 8 | } 9 | } 10 | export const setOpponentAnswers = (answers) => { 11 | return { 12 | type: SET_OPPONENT_ANSWERS, 13 | answers 14 | } 15 | } 16 | 17 | export const setOwnAnswers = (answers) => { 18 | return { 19 | type: SET_OWN_ANSWERS, 20 | answers 21 | } 22 | } 23 | 24 | const initialState = { opponentAnswers: [], ownAnswers: [] }; 25 | export default function matchResultsReducer(state = initialState, action) { 26 | const newState = Object.assign({}, state); 27 | switch(action.type) { 28 | case SET_OPPONENT_ANSWERS: 29 | newState.opponentAnswers = action.answers; 30 | break; 31 | case SET_OWN_ANSWERS: 32 | newState.ownAnswers = action.answers; 33 | break; 34 | case GET_ANSWERS: 35 | return newState 36 | } 37 | return newState; 38 | } 39 | -------------------------------------------------------------------------------- /src/store/profile.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const SET_PROFILE_USER = 'SET_PROFILE_USER'; 4 | export const SET_PROFILE_MATCHES = 'SET_PROFILE_MATCHES'; 5 | 6 | export function setProfileUser(user) { 7 | return { 8 | type: SET_PROFILE_USER, 9 | user 10 | } 11 | } 12 | 13 | export function setProfileMatches(matches) { 14 | return { 15 | type: SET_PROFILE_MATCHES, 16 | matches 17 | } 18 | } 19 | 20 | export const fetchProfile = (username) => { 21 | return (dispatch) => { 22 | let user; 23 | 24 | axios.get(`/api/users/user/${username}`) 25 | .then(res => res.data) 26 | .then(usr => { 27 | user = usr; 28 | return axios.get(`/api/users/matches/${user.id}`); 29 | }) 30 | .then(result => result.data) 31 | .then(matches => { 32 | dispatch(setProfileUser(user)); 33 | dispatch(setProfileMatches(matches)); 34 | }) 35 | .catch(err => {}) 36 | } 37 | } 38 | 39 | const initialState = { user: {}, matches: [] }; 40 | export default function profilesReducer(state = initialState, action) { 41 | 42 | const newState = Object.assign({}, state); 43 | 44 | switch(action.type) { 45 | case SET_PROFILE_USER: 46 | newState.user = action.user; 47 | break; 48 | case SET_PROFILE_MATCHES: 49 | newState.matches = action.matches; 50 | break; 51 | } 52 | 53 | return newState; 54 | } 55 | -------------------------------------------------------------------------------- /src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import locationReducer from './location' 3 | import clientsReducer from './client' 4 | import gameLobbyReducer from './gamelobby'; 5 | import usersReducer from './user'; 6 | import matchesReducer from './match'; 7 | import profilesReducer from './profile'; 8 | import matchResultsReducer from './matchresult'; 9 | 10 | export const makeRootReducer = (asyncReducers) => { 11 | return combineReducers({ 12 | location: locationReducer, 13 | clients: clientsReducer, 14 | gameLobby: gameLobbyReducer, 15 | user: usersReducer, 16 | match: matchesReducer, 17 | profile: profilesReducer, 18 | matchResults: matchResultsReducer, 19 | ...asyncReducers 20 | }) 21 | } 22 | 23 | export const injectReducer = (store, { key, reducer }) => { 24 | if (Object.hasOwnProperty.call(store.asyncReducers, key)) return 25 | 26 | store.asyncReducers[key] = reducer 27 | store.replaceReducer(makeRootReducer(store.asyncReducers)) 28 | } 29 | 30 | export default makeRootReducer 31 | -------------------------------------------------------------------------------- /src/store/user.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------ 2 | // Constants 3 | // ------------------------------------ 4 | export const SET_USER = 'SET_USER'; 5 | 6 | // ------------------------------------ 7 | // Actions 8 | // ------------------------------------ 9 | 10 | export function setUser(user) { 11 | socket.emit('setUser', { user }); 12 | return { 13 | type: SET_USER, 14 | user 15 | } 16 | } 17 | 18 | // ------------------------------------ 19 | // Specialized Action Creator 20 | // ------------------------------------ 21 | 22 | // ------------------------------------ 23 | // Reducer 24 | // ------------------------------------ 25 | const initialState = {user: {}}; 26 | export default function usersReducer(state = initialState, action) { 27 | const newState = Object.assign({}, state); 28 | switch(action.type) { 29 | case SET_USER: 30 | newState.user = action.user; 31 | break; 32 | } 33 | 34 | return newState; 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/_base.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Application Settings Go Here 3 | ------------------------------------ 4 | This file acts as a bundler for all variables/mixins/themes, so they 5 | can easily be swapped out without `core.scss` ever having to know. 6 | 7 | For example: 8 | 9 | @import './variables/colors'; 10 | @import './variables/components'; 11 | @import './themes/default'; 12 | */ 13 | 14 | @import '../routes/CodeEditor/components/CodeEditor.scss'; 15 | @import '../routes/Signup/Signup.scss'; 16 | @import '../components/Login/Login.scss'; 17 | -------------------------------------------------------------------------------- /src/styles/core.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import '~normalize.css/normalize'; 3 | @import '../../node_modules/codemirror/lib/codemirror.css'; 4 | @import '../../node_modules/codemirror/theme/solarized.css'; 5 | 6 | 7 | 8 | // Some best-practice CSS that's useful for most apps 9 | // Just remove them if they're not what you want 10 | html { 11 | box-sizing: border-box; 12 | } 13 | 14 | html, 15 | body { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | *, 21 | *:before, 22 | *:after { 23 | box-sizing: inherit; 24 | } 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/styles/styles.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6 { 14 | font-family: "Lato","Helvetica Neue",Helvetica,Arial,sans-serif; 15 | font-weight: 700; 16 | } 17 | 18 | .topnav { 19 | font-size: 14px; 20 | } 21 | 22 | .lead { 23 | font-size: 18px; 24 | font-weight: 400; 25 | } 26 | 27 | .intro-header { 28 | padding-top: 50px; /* If you're making other pages, make sure there is 50px of padding to make sure the navbar doesn't overlap content! */ 29 | padding-bottom: 50px; 30 | text-align: center; 31 | color: #f8f8f8; 32 | background-size: cover; 33 | } 34 | 35 | .intro-message { 36 | position: relative; 37 | padding-top: 20%; 38 | padding-bottom: 20%; 39 | } 40 | 41 | .intro-message > h1 { 42 | margin: 0; 43 | text-shadow: 2px 2px 3px rgba(0,0,0,0.6); 44 | font-size: 5em; 45 | } 46 | 47 | .intro-divider { 48 | width: 400px; 49 | border-top: 1px solid #f8f8f8; 50 | border-bottom: 1px solid rgba(0,0,0,0.2); 51 | } 52 | 53 | .intro-message > h3 { 54 | text-shadow: 2px 2px 3px rgba(0,0,0,0.6); 55 | } 56 | 57 | @media(max-width:767px) { 58 | .intro-message { 59 | padding-bottom: 15%; 60 | } 61 | 62 | .intro-message > h1 { 63 | font-size: 3em; 64 | } 65 | 66 | ul.intro-social-buttons > li { 67 | display: block; 68 | margin-bottom: 20px; 69 | padding: 0; 70 | } 71 | 72 | ul.intro-social-buttons > li:last-child { 73 | margin-bottom: 0; 74 | } 75 | 76 | .intro-divider { 77 | width: 100%; 78 | } 79 | } 80 | 81 | .network-name { 82 | text-transform: uppercase; 83 | font-size: 14px; 84 | font-weight: 400; 85 | letter-spacing: 2px; 86 | } 87 | 88 | .content-section-a { 89 | padding: 50px 0; 90 | background-color: #f8f8f8; 91 | } 92 | 93 | .content-section-b { 94 | padding: 50px 0; 95 | border-top: 1px solid #e7e7e7; 96 | border-bottom: 1px solid #e7e7e7; 97 | } 98 | 99 | .section-heading { 100 | margin-bottom: 30px; 101 | } 102 | 103 | .section-heading-spacer { 104 | float: left; 105 | width: 200px; 106 | border-top: 3px solid #e7e7e7; 107 | } 108 | 109 | .banner { 110 | padding: 100px 0; 111 | color: #f8f8f8; 112 | background-size: cover; 113 | } 114 | 115 | .banner h2 { 116 | margin: 0; 117 | text-shadow: 2px 2px 3px rgba(0,0,0,0.6); 118 | font-size: 3em; 119 | } 120 | 121 | .banner ul { 122 | margin-bottom: 0; 123 | } 124 | 125 | .banner-social-buttons { 126 | float: left; 127 | margin-top: 0; 128 | } 129 | 130 | @media(max-width:1199px) { 131 | ul.banner-social-buttons { 132 | float: left; 133 | margin-top: 15px; 134 | } 135 | } 136 | 137 | @media(max-width:767px) { 138 | .banner h2 { 139 | margin: 0; 140 | text-shadow: 2px 2px 3px rgba(0,0,0,0.6); 141 | font-size: 3em; 142 | } 143 | 144 | ul.banner-social-buttons > li { 145 | display: block; 146 | margin-bottom: 20px; 147 | padding: 0; 148 | } 149 | 150 | ul.banner-social-buttons > li:last-child { 151 | margin-bottom: 0; 152 | } 153 | } 154 | 155 | footer { 156 | padding: 50px 0; 157 | background-color: #f8f8f8; 158 | } 159 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : "../.eslintrc", 3 | "env" : { 4 | "mocha" : true 5 | }, 6 | "globals" : { 7 | "expect" : false, 8 | "should" : false, 9 | "sinon" : false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/layouts/CoreLayout.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TestUtils from 'react-addons-test-utils' 3 | import CoreLayout from 'layouts/CoreLayout/CoreLayout' 4 | 5 | function shallowRender (component) { 6 | const renderer = TestUtils.createRenderer() 7 | 8 | renderer.render(component) 9 | return renderer.getRenderOutput() 10 | } 11 | 12 | function shallowRenderWithProps (props = {}) { 13 | return shallowRender() 14 | } 15 | 16 | describe('(Layout) Core', function () { 17 | let _component 18 | let _props 19 | let _child 20 | 21 | beforeEach(function () { 22 | _child =

Child

23 | _props = { 24 | children : _child 25 | } 26 | 27 | _component = shallowRenderWithProps(_props) 28 | }) 29 | 30 | it('Should render as a
.', function () { 31 | expect(_component.type).to.equal('div') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /tests/routes/Counter/components/Counter.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { bindActionCreators } from 'redux' 3 | import { Counter } from 'routes/Counter/components/Counter' 4 | import { shallow } from 'enzyme' 5 | 6 | describe('(Component) Counter', () => { 7 | let _props, _spies, _wrapper 8 | 9 | beforeEach(() => { 10 | _spies = {} 11 | _props = { 12 | counter : 5, 13 | ...bindActionCreators({ 14 | doubleAsync : (_spies.doubleAsync = sinon.spy()), 15 | increment : (_spies.increment = sinon.spy()) 16 | }, _spies.dispatch = sinon.spy()) 17 | } 18 | _wrapper = shallow() 19 | }) 20 | 21 | it('Should render as a
.', () => { 22 | expect(_wrapper.is('div')).to.equal(true) 23 | }) 24 | 25 | it('Should render with an

that includes Sample Counter text.', () => { 26 | expect(_wrapper.find('h2').text()).to.match(/Counter:/) 27 | }) 28 | 29 | it('Should render props.counter at the end of the sample counter

.', () => { 30 | expect(_wrapper.find('h2').text()).to.match(/5$/) 31 | _wrapper.setProps({ counter: 8 }) 32 | expect(_wrapper.find('h2').text()).to.match(/8$/) 33 | }) 34 | 35 | it('Should render exactly two buttons.', () => { 36 | expect(_wrapper.find('button')).to.have.length(2) 37 | }) 38 | 39 | describe('An increment button...', () => { 40 | let _button 41 | 42 | beforeEach(() => { 43 | _button = _wrapper.find('button').filterWhere(a => a.text() === 'Increment') 44 | }) 45 | 46 | it('has bootstrap classes', () => { 47 | expect(_button.hasClass('btn btn-default')).to.be.true 48 | }) 49 | 50 | it('Should dispatch a `increment` action when clicked', () => { 51 | _spies.dispatch.should.have.not.been.called 52 | 53 | _button.simulate('click') 54 | 55 | _spies.dispatch.should.have.been.called 56 | _spies.increment.should.have.been.called 57 | }) 58 | }) 59 | 60 | describe('A Double (Async) button...', () => { 61 | let _button 62 | 63 | beforeEach(() => { 64 | _button = _wrapper.find('button').filterWhere(a => a.text() === 'Double (Async)') 65 | }) 66 | 67 | it('has bootstrap classes', () => { 68 | expect(_button.hasClass('btn btn-default')).to.be.true 69 | }) 70 | 71 | it('Should dispatch a `doubleAsync` action when clicked', () => { 72 | _spies.dispatch.should.have.not.been.called 73 | 74 | _button.simulate('click') 75 | 76 | _spies.dispatch.should.have.been.called 77 | _spies.doubleAsync.should.have.been.called 78 | }) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /tests/routes/Counter/index.spec.js: -------------------------------------------------------------------------------- 1 | import CounterRoute from 'routes/Counter' 2 | 3 | describe('(Route) Counter', () => { 4 | let _route 5 | 6 | beforeEach(() => { 7 | _route = CounterRoute({}) 8 | }) 9 | 10 | it('Should return a route configuration object', () => { 11 | expect(typeof _route).to.equal('object') 12 | }) 13 | 14 | it('Configuration should contain path `counter`', () => { 15 | expect(_route.path).to.equal('counter') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/routes/Counter/modules/counter.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | COUNTER_INCREMENT, 3 | increment, 4 | doubleAsync, 5 | default as counterReducer 6 | } from 'routes/Counter/modules/counter' 7 | 8 | describe('(Redux Module) Counter', () => { 9 | it('Should export a constant COUNTER_INCREMENT.', () => { 10 | expect(COUNTER_INCREMENT).to.equal('COUNTER_INCREMENT') 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(counterReducer).to.be.a('function') 16 | }) 17 | 18 | it('Should initialize with a state of 0 (Number).', () => { 19 | expect(counterReducer(undefined, {})).to.equal(0) 20 | }) 21 | 22 | it('Should return the previous state if an action was not matched.', () => { 23 | let state = counterReducer(undefined, {}) 24 | expect(state).to.equal(0) 25 | state = counterReducer(state, { type: '@@@@@@@' }) 26 | expect(state).to.equal(0) 27 | state = counterReducer(state, increment(5)) 28 | expect(state).to.equal(5) 29 | state = counterReducer(state, { type: '@@@@@@@' }) 30 | expect(state).to.equal(5) 31 | }) 32 | }) 33 | 34 | describe('(Action Creator) increment', () => { 35 | it('Should be exported as a function.', () => { 36 | expect(increment).to.be.a('function') 37 | }) 38 | 39 | it('Should return an action with type "COUNTER_INCREMENT".', () => { 40 | expect(increment()).to.have.property('type', COUNTER_INCREMENT) 41 | }) 42 | 43 | it('Should assign the first argument to the "payload" property.', () => { 44 | expect(increment(5)).to.have.property('payload', 5) 45 | }) 46 | 47 | it('Should default the "payload" property to 1 if not provided.', () => { 48 | expect(increment()).to.have.property('payload', 1) 49 | }) 50 | }) 51 | 52 | describe('(Action Creator) doubleAsync', () => { 53 | let _globalState 54 | let _dispatchSpy 55 | let _getStateSpy 56 | 57 | beforeEach(() => { 58 | _globalState = { 59 | counter : counterReducer(undefined, {}) 60 | } 61 | _dispatchSpy = sinon.spy((action) => { 62 | _globalState = { 63 | ..._globalState, 64 | counter : counterReducer(_globalState.counter, action) 65 | } 66 | }) 67 | _getStateSpy = sinon.spy(() => { 68 | return _globalState 69 | }) 70 | }) 71 | 72 | it('Should be exported as a function.', () => { 73 | expect(doubleAsync).to.be.a('function') 74 | }) 75 | 76 | it('Should return a function (is a thunk).', () => { 77 | expect(doubleAsync()).to.be.a('function') 78 | }) 79 | 80 | it('Should return a promise from that thunk that gets fulfilled.', () => { 81 | return doubleAsync()(_dispatchSpy, _getStateSpy).should.eventually.be.fulfilled 82 | }) 83 | 84 | it('Should call dispatch and getState exactly once.', () => { 85 | return doubleAsync()(_dispatchSpy, _getStateSpy) 86 | .then(() => { 87 | _dispatchSpy.should.have.been.calledOnce 88 | _getStateSpy.should.have.been.calledOnce 89 | }) 90 | }) 91 | 92 | it('Should produce a state that is double the previous state.', () => { 93 | _globalState = { counter: 2 } 94 | 95 | return doubleAsync()(_dispatchSpy, _getStateSpy) 96 | .then(() => { 97 | _dispatchSpy.should.have.been.calledOnce 98 | _getStateSpy.should.have.been.calledOnce 99 | expect(_globalState.counter).to.equal(4) 100 | return doubleAsync()(_dispatchSpy, _getStateSpy) 101 | }) 102 | .then(() => { 103 | _dispatchSpy.should.have.been.calledTwice 104 | _getStateSpy.should.have.been.calledTwice 105 | expect(_globalState.counter).to.equal(8) 106 | }) 107 | }) 108 | }) 109 | 110 | // NOTE: if you have a more complex state, you will probably want to verify 111 | // that you did not mutate the state. In this case our state is just a number 112 | // (which cannot be mutated). 113 | describe('(Action Handler) COUNTER_INCREMENT', () => { 114 | it('Should increment the state by the action payload\'s "value" property.', () => { 115 | let state = counterReducer(undefined, {}) 116 | expect(state).to.equal(0) 117 | state = counterReducer(state, increment(1)) 118 | expect(state).to.equal(1) 119 | state = counterReducer(state, increment(2)) 120 | expect(state).to.equal(3) 121 | state = counterReducer(state, increment(-3)) 122 | expect(state).to.equal(0) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /tests/store/client.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_CLIENTS, 3 | setClients, 4 | fetchClients, 5 | default as clientsReducer 6 | } from 'store/client'; 7 | 8 | describe('(Internal Module) Client', () => { 9 | it('Should export a constant GET_CLIENTS', () => { 10 | expect(GET_CLIENTS).to.equal('GET_CLIENTS'); 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(clientsReducer).to.be.a('function'); 16 | }) 17 | 18 | it('Should initialize clients as an empty array.', () => { 19 | expect(clientsReducer(undefined, {})).to.deep.equal({clients: []}); 20 | }) 21 | 22 | it('Should return the previous state if an action was not matched.', () => { 23 | let state = clientsReducer(undefined, {}); 24 | expect(state).to.deep.equal({clients: []}); 25 | state = clientsReducer(state, {type: 'bbb'}); 26 | expect(state).to.deep.equal({clients: []}); 27 | 28 | const clients = ['a']; 29 | state = clientsReducer(state, setClients(clients)); 30 | expect(state).to.deep.equal({clients}); 31 | state = clientsReducer(state, {type: 'aaa'}); 32 | expect(state).to.deep.equal({clients}); 33 | }) 34 | }) 35 | 36 | describe('(Action Creator) setClients', () => { 37 | it('Should be exported as a function.', () => { 38 | expect(setClients).to.be.a('function'); 39 | }) 40 | 41 | it('Should return an action with type "GET_CLIENTS".', () => { 42 | expect(setClients()).to.have.property('type', GET_CLIENTS); 43 | }) 44 | 45 | it('Should assign the first argument to the "clients" property.', () => { 46 | const clients = ['a']; 47 | expect(setClients(clients)).to.have.property('clients', clients); 48 | }) 49 | }) 50 | 51 | describe('(Specialized Action Creator) fetchClients', () => { 52 | it('Should be exported as a function.', () => { 53 | expect(fetchClients).to.be.a('function'); 54 | }) 55 | 56 | it('Should return a function (is a thunk).', () => { 57 | expect(fetchClients()).to.be.a('function'); 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /tests/store/createStore.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | default as createStore 3 | } from 'store/createStore' 4 | 5 | describe('(Store) createStore', () => { 6 | let store 7 | 8 | before(() => { 9 | store = createStore() 10 | }) 11 | 12 | it('should have an empty asyncReducers object', () => { 13 | expect(store.asyncReducers).to.be.an('object') 14 | expect(store.asyncReducers).to.be.empty 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/store/gamelobby.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_ROOM_ID, 3 | setRoomId, 4 | default as gameLobbyReducer 5 | } from 'store/gamelobby'; 6 | 7 | describe('(Internal Module) GameLobby', () => { 8 | it('Should export a constant SET_ROOM_ID', () => { 9 | expect(SET_ROOM_ID).to.equal('SET_ROOM_ID'); 10 | }) 11 | 12 | describe('(Reducer)', () => { 13 | it('Should be a function', () => { 14 | expect(gameLobbyReducer).to.be.a('function'); 15 | }) 16 | 17 | it('Should initialize id to an empty string', () => { 18 | expect(gameLobbyReducer(undefined, {})).to.deep.equal({id: ''}); 19 | }) 20 | 21 | it('Should return the previous state if an action was not matched.', () => { 22 | let state = gameLobbyReducer(undefined, {}); 23 | expect(state).to.deep.equal({id: ''}); 24 | state = gameLobbyReducer(state, {type: 'bbb'}); 25 | expect(state).to.deep.equal({id: ''}); 26 | 27 | const id = 'Azx24B'; 28 | state = gameLobbyReducer(state, setRoomId(id)); 29 | expect(state).to.deep.equal({id}); 30 | state = gameLobbyReducer(state, {type: 'aaa'}); 31 | expect(state).to.deep.equal({id}); 32 | }) 33 | 34 | describe('(Action Creator) setRoomId', () => { 35 | it('Should be exported as a function.', () => { 36 | expect(setRoomId).to.be.a('function'); 37 | }) 38 | 39 | it('Should return an action with type "SET_ROOM_ID".', () => { 40 | expect(setRoomId()).to.have.property('type', SET_ROOM_ID); 41 | }) 42 | 43 | it('Should assign the first argument to the "id" property.', () => { 44 | const id = 'B23Jsx'; 45 | expect(setRoomId(id)).to.have.property('id', id); 46 | }) 47 | }) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/store/location.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOCATION_CHANGE, 3 | locationChange, 4 | updateLocation, 5 | default as locationReducer 6 | } from 'store/location' 7 | 8 | describe('(Internal Module) Location', () => { 9 | it('Should export a constant LOCATION_CHANGE.', () => { 10 | expect(LOCATION_CHANGE).to.equal('LOCATION_CHANGE') 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function.', () => { 15 | expect(locationReducer).to.be.a('function') 16 | }) 17 | 18 | it('Should initialize with a state of null.', () => { 19 | expect(locationReducer(undefined, {})).to.equal(null) 20 | }) 21 | }) 22 | 23 | describe('(Action Creator) locationChange', () => { 24 | it('Should be exported as a function.', () => { 25 | expect(locationChange).to.be.a('function') 26 | }) 27 | 28 | it('Should return an action with type "LOCATION_CHANGE".', () => { 29 | expect(locationChange()).to.have.property('type', LOCATION_CHANGE) 30 | }) 31 | 32 | it('Should assign the first argument to the "payload" property.', () => { 33 | const locationState = { pathname: '/yup' } 34 | expect(locationChange(locationState)).to.have.property('payload', locationState) 35 | }) 36 | 37 | it('Should default the "payload" property to "/" if not provided.', () => { 38 | expect(locationChange()).to.have.property('payload', '/') 39 | }) 40 | }) 41 | 42 | describe('(Specialized Action Creator) updateLocation', () => { 43 | let _globalState 44 | let _dispatchSpy 45 | 46 | beforeEach(() => { 47 | _globalState = { 48 | location : locationReducer(undefined, {}) 49 | } 50 | _dispatchSpy = sinon.spy((action) => { 51 | _globalState = { 52 | ..._globalState, 53 | location : locationReducer(_globalState.location, action) 54 | } 55 | }) 56 | }) 57 | 58 | it('Should be exported as a function.', () => { 59 | expect(updateLocation).to.be.a('function') 60 | }) 61 | 62 | it('Should return a function (is a thunk).', () => { 63 | expect(updateLocation({ dispatch: _dispatchSpy })).to.be.a('function') 64 | }) 65 | 66 | it('Should call dispatch exactly once.', () => { 67 | updateLocation({ dispatch: _dispatchSpy })('/') 68 | expect(_dispatchSpy.should.have.been.calledOnce) 69 | }) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /tests/store/match.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_MATCHES, 3 | setMatches, 4 | fetchMatches, 5 | default as matchesReducer 6 | } from 'store/match'; 7 | 8 | describe('(Internal Module) Match', () => { 9 | it('Should export a constant SET_MATCHES', () => { 10 | expect(SET_MATCHES).to.equal('SET_MATCHES'); 11 | }) 12 | 13 | describe('(Reducer)', () => { 14 | it('Should be a function', () => { 15 | expect(matchesReducer).to.be.a('function'); 16 | }) 17 | 18 | it('Should initialize matches as an empty array.', () => { 19 | expect(matchesReducer(undefined, {})).to.deep.equal({matches: []}); 20 | }) 21 | 22 | it('Should return the previous state if an action was not matched', () => { 23 | let state = matchesReducer(undefined, {}); 24 | expect(state).to.deep.equal({matches: []}); 25 | state = matchesReducer(state, {type: 'bbb'}); 26 | expect(state).to.deep.equal({matches: []}); 27 | 28 | const matches = ['a']; 29 | state = matchesReducer(state, setMatches(matches)); 30 | expect(state).to.deep.equal({matches}); 31 | state = matchesReducer(state, {type: 'aaa'}); 32 | expect(state).to.deep.equal({matches}); 33 | }) 34 | }) 35 | 36 | describe('(Action Creator) setMatches', () => { 37 | it('Should be exported as a function.', () => { 38 | expect(setMatches).to.be.a('function'); 39 | }) 40 | 41 | it('Should return an action wtih type "SET_MATCHES".', () => { 42 | expect(setMatches()).to.have.property('type', SET_MATCHES); 43 | }) 44 | 45 | it('Should assign the first argument to the "matches" property.', () => { 46 | const matches = ['a']; 47 | expect(setMatches(matches)).to.have.property('matches', matches); 48 | }) 49 | }) 50 | 51 | describe('(Specialized Action Creator) fetchMatches', () => { 52 | it('Should be exported as a function.', () => { 53 | expect(fetchMatches).to.be.a('function'); 54 | }) 55 | 56 | it('Should return a function (is a thunk)', () => { 57 | expect(fetchMatches()).to.be.a('function'); 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /tests/store/matchresult.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_OPPONENT_ANSWERS, 3 | SET_OWN_ANSWERS, 4 | setOpponentAnswers, 5 | setOwnAnswers, 6 | default as matchResultsReducer 7 | } from 'store/matchresult'; 8 | 9 | describe('(Internal Module) Match Result', () => { 10 | it('Should export a constant SET_OPPONENT_ANSWERS', () => { 11 | expect(SET_OPPONENT_ANSWERS).to.equal('SET_OPPONENT_ANSWERS'); 12 | }) 13 | 14 | it('Should export a constant SET_OWN_ANSWERS', () => { 15 | expect(SET_OWN_ANSWERS).to.equal('SET_OWN_ANSWERS'); 16 | }) 17 | 18 | describe('(Reducer)', () => { 19 | it('Should be a function.', () => { 20 | expect(matchResultsReducer).to.be.a('function'); 21 | }) 22 | 23 | it('Should initialize ownAnswers and opponentAnswers as empty arrays.', () => { 24 | expect(matchResultsReducer(undefined, {})).to.deep.equal({ownAnswers: [], opponentAnswers: []}); 25 | }) 26 | 27 | it('Should return the previous state if an action was not matched.', () => { 28 | let state = matchResultsReducer(undefined, {}); 29 | expect(state).to.deep.equal({ownAnswers: [], opponentAnswers: []}); 30 | state = matchResultsReducer(state, {type: 'bbb'}); 31 | expect(state).to.deep.equal({ownAnswers: [], opponentAnswers: []}); 32 | 33 | const ownAnswers = ['a']; 34 | const opponentAnswers = ['b']; 35 | state = matchResultsReducer(state, setOwnAnswers(ownAnswers)); 36 | expect(state).to.deep.equal({opponentAnswers: [], ownAnswers}); 37 | state = matchResultsReducer(state, {type: 'aaa'}); 38 | expect(state).to.deep.equal({opponentAnswers: [], ownAnswers}); 39 | 40 | state = matchResultsReducer(state, setOpponentAnswers(opponentAnswers)); 41 | expect(state).to.deep.equal({opponentAnswers, ownAnswers}); 42 | state = matchResultsReducer(state, {type: 'aaa'}); 43 | expect(state).to.deep.equal({opponentAnswers, ownAnswers}); 44 | }) 45 | }) 46 | 47 | describe('(Action Creator) setOpponentAnswers', () => { 48 | it('Should be exported as a function.', () => { 49 | expect(setOpponentAnswers).to.be.a('function'); 50 | }) 51 | 52 | it('Should return an action with type "SET_OPPONENT_ANSWERS".', () => { 53 | expect(setOpponentAnswers()).to.have.property('type', SET_OPPONENT_ANSWERS); 54 | }) 55 | 56 | it('Should assign the first argument to the "answers" property.', () => { 57 | const answers = ['a']; 58 | expect(setOpponentAnswers(answers)).to.have.property('answers', answers); 59 | }) 60 | }) 61 | 62 | describe('(Action Creator) setOwnAnswers', () => { 63 | it('Should be exported as a function.', () => { 64 | expect(setOwnAnswers).to.be.a('function'); 65 | }) 66 | 67 | it('Should return an action with type "SET_OWN_ANSWERS".', () => { 68 | expect(setOwnAnswers()).to.have.property('type', SET_OWN_ANSWERS); 69 | }) 70 | 71 | it('Should assign the first argument to the "answers" property.', () => { 72 | const answers = ['b']; 73 | expect(setOwnAnswers(answers)).to.have.property('answers', answers); 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /tests/store/profile.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_PROFILE_USER, 3 | SET_PROFILE_MATCHES, 4 | setProfileUser, 5 | setProfileMatches, 6 | default as profilesReducer 7 | } from 'store/profile'; 8 | 9 | describe('(Internal Module) Profile', () => { 10 | it('Should export a constant SET_PROFILE_USER', () => { 11 | expect(SET_PROFILE_USER).to.equal('SET_PROFILE_USER'); 12 | }) 13 | 14 | it('Should export a constant SET_PROFILE_MATCHES', () => { 15 | expect(SET_PROFILE_MATCHES).to.equal('SET_PROFILE_MATCHES'); 16 | }) 17 | 18 | describe('(Reducer)', () => { 19 | it('Should be a function.', () => { 20 | expect(profilesReducer).to.be.a('function'); 21 | }) 22 | 23 | let state = profilesReducer(undefined, {}); 24 | 25 | it('Should initialize user to an empty object', () => { 26 | expect(state).to.deep.equal({user: {}, matches: []}); 27 | }) 28 | 29 | it('Should initialize matches to an empty array', () => { 30 | expect(state).to.deep.equal({user: {}, matches: []}); 31 | }) 32 | 33 | it('Should return the previous state if an action was not matched.', () => { 34 | let state = profilesReducer(undefined, {}); 35 | expect(state).to.deep.equal({user: {}, matches: []}); 36 | state = profilesReducer(state, {type: 'aaa'}); 37 | expect(state).to.deep.equal({user: {}, matches: []}); 38 | 39 | const user = {username: 'test'}; 40 | const matches = ['a', 'b']; 41 | 42 | state = profilesReducer(state, setProfileUser(user)); 43 | expect(state).to.deep.equal({matches: [], user}); 44 | state = profilesReducer(state, {type: 'aaa'}); 45 | expect(state).to.deep.equal({matches: [], user}); 46 | 47 | state = profilesReducer(state, setProfileMatches(matches)); 48 | expect(state).to.deep.equal({user, matches}); 49 | state = profilesReducer(state, {type: 'bbb'}); 50 | expect(state).to.deep.equal({user, matches}); 51 | }) 52 | }) 53 | 54 | describe('(Action Creator) setProfileUser', () => { 55 | it('Should be exported as a function', () => { 56 | expect(setProfileUser).to.be.a('function'); 57 | }) 58 | 59 | it('Should return an action with type "SET_PROFILE_USER"', () => { 60 | expect(setProfileUser()).to.have.property('type', SET_PROFILE_USER); 61 | }) 62 | 63 | it('Should assign the first argument to the "user" property.', () => { 64 | const user = {username: 'test'}; 65 | expect(setProfileUser(user)).to.have.property('user', user); 66 | }) 67 | }) 68 | 69 | describe('(Action Creator) setProfileMatches', () => { 70 | it('Should be exported as a function.', () => { 71 | expect(setProfileMatches).to.be.a('function'); 72 | }) 73 | 74 | it('Should return an action with type "SET_PROFILE_MATCHES"', () => { 75 | expect(setProfileMatches()).to.have.property('type', SET_PROFILE_MATCHES); 76 | }) 77 | 78 | it('Should assign the first argument to the "matches" property.', () => { 79 | const matches = ['a', 'b', 'c']; 80 | expect(setProfileMatches(matches)).to.have.property('matches', matches); 81 | }) 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /tests/store/user.spec.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_USER, 3 | setUser, 4 | default as usersReducer 5 | } from 'store/user'; 6 | 7 | describe('(Internal Module) User', () => { 8 | it('Should export a constant SET_USER', () => { 9 | expect(SET_USER).to.equal('SET_USER'); 10 | }) 11 | 12 | describe('(Reducer)', () => { 13 | it('Should be a function.', () => { 14 | expect(usersReducer).to.be.a('function'); 15 | }) 16 | 17 | it('Should initialize users as an empty object', () => { 18 | expect(usersReducer(undefined, {})).to.deep.equal({user: {}}); 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/test-bundler.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------- 2 | // Test Environment Setup 3 | // --------------------------------------- 4 | import sinon from 'sinon' 5 | import chai from 'chai' 6 | import sinonChai from 'sinon-chai' 7 | import chaiAsPromised from 'chai-as-promised' 8 | import chaiEnzyme from 'chai-enzyme' 9 | 10 | chai.use(sinonChai) 11 | chai.use(chaiAsPromised) 12 | chai.use(chaiEnzyme()) 13 | 14 | global.chai = chai 15 | global.sinon = sinon 16 | global.expect = chai.expect 17 | global.should = chai.should() 18 | 19 | // --------------------------------------- 20 | // Require Tests 21 | // --------------------------------------- 22 | // for use with karma-webpack-with-fast-source-maps 23 | const __karmaWebpackManifest__ = []; // eslint-disable-line 24 | const inManifest = (path) => ~__karmaWebpackManifest__.indexOf(path) 25 | 26 | // require all `tests/**/*.spec.js` 27 | const testsContext = require.context('./', true, /\.spec\.js$/) 28 | 29 | // only run tests that have changed after the first pass. 30 | const testsToRun = testsContext.keys().filter(inManifest) 31 | ;(testsToRun.length ? testsToRun : testsContext.keys()).forEach(testsContext) 32 | 33 | // require all `src/**/*.js` except for `main.js` (for isparta coverage reporting) 34 | if (__COVERAGE__) { 35 | const componentsContext = require.context('../src/', true, /^((?!main|reducers).)*\.js$/) 36 | componentsContext.keys().forEach(componentsContext) 37 | } 38 | --------------------------------------------------------------------------------