diff --git a/common/utilities.js b/common/utilities.js index f24f840..61ab191 100644 --- a/common/utilities.js +++ b/common/utilities.js @@ -18,6 +18,7 @@ let excluded = [ //messages that should not be logged 'idle', 'Combat log sent', + 'News sent', 'Can\'t train while attacking', 'Can\'t untrain while attacking' diff --git a/public/news/2019-05-31-01.md b/public/news/2019-05-31-01.md new file mode 100644 index 0000000..f985f44 --- /dev/null +++ b/public/news/2019-05-31-01.md @@ -0,0 +1,34 @@ +14 Hours Of Refactoring And Debugging +--- +_31 May 2019_ + +What started as a relatively simple task - raising state out of the profile page and into the redux store - turned into a massive overhaul of the entire project. Almost every file has been poured over with a fine toothed come to ensure that it works just right, and that the coding standards are up to scratch with the rest of the project. + +I started working at midnight, which is typical for me lately but I'm still working on it now at 2pm. This was... quite an experience. Also, the mere fact that I'm still excited about this project after this ordeal shows that there is definitely something worth working on here. + +I began working on equipment before the refactoring, and the equipment was passed over during it, simply because I haven't finished it yet. However, now that the profile is stored in redux, I should be able to access information such as the scientist count much more easily. So, look forward to the equipment, coming... tomorrow probably. I'm about to get some sleep. + +```js +{ + "Weapons": { + "Stick": { "cost": 50, "combatBoost": 0.02, "scientistsRequired": 1 }, + "Dagger": { "cost": 75, "combatBoost": 0.03, "scientistsRequired": 2 }, + "Sword": { "cost": 100, "combatBoost": 0.04, "scientistsRequired": 3 }, + "Longsword": { "cost": 150, "combatBoost": 0.05, "scientistsRequired": 4 }, + "Frying Pan": { "cost": 200, "combatBoost": 0.06, "scientistsRequired": 5 } + }, + "Armour": { + "Leather": { "cost": 75, "combatBoost": 0.02, "scientistsRequired": 2 }, + "Gambeson": { "cost": 100, "combatBoost": 0.03, "scientistsRequired": 3 }, + "Chainmail": { "cost": 150, "combatBoost": 0.04, "scientistsRequired": 4 }, + "Platemail": { "cost": 200, "combatBoost": 0.05, "scientistsRequired": 5 } + } +} +``` + +In the mean time, here's the placeholder data for the equipment! I probably shouldn't be showing this, but meh. It's stored as a JSON file, rather than in the database, to make it faster to edit. Player inventories will be stored in the database, however. + +Oh, also, who would like a public-facing API? I'm not sure what for though, but still... + +P.S. The wait time between recruitments has been lowered to 20 hours. + diff --git a/public/styles/shared.css b/public/styles/shared.css index c47781f..f9d7929 100644 --- a/public/styles/shared.css +++ b/public/styles/shared.css @@ -266,4 +266,11 @@ footer { .minWidth { /* hacky */ min-width: 80px; -} \ No newline at end of file +} + +pre { + color: pink; + background-color: #222222; + margin-top: 1em; + margin-bottom: 1em; +} diff --git a/server/accounts.js b/server/accounts.js index c5e029b..ed34016 100644 --- a/server/accounts.js +++ b/server/accounts.js @@ -4,7 +4,7 @@ require('dotenv').config(); //libraries let bcrypt = require('bcrypt'); let formidable = require('formidable'); -let sendmail = require('sendmail')(); +let sendmail = require('sendmail')({silent: true}); //utilities let { log, validateEmail } = require('../common/utilities.js'); @@ -20,7 +20,7 @@ const signupRequest = (connection) => (req, res) => { //prevent too many clicks if (isThrottled(fields.email)) { - res.status(400).write(log('signup throttled', fields.email)); + res.status(400).write(log('Signup throttled', fields.email)); res.end(); return; } @@ -65,11 +65,15 @@ const signupRequest = (connection) => (req, res) => { connection.query(query, [fields.email, fields.username, salt, hash, rand], (err) => { if (err) throw err; + //TODO: make the verification email prettier //build the verification email let addr = `http://${process.env.WEB_ADDRESS}/verifyrequest?email=${fields.email}&verify=${rand}`; let msg = 'Hello! Please visit the following address to verify your account: '; let msgHtml = `

${msg}${addr}

`; + //BUGFIX: is gmail being cruel? + let sentinel = false; + //send the verification email sendmail({ from: `signup@${process.env.WEB_ADDRESS}`, @@ -78,32 +82,39 @@ const signupRequest = (connection) => (req, res) => { text: msg + addr, html: msgHtml }, (err, reply) => { - //final check - if (err) { - res.status(400).write(log('Something went wrong (did you use a valid email?)', err)); - res.end(); - return; - } + if (err) { //final check + let msg = log('Something went wrong (did you use a valid email?)', err); - res.status(200).write(log('Verification email sent!', fields.email)); - res.end(); + if (!sentinel) { + res.status(400).write(msg); + res.end(); + } + } else { + let msg = log('Verification email sent!', fields.email); + + if (!sentinel) { + res.status(200).json({ msg: msg }); + res.end(); + } + } + sentinel = true; }); }); }); }); }); }); -} +}; const verifyRequest = (connection) => (req, res) => { //get the saved data - let query = 'SELECT email, username, salt, hash, verify FROM signups WHERE email = ?;'; - + let query = 'SELECT * FROM signups WHERE email = ?;'; connection.query(query, [req.query.email], (err, results) => { if (err) throw err; //correct number of results - if (results.length != 1) { + if (results.length !== 1) { +console.log(req.query.email); res.status(400).write(log('That account does not exist or this link has already been used.', req.query.email, req.query.verify)); res.end(); return; @@ -126,12 +137,13 @@ const verifyRequest = (connection) => (req, res) => { connection.query(query, [results[0].email], (err) => { if (err) throw err; + //TODO: prettier verification page res.status(200).write(log('Verification succeeded!', req.query.email)); res.end(); }); }); }); -} +}; const loginRequest = (connection) => (req, res) => { //formidable handles forms @@ -155,7 +167,7 @@ const loginRequest = (connection) => (req, res) => { //found this email? if (results.length === 0) { - res.status(400).write(log('Incorrect email or password', fields.email, 'Did not find this email')); + res.status(400).write(log('Incorrect email or password', fields.email, 'Did not find this email')); //NOTE: deliberately obscure incorrect email or password res.end(); return; } @@ -183,25 +195,25 @@ const loginRequest = (connection) => (req, res) => { id: results[0].id, email: fields.email, username: results[0].username, - token: rand + token: rand, + msg: log('Logged in', fields.email, rand) }); res.end(); - log('Logged in', fields.email, rand); }); }); }); }); -} +}; const logoutRequest = (connection) => (req, res) => { - let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?) AND token = ?;'; - connection.query(query, [req.body.email, req.body.token], (err) => { + let query = 'DELETE FROM sessions WHERE sessions.accountId = ? AND token = ?;'; //NOTE: The user now loses this access token + connection.query(query, [req.body.id, req.body.token], (err) => { if (err) throw err; - log('Logged out', req.body.email, req.body.token); + log('Logged out', req.body.id, req.body.token); }); - res.end(); -} + res.end(); //NOTE: don't send a response +}; const passwordChangeRequest = (connection) => (req, res) => { //formidable handles forms @@ -212,23 +224,19 @@ const passwordChangeRequest = (connection) => (req, res) => { if (err) throw err; //validate password, retype - if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) { - res.status(400).write(log('Invalid password change data', fields.email)); + if (fields.password.length < 8 || fields.password !== fields.retype) { + res.status(400).write(log('Invalid password change data', fields.id)); res.end(); return; } //validate token - query = 'SELECT sessions.token FROM sessions WHERE sessions.accountId IN (SELECT id FROM accounts WHERE email = ?);'; - connection.query(query, [fields.email], (err, results) => { + query = 'SELECT COUNT(*) AS total FROM sessions WHERE sessions.accountId = ? AND sessions.token = ?;'; + connection.query(query, [fields.id, fields.token], (err, results) => { if (err) throw err; - let found = false; - - results.map((result) => { if (result.token == fields.token) found = true; }); - - if (!found) { - res.status(400).write(log('Invalid password change authentication', fields.email, fields.token)); + if (results[0].total !== 1) { + res.status(400).write(log('Invalid password change credentials', fields.id, fields.token)); res.end(); return; } @@ -239,26 +247,26 @@ const passwordChangeRequest = (connection) => (req, res) => { bcrypt.hash(fields.password, salt, (err, hash) => { if (err) throw err; - let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE email = ?;'; - connection.query(query, [salt, hash, fields.email], (err) => { + let query = 'UPDATE accounts SET salt = ?, hash = ? WHERE id = ?;'; + connection.query(query, [salt, hash, fields.id], (err) => { if (err) throw err; //clear all session data for this user (a 'feature') - let query = 'DELETE FROM sessions WHERE sessions.accountId IN (SELECT accounts.id FROM accounts WHERE email = ?);'; - connection.query(query, [fields.email], (err) => { + let query = 'DELETE FROM sessions WHERE sessions.accountId = ?;'; + connection.query(query, [fields.id], (err) => { if (err) throw err; //create the new session let rand = Math.floor(Math.random() * 100000); - let query = 'INSERT INTO sessions (accountId, token) VALUES ((SELECT accounts.id FROM accounts WHERE email = ?), ?);'; - connection.query(query, [fields.email, rand], (err) => { + let query = 'INSERT INTO sessions (accountId, token) VALUES (?, ?);'; + connection.query(query, [fields.id, rand], (err) => { if (err) throw err; //send json containing the account info res.status(200).json({ token: rand, - msg: log('Password changed!', fields.email) + msg: log('Password changed!', fields.id) }); res.end(); }); @@ -268,7 +276,7 @@ const passwordChangeRequest = (connection) => (req, res) => { }); }); }); -} +}; const passwordRecoverRequest = (connection) => (req, res) => { //formidable handles forms @@ -280,14 +288,14 @@ const passwordRecoverRequest = (connection) => (req, res) => { //prevent too many clicks if (isThrottled(fields.email)) { - res.status(400).write(log('recover throttled', fields.email)); + res.status(400).write(log('Recover throttled', fields.email)); res.end(); return; } throttle(fields.email); - //validate email, username and password + //validate email if (!validateEmail(fields.email)) { res.status(400).write(log('Invalid recover data', fields.email)); res.end(); @@ -312,11 +320,15 @@ const passwordRecoverRequest = (connection) => (req, res) => { connection.query(query, [results[0].id, rand], (err) => { if (err) throw err; + //TODO: prettier recovery email //build the recovery email let addr = `http://${process.env.WEB_ADDRESS}/passwordreset?email=${fields.email}&token=${rand}`; let msg = 'Hello! Please visit the following address to set a new password (if you didn\'t request a password recovery, ignore this email): '; let msgHtml = `

${msg}${addr}

`; + //BUGFIX: is gmail being cruel? + let sentinel = false; + //send the verification email sendmail({ from: `passwordrecover@${process.env.WEB_ADDRESS}`, @@ -327,18 +339,25 @@ const passwordRecoverRequest = (connection) => (req, res) => { }, (err, reply) => { //final check if (err) { - res.status(400).write(log('Something went wrong (did you use a valid email?)', err)); + if (!sentinel) { + let msg = log('Something went wrong (did you use a valid email?)', err); + + res.status(400).write(msg); + res.end(); + } + } else { + let msg = log('Recovery email sent!', fields.email); + + res.status(200).json({ msg: msg }); res.end(); - return; } - res.status(200).write(log('Recovery email sent!', fields.email)); - res.end(); + sentinel = true; }); }); }); }); -} +}; const passwordResetRequest = (connection) => (req, res) => { //formidable handles forms @@ -348,7 +367,7 @@ const passwordResetRequest = (connection) => (req, res) => { form.parse(req, (err, fields) => { if (err) throw err; - //validate email, username and password + //validate email and password if (!validateEmail(fields.email) || fields.password.length < 8 || fields.password !== fields.retype) { res.status(400).write(log('Invalid reset data (invalid email/password)', fields.email)); res.end(); @@ -383,7 +402,7 @@ const passwordResetRequest = (connection) => (req, res) => { connection.query(query, [fields.email], (err) => { if (err) throw err; - res.status(200).write(log('Password updated!', fields.email)); + res.status(200).json({ msg: log('Password updated!', fields.email) }); res.end(); return; }); @@ -392,7 +411,7 @@ const passwordResetRequest = (connection) => (req, res) => { }); }); }); -} +}; module.exports = { signupRequest: signupRequest, diff --git a/server/combat.js b/server/combat.js index 151eb62..d59c506 100644 --- a/server/combat.js +++ b/server/combat.js @@ -8,35 +8,33 @@ let CronJob = require('cron').CronJob; let { log } = require('../common/utilities.js'); const attackRequest = (connection) => (req, res) => { - //verify the attacker's credentials - let query = 'SELECT accountId FROM sessions WHERE accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;'; - connection.query(query, [req.body.attacker, req.body.token], (err, results) => { + //verify the attacker's credentials (only the attacker can launch an attack) + let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;'; + connection.query(query, [req.body.id, req.body.attacker, req.body.token], (err, results) => { if (err) throw err; - if (results.length !== 1) { - res.status(400).write(log('Invalid attack credentials', req.body.attacker, req.body.defender, req.body.token)); + if (results[0].total !== 1) { + res.status(400).write(log('Invalid attack credentials', req.body.id, req.body.attacker, req.body.defender, req.body.token)); res.end(); return; } - let attackerId = results[0].accountId; - - //verify that the defender exists - let query = 'SELECT id FROM accounts WHERE username = ?;'; + //verify that the defender's profile exists + let query = 'SELECT accountId FROM profiles WHERE accountId IN (SELECT id FROM accounts WHERE username = ?);'; connection.query(query, [req.body.defender], (err, results) => { if (err) throw err; if (results.length !== 1) { - res.status(400).write(log('Invalid defender credentials', req.body.attacker, req.body.defender)); + res.status(400).write(log('Invalid defender credentials', req.body.id, req.body.attacker, req.body.defender, req.body.token)); res.end(); return; } - let defenderId = results[0].id; + let defenderId = results[0].accountId; //verify that the attacker has enough soldiers let query = 'SELECT soldiers FROM profiles WHERE accountId = ?;'; - connection.query(query, [attackerId], (err, results) => { + connection.query(query, [req.body.id], (err, results) => { if (err) throw err; if (results[0].soldiers <= 0) { @@ -52,54 +50,57 @@ const attackRequest = (connection) => (req, res) => { if (err) throw err; if (attacking) { - res.status(400).write(log('You are already attacking someone', req.body.attacker, req.body.defender)); + res.status(400).write(log('You are already attacking someone', req.body.id, req.body.attacker, req.body.token)); res.end(); return; } - //create the pending attack value + //create the pending attack record let query = 'INSERT INTO pendingCombat (eventTime, attackerId, defenderId, attackingUnits) VALUES (DATE_ADD(CURRENT_TIMESTAMP(), INTERVAL 60 * ? SECOND), ?, ?, ?);'; - connection.query(query, [attackingUnits, attackerId, defenderId, attackingUnits], (err) => { + connection.query(query, [attackingUnits, req.body.id, defenderId, attackingUnits], (err) => { if (err) throw err; res.status(200).json({ status: 'attacking', - defender: req.body.defender + attacker: req.body.attacker, + defender: req.body.defender, + msg: log('Attacking', req.body.attacker, req.body.defender) }); res.end(); - - log(`attacking ${req.body.defender}`, req.body.attacker, req.body.defender) }); }); }); }); }); -} +}; -const attackStatusRequest = (connection) => (req, res) => { - isAttacking(connection, req.body.username, (err, attacking, defender) => { +const attackStatusRequest = (connection) => (req, res) => { //TODO: proper credentials + isAttacking(connection, req.body.attacker, (err, attacking, defender) => { if (err) throw err; res.status(200).json({ - status: log(attacking ? 'attacking' : 'idle', req.body.username, defender), - defender: defender + status: attacking ? 'attacking' : 'idle', + attacker: req.body.attacker, + defender: defender, + msg: null }); res.end(); }); -} +}; const combatLogRequest = (connection) => (req, res) => { - let query = 'SELECT pastCombat.*, atk.username AS attackerUsername, def.username AS defenderUsername FROM pastCombat JOIN accounts AS atk ON pastCombat.attackerId = atk.id JOIN accounts AS def ON pastCombat.defenderId = def.id WHERE atk.username = ? OR def.username = ? ORDER BY eventTime DESC LIMIT ?, ?;'; + let query = 'SELECT pastCombat.*, atk.username AS attacker, def.username AS defender FROM pastCombat JOIN accounts AS atk ON pastCombat.attackerId = atk.id JOIN accounts AS def ON pastCombat.defenderId = def.id WHERE atk.username = ? OR def.username = ? ORDER BY eventTime DESC LIMIT ?, ?;'; connection.query(query, [req.body.username, req.body.username, req.body.start, req.body.length], (err, results) => { if (err) throw err; res.status(200).json(results); - log('Combat log sent', req.body.username, req.body.start, req.body.length, JSON.stringify(results)); + log('Combat log sent', req.body.username, req.body.start, req.body.length); }); -} +}; const runCombatTick = (connection) => { + //once per second let combatTick = new CronJob('* * * * * *', () => { //find each pending combat let query = 'SELECT * FROM pendingCombat WHERE eventTime < CURRENT_TIMESTAMP();'; @@ -140,6 +141,7 @@ const runCombatTick = (connection) => { } //determine the victor + //TODO: add equipment effectiveness let rand = Math.random() * (pendingCombat.attackingUnits + defendingUnits * (undefended ? 0.25 : 1)); let victor = rand <= pendingCombat.attackingUnits ? 'attacker' : 'defender'; @@ -147,23 +149,18 @@ const runCombatTick = (connection) => { let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02)); let casualtiesVictor = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits - 10 : 0) * (victor === 'attacker' ? 0.05 : 0.1)); - //NOTE: there is a negative gold bug somewhere - if (spoilsGold <= 0) { - log('WARNING: spoilsGold <= 0', pendingCombat.attackerId, pendingCombat.defenderId, spoilsGold); - } - //save the combat let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);'; connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor], (err) => { if (err) throw err; //update the attacker profile - let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE id = ?;'; + let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE accountId = ?;'; connection.query(query, [spoilsGold, casualtiesVictor, pendingCombat.attackerId], (err) => { if (err) throw err; //update the defender profile - let query = 'UPDATE profiles SET gold = gold - ? WHERE id = ?;'; + let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;'; connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => { if (err) throw err; @@ -185,12 +182,12 @@ const runCombatTick = (connection) => { }); combatTick.start(); -} +}; const isNormalInteger = (str) => { let n = Math.floor(Number(str)); return n !== Infinity && String(n) == str && n >= 0; -} +}; const isAttacking = (connection, user, cb) => { let query; @@ -200,7 +197,7 @@ const isAttacking = (connection, user, cb) => { } else if (typeof(user) === 'string') { query = 'SELECT * FROM pendingCombat WHERE attackerId IN (SELECT id FROM accounts WHERE username = ?);'; } else { - return cb('Unknown argument type for user'); + return cb(`Unknown argument type for user: ${typeof(user)}`); } connection.query(query, [user], (err, results) => { @@ -217,7 +214,7 @@ const isAttacking = (connection, user, cb) => { }); } }); -} +}; module.exports = { attackRequest: attackRequest, @@ -225,4 +222,4 @@ module.exports = { combatLogRequest: combatLogRequest, runCombatTick: runCombatTick, isAttacking: isAttacking -} +}; diff --git a/server/database.js b/server/database.js index 029c27f..614e68f 100644 --- a/server/database.js +++ b/server/database.js @@ -42,8 +42,8 @@ function handleDisconnect() { //finally return connection; -} +}; module.exports = { connectToDatabase: handleDisconnect -} \ No newline at end of file +}; \ No newline at end of file diff --git a/server/equipment.js b/server/equipment.js index 1fcce23..c524033 100644 --- a/server/equipment.js +++ b/server/equipment.js @@ -10,7 +10,9 @@ let equipmentStatistics = require('./equipment_statistics.json'); const statisticsRequest = () => (req, res) => { res.status(200).json(equipmentStatistics); res.end(); -} +}; + +//TODO: incomplete const listRequest = (connection) => (req, res) => { //verify identity @@ -37,7 +39,7 @@ const listRequest = (connection) => (req, res) => { res.end(); }); }); -} +}; module.exports = { statisticsRequest: statisticsRequest, diff --git a/server/equipment_statistics.json b/server/equipment_statistics.json index 8523b4a..18ed71f 100644 --- a/server/equipment_statistics.json +++ b/server/equipment_statistics.json @@ -1,15 +1,15 @@ { "Weapons": { - "Stick": { "cost": 50, "combatBoost": 0.02, "scientists": 1 }, - "Dagger": { "cost": 75, "combatBoost": 0.03, "scientists": 2 }, - "Sword": { "cost": 100, "combatBoost": 0.04, "scientists": 3 }, - "Longsword": { "cost": 150, "combatBoost": 0.05, "scientists": 4 }, - "Frying Pan": { "cost": 200, "combatBoost": 0.06, "scientists": 5 } + "Stick": { "cost": 50, "combatBoost": 0.02, "scientistsRequired": 1 }, + "Dagger": { "cost": 75, "combatBoost": 0.03, "scientistsRequired": 2 }, + "Sword": { "cost": 100, "combatBoost": 0.04, "scientistsRequired": 3 }, + "Longsword": { "cost": 150, "combatBoost": 0.05, "scientistsRequired": 4 }, + "Frying Pan": { "cost": 200, "combatBoost": 0.06, "scientistsRequired": 5 } }, "Armour": { - "Leather": { "cost": 75, "combatBoost": 0.02, "scientists": 2 }, - "Gambeson": { "cost": 100, "combatBoost": 0.03, "scientists": 3 }, - "Chainmail": { "cost": 150, "combatBoost": 0.04, "scientists": 4 }, - "Platemail": { "cost": 200, "combatBoost": 0.05, "scientists": 5 } + "Leather": { "cost": 75, "combatBoost": 0.02, "scientistsRequired": 2 }, + "Gambeson": { "cost": 100, "combatBoost": 0.03, "scientistsRequired": 3 }, + "Chainmail": { "cost": 150, "combatBoost": 0.04, "scientistsRequired": 4 }, + "Platemail": { "cost": 200, "combatBoost": 0.05, "scientistsRequired": 5 } } } \ No newline at end of file diff --git a/server/index.js b/server/index.js index 896bb97..3494d92 100644 --- a/server/index.js +++ b/server/index.js @@ -15,6 +15,7 @@ app.use(bodyParser.json()); //handle the news request let news = require('./news.js'); +app.get('/newsrequest', news.newsRequest()); app.post('/newsrequest', news.newsRequest()); //database @@ -46,9 +47,9 @@ app.post('/attackstatusrequest', combat.attackStatusRequest(connection)); app.post('/combatlogrequest', combat.combatLogRequest(connection)); combat.runCombatTick(connection); -let equipment = require('./equipment.js'); -app.post('/equipmentstatisticsrequest', equipment.statisticsRequest()); -app.post('/equipmentlistrequest', equipment.listRequest(connection)); +//let equipment = require('./equipment.js'); +//app.post('/equipmentstatisticsrequest', equipment.statisticsRequest()); +//app.post('/equipmentlistrequest', equipment.listRequest(connection)); //static directories app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) ); diff --git a/server/news.js b/server/news.js index 4442d85..138d55a 100644 --- a/server/news.js +++ b/server/news.js @@ -5,18 +5,20 @@ require('dotenv').config(); let fs = require('fs'); let path = require('path'); +let { log } = require('../common/utilities.js'); + const newsRequest = () => (req, res) => { let fpath = path.join(__dirname, '..', 'public', 'news'); let fileNames = fs.readdirSync(fpath); //set the maximum - let max = parseInt(req.body.max); - if (max > fileNames.length) { + let max = parseInt(req.body.length) || 99; + if (isNaN(max) || max > fileNames.length) { max = fileNames.length; } //build the object to send - let json = {} //TODO: caching + let json = {}; //TODO: caching //send each file as json for (let i = 0; i < max; i++) { @@ -26,8 +28,10 @@ const newsRequest = () => (req, res) => { //actually send the data res.json(json); res.end(); -} + + log('News sent', max, fileNames, JSON.stringify(json)); +}; module.exports = { newsRequest: newsRequest -} \ No newline at end of file +}; \ No newline at end of file diff --git a/server/profiles.js b/server/profiles.js index 8a58dd6..54da52a 100644 --- a/server/profiles.js +++ b/server/profiles.js @@ -2,7 +2,6 @@ require('dotenv').config(); //libraries -let formidable = require('formidable'); let CronJob = require('cron').CronJob; let { isAttacking } = require('./combat.js'); @@ -12,87 +11,73 @@ let { log } = require('../common/utilities.js'); //profile creation & requesting const profileCreateRequest = (connection) => (req, res) => { - //formidable handles forms - let form = formidable.IncomingForm(); + //separate this section so it can be used elsewhere too + return profileCreateRequestInner(connection, req, res, req.body); +}; - //parse form TODO: form? That was a bad idea - form.parse(req, (err, fields) => { - if (err) throw err; - - //separate this section so it can be used elsewhere too - return profileCreateRequestInner(connection, req, res, fields); - }); -} - -function profileCreateRequestInner(connection, req, res, fields) { +function profileCreateRequestInner(connection, req, res, body) { let query = 'SELECT accountId FROM profiles WHERE accountId IN (SELECT accounts.id FROM accounts WHERE username = ?);'; - connection.query(query, [fields.username], (err, results) => { + connection.query(query, [body.username], (err, results) => { if (err) throw err; if (results.length === 1) { - res.status(400).write(log('That profile already exists', fields.username)); + res.status(400).write(log('That profile already exists', body.username)); res.end(); return; } - //check ID, username and token match + //check ID, username and token match (only the profile's owner can create it) let query = 'SELECT accountId FROM sessions WHERE accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;'; - connection.query(query, [fields.username, fields.token], (err, results) => { + connection.query(query, [body.username, body.token], (err, results) => { if (err) throw err; - if (results.length !== 1 || results[0].accountId != fields.id) { - res.status(400).write(log('Invalid profile creation credentials', fields.username, fields.id, fields.token)); + if (results.length !== 1 || results[0].accountId != body.id) { + res.status(400).write(log('Invalid profile creation credentials', body.username, body.id, body.token)); res.end(); return; } + //create the profile let query = 'INSERT INTO profiles (accountId) SELECT accounts.id FROM accounts WHERE username = ?;'; - connection.query(query, [fields.username], (err) => { + connection.query(query, [body.username], (err) => { if (err) throw err; - log('Profile created', fields.username, fields.id, fields.token); + log('Profile created', body.username, body.id, body.token); - return profileRequestInner(connection, req, res, fields); + return profileRequestInner(connection, req, res, body); }); }); }); -} - -const profileRequest = (connection) => (req, res) => { - //formidable handles forms - let form = formidable.IncomingForm(); - - //parse form - form.parse(req, (err, fields) => { - if (err) throw err; - - //separate this section so it can be used elsewhere too - return profileRequestInner(connection, req, res, fields); - }); }; -function profileRequestInner(connection, req, res, fields) { +const profileRequest = (connection) => (req, res) => { + //separate this section so it can be used elsewhere too + return profileRequestInner(connection, req, res, req.body); +}; + +function profileRequestInner(connection, req, res, body) { + //find the profile let query = 'SELECT * FROM profiles WHERE accountId IN (SELECT accounts.id FROM accounts WHERE username = ?);'; - connection.query(query, [fields.username], (err, results) => { + connection.query(query, [body.username], (err, results) => { if (err) throw err; if (results.length !== 1) { //pass it off to the profile creation process, IF the user is requesting their own profile let query = 'SELECT id FROM accounts WHERE id = ? AND id IN (SELECT accountId FROM sessions WHERE token = ?);'; - connection.query(query, [fields.id, fields.token], (err, results) => { + connection.query(query, [body.id, body.token], (err, results) => { if (err) throw err; if (results.length === 1) { - return profileCreateRequestInner(connection, req, res, fields); + return profileCreateRequestInner(connection, req, res, body); } else { - res.status(400).write(log('Profile not found', fields.username, fields.id, fields.token)); + res.status(400).write(log('Profile not found', body.username, body.id, body.token)); res.end(); } }); } else { //results.length === 1 res.status(200).json({ - username: fields.username, + username: body.username, gold: results[0].gold, recruits: results[0].recruits, soldiers: results[0].soldiers, @@ -100,71 +85,161 @@ function profileRequestInner(connection, req, res, fields) { scientists: results[0].scientists }); res.end(); - log('Profile sent', fields.username, fields.id, fields.token); + log('Profile sent', body.username, body.id, body.token); } }); -} +}; //actual actions to be taken const recruitRequest = (connection) => (req, res) => { - //formidable handles forms - let form = formidable.IncomingForm(); - - //parse form - form.parse(req, (err, fields) => { + //verify the credentials + let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;'; + connection.query(query, [req.body.id, req.body.token], (err, results) => { if (err) throw err; - //verify the credentials - let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; - connection.query(query, [fields.id, fields.token], (err, results) => { + if (results[0].total !== 1) { + res.status(400).write(log('Invalid recruit credentials - 1', req.body.id, req.body.token)); + res.end(); + return; + } + + //verify enough time has passed since the last successful recruit action + let query = 'SELECT TIMESTAMPDIFF(HOUR, (SELECT lastRecruitTime FROM profiles WHERE accountId = ?), CURRENT_TIMESTAMP());'; + connection.query(query, [req.body.id], (err, results) => { if (err) throw err; if (results.length !== 1) { - res.status(400).write(log('Invalid recruit credentials', fields.username, fields.id, fields.token)); + res.status(400).write(log('Invalid database state', req.body.id, req.body.token)); res.end(); return; } - //verify enough time has passed since the last successful recruit action - let query = 'SELECT TIMESTAMPDIFF(HOUR, (SELECT lastRecruitTime FROM profiles WHERE accountId = ?), CURRENT_TIMESTAMP());'; - connection.query(query, [fields.id], (err, results) => { + let timespans = results[0][Object.keys(results[0])]; + + //not enough time has passed + if (timespans < 20) { + res.status(400).write(log('Not enough time has passed', req.body.id, req.body.token)); + res.end(); + return; + } + + //update the profile with the new data (gaining 1 recruit) + let query = 'UPDATE profiles SET recruits = recruits + 1, lastRecruitTime = CURRENT_TIMESTAMP() WHERE accountId = ?;'; + connection.query(query, [req.body.id], (err) => { if (err) throw err; - if (results.length !== 1) { - res.status(400).write(log('Invalid database state', fields.username, fields.id, fields.token)); + //send the new profile data as JSON + let query = 'SELECT username, profiles.* FROM profiles JOIN accounts ON accounts.id = profiles.accountId WHERE accounts.id = ?;'; + connection.query(query, [req.body.id], (err, results) => { + if (err) throw err; + + //check just in case + if (results.length !== 1) { + res.status(400).write(log('Invalid recruit credentials - 2', req.body.id, req.body.token)); + res.end(); + return; + } + + //results.length === 1 + res.status(200).json({ + username: results[0].username, + gold: results[0].gold, + recruits: results[0].recruits, + soldiers: results[0].soldiers, + spies: results[0].spies, + scientists: results[0].scientists + }); + res.end(); + + log('Recruit successful', results[0].username, req.body.id, req.body.token); + }); + }); + }); + }); +}; + +const trainRequest = (connection) => (req, res) => { + //verify the credentials (NOTE: duplication) + let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;'; + connection.query(query, [req.body.id, req.body.token], (err, results) => { + if (err) throw err; + + if (results[0].total !== 1) { + res.status(400).write(log('Invalid train credentials - 1', req.body.id, req.body.token)); + res.end(); + return; + } + + //verify the role argument + if (req.body.role !== 'soldier' && req.body.role !== 'spy' && req.body.role !== 'scientist') { + res.status(400).write(log('Invalid train parameters', req.body.role, req.body.id, req.body.token)); + res.end(); + return; + } + + //can't train while attacking + isAttacking(connection, req.body.id, (err, attacking) => { + if (err) throw err; + + if (attacking) { + res.status(400).write(log('Can\'t train while attacking', req.body.id)); + res.end(); + return; + } + + //determine the cost of the training TODO: make these global for the client too + let cost = 0; + switch(req.body.role) { + case 'soldier': + cost = 100; + break; + + case 'spy': + cost = 200; + break; + + case 'scientist': + cost = 120; + break; + } + + //verify that the user has a high enough gold and recruit balance + let query = 'SELECT recruits, gold FROM profiles WHERE accountId = ?;'; + connection.query(query, [req.body.id], (err, results) => { + if (err) throw err; + + if (results[0].recruits <= 0) { + res.status(400).write(log('Not enough recruits', results[0].recruits, req.body.id, req.body.token)); res.end(); return; } - let timespans = results[0][Object.keys(results[0])]; - - //not enough time has passed - if (timespans < 22) { - res.status(400).write(log('Not enough time has passed', fields.username, fields.id, fields.token)); + if (results[0].gold < cost) { + res.status(400).write(log('Not enough gold', results[0].gold, req.body.id, req.body.token)); res.end(); return; } - //update the profile with the new data (gaining 1 recruit) - let query = 'UPDATE profiles SET recruits = recruits + 1, lastRecruitTime = CURRENT_TIMESTAMP() WHERE accountId = ?;'; - connection.query(query, [fields.id], (err) => { + //update the profile with new values (NOTE: extra protection for network latency) + let query = 'UPDATE profiles SET gold = gold - ?, recruits = recruits - 1, soldiers = soldiers + ?, spies = spies + ?, scientists = scientists + ? WHERE accountId = ? AND gold >= ? AND recruits > 0;'; + connection.query(query, [cost, req.body.role === 'soldier' ? 1 : 0, req.body.role === 'spy' ? 1 : 0, req.body.role === 'scientist' ? 1 : 0, req.body.id, cost], (err) => { if (err) throw err; //send the new profile data as JSON (NOTE: possible duplication) - let query = 'SELECT * FROM profiles WHERE accountId = ?;'; - connection.query(query, [fields.id], (err, results) => { + let query = 'SELECT username, profiles.* FROM profiles JOIN accounts ON accounts.id = profiles.accountId WHERE accounts.id = ?;'; + connection.query(query, [req.body.id], (err, results) => { if (err) throw err; //check just in case if (results.length !== 1) { - res.status(400).write(log('Invalid recruit credentials', fields.username, fields.id, fields.token)); + res.status(400).write(log('Invalid recruit credentials - 2', req.body.id, req.body.token)); res.end(); return; } //results.length === 1 res.status(200).json({ - username: fields.username, //TODO: join here + username: results[0].username, gold: results[0].gold, recruits: results[0].recruits, soldiers: results[0].soldiers, @@ -172,227 +247,115 @@ const recruitRequest = (connection) => (req, res) => { scientists: results[0].scientists }); res.end(); - log('Recruit successful', fields.username, fields.id, fields.token); + log('Train executed', results[0].username, req.body.role, req.body.id, req.body.token); }); }); }); }); }); -} - -const trainRequest = (connection) => (req, res) => { - //formidable handles forms - let form = formidable.IncomingForm(); - - //parse form - form.parse(req, (err, fields) => { - if (err) throw err; - - //verify the credentials (NOTE: duplication) - let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; - connection.query(query, [fields.id, fields.token], (err, results) => { - if (err) throw err; - - if (results.length !== 1) { - res.status(400).write(log('Invalid train credentials', fields.username, fields.id, fields.token)); - res.end(); - return; - } - - //verify the role argument - if (fields.role !== 'soldier' && fields.role !== 'spy' && fields.role !== 'scientist') { - res.status(400).write(log('Invalid train parameters', fields.username, fields.role, fields.id, fields.token)); - res.end(); - return; - } - - //can't train while attacking - isAttacking(connection, fields.id, (err, attacking) => { - if (err) throw err; - - if (attacking) { - res.status(400).write(log('Can\'t train while attacking', fields.id)); - res.end(); - return; - } - - //determine the cost of the training TODO: make these global for the client too - let cost = 0; - switch(fields.role) { - case 'soldier': - cost = 100; - break; - - case 'spy': - cost = 200; - break; - - case 'scientist': - cost = 120; - break; - } - - //verify that the user has a high enough gold and recruit balance - let query = 'SELECT recruits, gold FROM profiles WHERE accountId = ?;'; - connection.query(query, [fields.id], (err, results) => { - if (err) throw err; - - if (results[0].recruits <= 0) { - res.status(400).write(log('Not enough recruits', fields.username, results[0].recruits, fields.id, fields.token)); - res.end(); - return; - } - - if (results[0].gold < cost) { - res.status(400).write(log('Not enough gold', fields.username, results[0].gold, fields.id, fields.token)); - res.end(); - return; - } - - //update the profile with new values - let query = 'UPDATE profiles SET gold = gold - ?, recruits = recruits - 1, soldiers = soldiers + ?, spies = spies + ?, scientists = scientists + ? WHERE accountId = ? AND gold >= ? AND recruits > 0;'; - connection.query(query, [cost, fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id, cost], (err) => { - if (err) throw err; - - //send the new profile data as JSON (NOTE: possible duplication) - let query = 'SELECT * FROM profiles WHERE accountId = ?;'; - connection.query(query, [fields.id], (err, results) => { - if (err) throw err; - - //check just in case - if (results.length !== 1) { - res.status(400).write(log('Invalid recruit credentials', fields.username, fields.id, fields.token)); - res.end(); - return; - } - - //results.length === 1 - res.status(200).json({ - username: fields.username, //TODO: join here - gold: results[0].gold, - recruits: results[0].recruits, - soldiers: results[0].soldiers, - spies: results[0].spies, - scientists: results[0].scientists - }); - res.end(); - log('Train executed', fields.username, fields.role, fields.id, fields.token); - }); - }); - }); - }); - }); - }); -} +}; const untrainRequest = (connection) => (req, res) => { - //formidable handles forms - let form = formidable.IncomingForm(); - - //parse form - form.parse(req, (err, fields) => { + //verify the credentials (NOTE: duplication) + let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; + connection.query(query, [req.body.id, req.body.token], (err, results) => { if (err) throw err; - //verify the credentials (NOTE: duplication) - let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; - connection.query(query, [fields.id, fields.token], (err, results) => { + if (results.length !== 1) { + res.status(400).write(log('Invalid untrain credentials - 1', req.body.role, req.body.id, req.body.token)); + res.end(); + return; + } + + //verify the role argument + if (req.body.role !== 'soldier' && req.body.role !== 'spy' && req.body.role !== 'scientist') { + res.status(400).write(log('Invalid untrain parameters', req.body.role, req.body.id, req.body.token)); + res.end(); + return; + } + + //can't untrain while attacking + isAttacking(connection, req.body.id, (err, attacking) => { if (err) throw err; - - if (results.length !== 1) { - res.status(400).write(log('Invalid untrain credentials', fields.username, fields.role, fields.id, fields.token)); - res.end(); - return; - } - - //verify the role argument - if (fields.role !== 'soldier' && fields.role !== 'spy' && fields.role !== 'scientist') { - res.status(400).write(log('Invalid untrain parameters', fields.username, fields.role, fields.id, fields.token)); - res.end(); - return; - } - - //can't untrain while attacking - isAttacking(connection, fields.id, (err, attacking) => { - if (err) throw err; - if (attacking) { - res.status(400).write(log('Can\'t untrain while attacking', fields.id)); + if (attacking) { + res.status(400).write(log('Can\'t untrain while attacking', req.body.id, req.body.token)); + res.end(); + return; + } + + //verify that the user has a high enough balance + let query = 'SELECT soldiers, spies, scientists FROM profiles WHERE accountId = ?;'; + connection.query(query, [req.body.id], (err, results) => { + if (err) throw err; + + if (req.body.role === 'soldier' && results[0].soldiers <= 0) { + res.status(400).write(log('Not enough soldiers', results[0].soldiers, req.body.id, req.body.token)); res.end(); return; } - //verify that the user has a high enough balance - let query = 'SELECT soldiers, spies, scientists FROM profiles WHERE accountId = ?;'; - connection.query(query, [fields.id], (err, results) => { + if (req.body.role === 'spy' && results[0].spies <= 0) { + res.status(400).write(log('Not enough spies', results[0].spies, req.body.id, req.body.token)); + res.end(); + return; + } + + if (req.body.role === 'scientist' && results[0].scientists <= 0) { + res.status(400).write(log('Not enough scientists', results[0].scientists, req.body.id, req.body.token)); + res.end(); + return; + } + + //hacky + let roleName = null; + + if (req.body.role === 'soldier') { + roleName = 'soldiers'; + } else if (req.body.role === 'spy') { + roleName = 'spies'; + } else if (req.body.role === 'scientist') { + roleName = 'scientists'; + } else { + res.status(400).write(log('Unknown role received', req.body.role, req.body.id, req.body.token)); + res.end(); + return; + } + + //update the profile with new values (NOTE: extra protection for network latency) + let query = `UPDATE profiles SET recruits = recruits + 1, soldiers = soldiers - ?, spies = spies - ?, scientists = scientists - ? WHERE accountId = ? AND ${roleName} > 0;`; + connection.query(query, [roleName === 'soldiers' ? 1 : 0, roleName === 'spies' ? 1 : 0, roleName === 'scientists' ? 1 : 0, req.body.id], (err) => { if (err) throw err; - if (fields.role === 'soldier' && results[0].soldiers <= 0) { - res.status(400).write(log('Not enough soldiers', fields.username, results[0].soldiers, fields.id, fields.token)); - res.end(); - return; - } - - if (fields.role === 'spy' && results[0].spies <= 0) { - res.status(400).write(log('Not enough spies', fields.username, results[0].spies, fields.id, fields.token)); - res.end(); - return; - } - - if (fields.role === 'scientist' && results[0].scientists <= 0) { - res.status(400).write(log('Not enough scientists', fields.username, results[0].scientists, fields.id, fields.token)); - res.end(); - return; - } - - //hacky - let role = null; - if (fields.role === 'soldier') { - role = 'soldiers'; - } else if (fields.role === 'spy') { - role = 'spies'; - } else if (fields.role === 'scientist') { - role = 'scientists'; - } else { - res.status(400).write(log('Unknown role found', fields.role)); - res.end(); - return; - } - - //update the profile with new values - let query = `UPDATE profiles SET recruits = recruits + 1, soldiers = soldiers - ?, spies = spies - ?, scientists = scientists - ? WHERE accountId = ? AND ${role} > 0;`; - connection.query(query, [fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id], (err) => { + //send the new profile data as JSON (NOTE: possible duplication) + let query = 'SELECT username, profiles.* FROM profiles JOIN accounts ON accounts.id = profiles.accountId WHERE accounts.id = ?;'; + connection.query(query, [req.body.id], (err, results) => { if (err) throw err; - //send the new profile data as JSON (NOTE: possible duplication) - let query = 'SELECT * FROM profiles WHERE accountId = ?;'; - connection.query(query, [fields.id], (err, results) => { - if (err) throw err; - - //check just in case - if (results.length !== 1) { - res.status(400).write(log('Invalid untrain credentials', fields.username, fields.role, fields.id, fields.token)); - res.end(); - return; - } - - //results.length === 1 - res.status(200).json({ - username: fields.username, //TODO: join here - gold: results[0].gold, - recruits: results[0].recruits, - soldiers: results[0].soldiers, - spies: results[0].spies, - scientists: results[0].scientists - }); + //check just in case + if (results.length !== 1) { + res.status(400).write(log('Invalid untrain credentials - 2', req.body.role, req.body.id, req.body.token)); res.end(); - log('Untrain executed', fields.username, fields.role, fields.id, fields.token); + return; + } + + //results.length === 1 + res.status(200).json({ + username: results[0].username, + gold: results[0].gold, + recruits: results[0].recruits, + soldiers: results[0].soldiers, + spies: results[0].spies, + scientists: results[0].scientists }); + res.end(); + log('Untrain executed', results[0].username, roleName, req.body.id, req.body.token); }); }); }); }); }); -} +}; const ladderRequest = (connection) => (req, res) => { let query = 'SELECT username, soldiers, recruits, gold FROM accounts JOIN profiles ON accounts.id = profiles.accountId ORDER BY soldiers DESC, recruits DESC, gold DESC LIMIT ?, ?;'; @@ -402,7 +365,7 @@ const ladderRequest = (connection) => (req, res) => { res.status(200).json(results); log('Ladder sent', req.body.start, req.body.length, results); }); -} +}; const runGoldTick = (connection) => { let goldTickJob = new CronJob('0 */30 * * * *', () => { @@ -415,7 +378,7 @@ const runGoldTick = (connection) => { }); goldTickJob.start(); -} +}; module.exports = { // profileCreate: profileCreate, //NOTE: Not actually used @@ -425,4 +388,4 @@ module.exports = { untrainRequest: untrainRequest, ladderRequest: ladderRequest, runGoldTick: runGoldTick -} \ No newline at end of file +}; \ No newline at end of file diff --git a/sql/drop_everything.sql b/sql/drop_everything.sql index ab5e2ab..a1e4daa 100644 --- a/sql/drop_everything.sql +++ b/sql/drop_everything.sql @@ -1,3 +1,6 @@ +DROP TABLE equipment; +DROP TABLE pastCombat; +DROP TABLE pendingCombat; DROP TABLE profiles; DROP TABLE passwordRecover; DROP TABLE sessions; diff --git a/src/actions/accounts.js b/src/actions/account.js similarity index 52% rename from src/actions/accounts.js rename to src/actions/account.js index 7032105..fc4ab4d 100644 --- a/src/actions/accounts.js +++ b/src/actions/account.js @@ -1,8 +1,8 @@ export const LOGIN = 'LOGIN'; export const LOGOUT = 'LOGOUT'; -export const SESSIONCHANGE = 'SESSIONCHANGE'; +export const SESSION_CHANGE = 'SESSION_CHANGE'; -export function login(id, email, username, token) { +export const login = (id, email, username, token) => { return { type: LOGIN, id: id, @@ -12,15 +12,15 @@ export function login(id, email, username, token) { }; } -export function logout() { +export const logout = () => { return { type: LOGOUT }; } -export function sessionChange(token) { +export const sessionChange = (token) => { return { - type: SESSIONCHANGE, + type: SESSION_CHANGE, token: token }; } \ No newline at end of file diff --git a/src/actions/combat.js b/src/actions/combat.js deleted file mode 100644 index 1906331..0000000 --- a/src/actions/combat.js +++ /dev/null @@ -1,8 +0,0 @@ -export const SET_ATTACK_DISABLED = 'SET_ATTACK_DISABLED'; - -export function setAttackDisabled(disabled) { - return { - type: SET_ATTACK_DISABLED, - disabled: disabled - } -} \ No newline at end of file diff --git a/src/actions/profile.js b/src/actions/profile.js new file mode 100644 index 0000000..9fc969c --- /dev/null +++ b/src/actions/profile.js @@ -0,0 +1,61 @@ +export const STORE_PROFILE = 'STORE_PROFILE'; +export const STORE_USERNAME = 'STORE_USERNAME'; +export const STORE_GOLD = 'STORE_GOLD'; +export const STORE_RECRUITS = 'STORE_RECRUITS'; +export const STORE_SOLDIERS = 'STORE_SOLDIERS'; +export const STORE_SPIES = 'STORE_SPIES'; +export const STORE_SCIENTISTS = 'STORE_SCIENTISTS'; + +export const storeProfile = (username, gold, recruits, soldiers, spies, scientists) => { + return { + type: STORE_PROFILE, + username: username, + gold: gold, + recruits: recruits, + soldiers: soldiers, + spies: spies, + scientists: scientists + }; +} + +export const storeUsername = (username) => { + return { + type: STORE_USERNAME, + username: username + }; +} + +export const storeGold = (gold) => { + return { + type: STORE_GOLD, + gold: gold + }; +} + +export const storeRecruits = (recruits) => { + return { + type: STORE_RECRUITS, + recruits: recruits + }; +} + +export const storeSoldiers = (soldiers) => { + return { + type: STORE_SOLDIERS, + soldiers: soldiers + }; +} + +export const storeSpies = (spies) => { + return { + type: STORE_SPIES, + spies: spies + }; +} + +export const storeScientists = (scientists) => { + return { + type: STORE_SCIENTISTS, + scientists: scientists + }; +} \ No newline at end of file diff --git a/src/components/app.jsx b/src/components/app.jsx index d97c641..4c82b4e 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -59,6 +59,7 @@ export default class App extends React.Component { { GA.init() && } import('./pages/home.jsx')} /> + import('./pages/signup.jsx')} /> import('./pages/login.jsx')} /> import('./pages/password_change.jsx')} /> @@ -67,6 +68,7 @@ export default class App extends React.Component { import('./pages/profile.jsx')} /> import('./pages/ladder.jsx')} /> + import('./pages/combat_log.jsx')} /> import('./pages/page_not_found.jsx')} /> diff --git a/src/components/pages/combat_log.jsx b/src/components/pages/combat_log.jsx new file mode 100644 index 0000000..ccec432 --- /dev/null +++ b/src/components/pages/combat_log.jsx @@ -0,0 +1,147 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import queryString from 'query-string'; +import PropTypes from 'prop-types'; + +//panels +import CommonLinks from '../panels/common_links.jsx'; +import PagedCombatLog from '../panels/paged_combat_log.jsx'; + +class CombatLog extends React.Component { + constructor(props) { + super(props); + + let params = queryString.parse(props.location.search); + + this.state = { + params: params, + start: parseInt(params.log) || 0, + length: parseInt(params.length) || 20, + + fetch: null, + + warning: '' + }; + } + + componentDidMount() { + if (!this.props.loggedIn) { + this.props.history.replace('/login'); + } + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (JSON.stringify(this.state) !== JSON.stringify(prevState)) { + this.state.fetch(); + } + } + + render() { + let warningStyle = { + display: this.state.warning.length > 0 ? 'flex' : 'none' + }; + + let ButtonHeader = this.buttonHeader.bind(this); + + return ( +
+
+
+ +
+ +
+
+

{this.state.warning}

+
+ + + + +
+
+
+ + ); + } + + buttonHeader() { + return ( +
+
+ +
+
+ +
+
+ ); + } + + increment() { + let start = this.state.start + this.state.length; + + this.props.history.push(`${this.props.location.pathname}?log=${start}`); + } + + decrement() { + let start = Math.max(0, this.state.start - this.state.length); + + //don't decrement too far + if (start === this.state.start) { + return; + } + + this.props.history.push(`${this.props.location.pathname}?log=${start}`); + } + + //bound callbacks + getFetch(fn) { + this.setState({ fetch: fn }); + } + + onReceived(data) { + if (data.length === 0) { + let start = Math.max(0, this.state.start - this.state.length); + + //don't decrement too far + if (start === this.state.start) { + return; + } + + this.props.history.replace(`${this.props.location.pathname}?log=${start}`); + } + } + + setWarning(s) { + this.setState({ warning: s }); + } +}; + +CombatLog.propTypes = { + username: PropTypes.string.isRequired +}; + +const mapStoreToProps = (store) => { + return { + username: store.account.username, + loggedIn: store.account.id !== 0 + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + // + }; +}; + +CombatLog = connect(mapStoreToProps, mapDispatchToProps)(CombatLog); + +export default CombatLog; \ No newline at end of file diff --git a/src/components/pages/home.jsx b/src/components/pages/home.jsx index ed6e67e..00ddd77 100644 --- a/src/components/pages/home.jsx +++ b/src/components/pages/home.jsx @@ -1,23 +1,28 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { withRouter, Link } from 'react-router-dom'; //panels import CommonLinks from '../panels/common_links.jsx'; import Blurb from '../panels/blurb.jsx'; -import NewsPanel from '../panels/news_panel.jsx'; +import News from '../panels/news.jsx'; class Home extends React.Component { constructor(props) { super(props); this.state = { - // + warning: '', //TODO: unified warning? + fetch: null }; } - //rendering function + componentDidUpdate(prevProps, prevState, snapshot) { + this.state.fetch(); + } + render() { - //return the home page + let warningStyle = { + display: this.state.warning.length > 0 ? 'flex' : 'none' + }; + return (
@@ -26,29 +31,23 @@ class Home extends React.Component {
+
+

{this.state.warning}

+
+

About

News

- + this.setState({ fetch: fn }) } />
); } -} -function mapStoreToProps(store) { - return { - id: store.account.id + setWarning(s) { + this.setState({ warning: s }); } -} +}; -function mapDispatchToProps(dispatch) { - return { - // - } -} - -Home = connect(mapStoreToProps, mapDispatchToProps)(Home); - -export default withRouter(Home); \ No newline at end of file +export default Home; \ No newline at end of file diff --git a/src/components/pages/ladder.jsx b/src/components/pages/ladder.jsx index b91709f..89db9a2 100644 --- a/src/components/pages/ladder.jsx +++ b/src/components/pages/ladder.jsx @@ -1,6 +1,5 @@ import React from 'react'; import queryString from 'query-string'; -import { withRouter } from 'react-router-dom'; import CommonLinks from '../panels/common_links.jsx'; import PagedLadder from '../panels/paged_ladder.jsx'; @@ -38,7 +37,12 @@ class Ladder extends React.Component {

Game Ladder

- +
@@ -93,6 +97,6 @@ class Ladder extends React.Component { this.props.history.replace(`${this.props.location.pathname}?rank=${start}`); } } -} +}; -export default withRouter(Ladder); \ No newline at end of file +export default Ladder; \ No newline at end of file diff --git a/src/components/pages/login.jsx b/src/components/pages/login.jsx index 2f41743..c8f34a6 100644 --- a/src/components/pages/login.jsx +++ b/src/components/pages/login.jsx @@ -15,7 +15,7 @@ class Login extends React.Component { render() { return (
- this.props.history.push('/profile')} /> + this.props.history.push('/profile')} /> Return Home
); diff --git a/src/components/pages/page_not_found.jsx b/src/components/pages/page_not_found.jsx index 2e5ec3f..4b4491c 100644 --- a/src/components/pages/page_not_found.jsx +++ b/src/components/pages/page_not_found.jsx @@ -4,13 +4,16 @@ import { withRouter, Link } from 'react-router-dom'; class PageNotFound extends React.Component { constructor(props) { super(props); - this.state = {}; + this.state = { + // + }; } render() { let style = { justifyContent: 'center' - } + }; + return (

Page Not Found

@@ -18,6 +21,6 @@ class PageNotFound extends React.Component {
); } -} +}; export default withRouter(PageNotFound); \ No newline at end of file diff --git a/src/components/pages/password_change.jsx b/src/components/pages/password_change.jsx index 915c555..5dcfcd9 100644 --- a/src/components/pages/password_change.jsx +++ b/src/components/pages/password_change.jsx @@ -9,13 +9,12 @@ class PasswordChange extends React.Component { constructor(props) { super(props); this.state = { - changeSent: false, - changeMsg: '' + changed: '' } } componentDidMount() { - if (!this.props.id) { + if (!this.props.loggedIn) { this.props.history.push('/'); } } @@ -23,13 +22,13 @@ class PasswordChange extends React.Component { render() { let Panel; - if (!this.state.changeSent) { + if (!this.state.changed) { Panel = () => { - return ( this.setState({ changeSent: true, changeMsg: msg }) } />); + return ( this.setState({ changed: msg }) } />); } } else { Panel = () => { - return (

{this.state.changeMsg}

); + return (

{this.state.changed}

); } } @@ -42,17 +41,17 @@ class PasswordChange extends React.Component { } }; -function mapStoreToProps(store) { +const mapStoreToProps = (store) => { return { - id: store.account.id - } -} + loggedIn: store.account.id !== 0 + }; +}; -function mapDispatchToProps(dispatch) { +const mapDispatchToProps = (dispatch) => { return { // - } -} + }; +}; PasswordChange = connect(mapStoreToProps, mapDispatchToProps)(PasswordChange); diff --git a/src/components/pages/password_recover.jsx b/src/components/pages/password_recover.jsx index 9a9b9bc..9006035 100644 --- a/src/components/pages/password_recover.jsx +++ b/src/components/pages/password_recover.jsx @@ -8,21 +8,20 @@ class PasswordRecover extends React.Component { constructor(props) { super(props); this.state = { - recoverSent: false, - recoverMsg: '' + recovered: '' } } render() { let Panel; - if (!this.state.recoverSent) { + if (!this.state.recovered) { Panel = () => { - return ( this.setState( {recoverSent: true, recoverMsg: msg} )} />); + return ( this.setState( {recovered: msg} )} />); } } else { Panel = () => { - return (

{this.state.recoverMsg}

); + return (

{this.state.recovered}

); } } diff --git a/src/components/pages/password_reset.jsx b/src/components/pages/password_reset.jsx index 5870ef2..e1e4cb9 100644 --- a/src/components/pages/password_reset.jsx +++ b/src/components/pages/password_reset.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { withRouter, Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; import queryString from 'query-string'; //panels @@ -10,8 +9,7 @@ class PasswordReset extends React.Component { constructor(props) { super(props); this.state = { - reset: false, - resetMsg: '', + reset: '', params: queryString.parse(props.location.search) } } @@ -21,11 +19,11 @@ class PasswordReset extends React.Component { if (!this.state.reset) { Panel = () => { - return ( this.setState( {reset: true, resetMsg: msg} )}/>); + return ( this.setState({reset: msg}) } />); } } else { Panel = () => { - return (

{this.state.resetMsg}

); + return (

{this.state.reset}

); } } diff --git a/src/components/pages/profile.jsx b/src/components/pages/profile.jsx index a810f29..9516201 100644 --- a/src/components/pages/profile.jsx +++ b/src/components/pages/profile.jsx @@ -1,49 +1,25 @@ import React from 'react'; -import { connect } from 'react-redux'; import { withRouter, Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; import queryString from 'query-string'; +//actions +import { storeProfile } from '../../actions/profile.js'; + //panels import CommonLinks from '../panels/common_links.jsx'; import AttackButton from '../panels/attack_button.jsx'; -import Equipment from '../panels/equipment.jsx'; -import CombatLog from '../panels/combat_log.jsx'; class Profile extends React.Component { constructor(props) { super(props); - let params = queryString.parse(props.location.search); - this.state = { - params: params, - - username: '', - gold: 0, - recruits: 0, - soldiers: 0, - spies: 0, - scientists: 0, - - warning: '', - - //combat log - start: params.log, - - //equipment - fetchStatistics: null, - fetchEquipment: null + params: queryString.parse(props.location.search), + warning: '', //TODO: unified warning? }; - this.sendRequest('/profilerequest', this.state.params.username ? this.state.params.username : this.props.username); - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (JSON.stringify(this.state) !== JSON.stringify(prevState)) { -// if (this.state.fetchStatistics) this.state.fetchStatistics(); -// if (this.state.fetchEquipment) this.state.fetchEquipment(); - } + this.sendRequest('/profilerequest', {username: this.state.params.username ? this.state.params.username : this.props.account.username}); } render() { @@ -54,8 +30,8 @@ class Profile extends React.Component { //side panel stuff let SidePanel; - if (this.props.id) { - if (this.props.username === this.state.username) { + if (this.props.account.id) { + if (this.props.account.username === this.props.profile.username) { SidePanel = this.MyProfileSidePanel.bind(this); } else { SidePanel = this.NotMyProfileSidePanel.bind(this); @@ -67,13 +43,12 @@ class Profile extends React.Component { //main panel let MainPanel; - if (this.props.id) { + if (this.props.account.id) { //logged in - if (this.state.username === this.props.username) { + if (this.props.account.username === this.props.profile.username) { MainPanel = this.MyProfileMainPanel.bind(this); } else { - //not logged in - if (this.state.username !== '') { + if (this.props.profile.username) { MainPanel = this.NotMyProfileMainPanel.bind(this); } else { MainPanel = this.ProfileNotFoundMainPanel.bind(this); @@ -81,7 +56,7 @@ class Profile extends React.Component { } } else { //not logged in - if (this.state.username !== '') { + if (this.props.profile.username) { MainPanel = this.LoggedOutMainPanel.bind(this); } else { MainPanel = this.ProfileNotFoundMainPanel.bind(this); @@ -107,23 +82,17 @@ class Profile extends React.Component { } //gameplay functions - sendRequest(url, username = this.props.username, role = '') { //NOTE: merged all requests here - //request this profile's info, using my credentials - let formData = new FormData(); - - formData.append('id', this.props.id); - formData.append('token', this.props.token); - - formData.append('username', username); - formData.append('role', role); - + sendRequest(url, args = {}) { //send a unified request, using my credentials //build the XHR let xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { let json = JSON.parse(xhr.responseText); - this.storeProfile( + + this.props.storeProfile( json.username, json.gold, json.recruits, @@ -136,27 +105,18 @@ class Profile extends React.Component { this.setWarning(xhr.responseText); } } - } + }; - //send - xhr.open('POST', url, true); - xhr.send(formData); - } - - storeProfile(username, gold, recruits, soldiers, spies, scientists) { - this.setState({ - username: username, - gold: gold, - recruits: recruits, - soldiers: soldiers, - spies: spies, - scientists: scientists - }); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(JSON.stringify({ + id: this.props.account.id, + token: this.props.account.token, + ...args + })); } //panel functions MyProfileSidePanel() { - //return the side panel return (
@@ -171,52 +131,50 @@ class Profile extends React.Component {

Username:

-

{this.state.username}

+

{this.props.profile.username}

+

Gold:

-

{this.state.gold}

-
(+1 gold for each recruit every half hour)
+

{this.props.profile.gold}

+ +

(+1 gold for each recruit every half hour)

Recruits:

-

{this.state.recruits}

- +

{this.props.profile.recruits}

+ +

Soldiers:

-

{this.state.soldiers}

- - +

{this.props.profile.soldiers}

+ + +

Spies:

-

{this.state.spies}

- - +

{this.props.profile.spies}

+ + +

Scientists:

-

{this.state.scientists}

- - +

{this.props.profile.scientists}

+ + +
- -
-

Equipment

- this.setState({ fetchStatistics: fn }) } getFetchEquipment={ (fn) => this.setState({ fetchEquipment: fn}) } /> - -
-

Combat Log

-
); } @@ -225,7 +183,12 @@ class Profile extends React.Component { //return the side panel return (
- {e.preventDefault(); this.sendRequest('/profilerequest', this.props.username); this.setWarning(''); this.props.history.push('/profile');}} /> + { + e.preventDefault(); + this.sendRequest('/profilerequest', {username: this.props.account.username}); + this.setWarning(''); + this.props.history.push('/profile'); + }} />
); } @@ -233,47 +196,60 @@ class Profile extends React.Component { NotMyProfileMainPanel() { return (
-

{this.state.username}'s Kingdom

+

{this.props.profile.username}'s Kingdom

Username:

-

{this.state.username}

-
-
+

{this.props.profile.username}

+ +
+

Gold:

-

{this.state.gold}

-
-
+

{this.props.profile.gold}

+ +
+

Recruits:

-

{this.state.recruits}

- +

{this.props.profile.recruits}

+ +

Soldiers:

-

{this.state.soldiers}

-
-
+

{this.props.profile.soldiers}

+ +
+

Spies:

-

{this.state.spies}

-
-
+

{this.props.profile.spies}

+ +
+

Scientists:

-

{this.state.scientists}

-
-
+

{this.props.profile.scientists}

+ +
+
@@ -291,48 +267,54 @@ class Profile extends React.Component { LoggedOutMainPanel() { return (
-

{this.state.username}'s Kingdom

+

{this.props.profile.username}'s Kingdom

Username:

-

{this.state.username}

-
-
+

{this.props.profile.username}

+ +
+

Gold:

-

{this.state.gold}

-
-
+

{this.props.profile.gold}

+ +
+

Recruits:

-

{this.state.recruits}

-
-
+

{this.props.profile.recruits}

+ +
+

Soldiers:

-

{this.state.soldiers}

-
-
+

{this.props.profile.soldiers}

+ +
+

Spies:

-

{this.state.spies}

-
-
+

{this.props.profile.spies}

+ +
+

Scientists:

-

{this.state.scientists}

-
-
+

{this.props.profile.scientists}

+ +
+
@@ -341,38 +323,29 @@ class Profile extends React.Component { ProfileNotFoundMainPanel() { return ( -
+
+

Profile Not Found!

+
); } setWarning(s) { - this.setState({ - warning: s - }); + this.setState({ warning: s }); } -} - -Profile.propTypes = { - id: PropTypes.number.isRequired, - email: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - token: PropTypes.number.isRequired }; -function mapStoreToProps(store) { +const mapStoreToProps = (store) => { return { - id: store.account.id, - email: store.account.email, - username: store.account.username, - token: store.account.token - } -} + account: store.account, + profile: store.profile, + }; +}; -function mapDispatchToProps(dispatch) { +const mapDispatchToProps = (dispatch) => { return { - // - } -} + storeProfile: (username, gold, recruits, soldiers, spies, scientists) => dispatch(storeProfile(username, gold, recruits, soldiers, spies, scientists)) + }; +}; Profile = connect(mapStoreToProps, mapDispatchToProps)(Profile); diff --git a/src/components/pages/signup.jsx b/src/components/pages/signup.jsx index 7e118a9..53e9445 100644 --- a/src/components/pages/signup.jsx +++ b/src/components/pages/signup.jsx @@ -8,8 +8,7 @@ class Signup extends React.Component { constructor(props) { super(props); this.state = { - signupSent: false, - signupMsg: '' + signedUp: '' } //TODO: referral links @@ -18,13 +17,13 @@ class Signup extends React.Component { render() { let Panel; - if (!this.state.signupSent) { + if (!this.state.signedUp) { Panel = () => { - return ( this.setState( {signupSent: true, signupMsg: msg} )} />); + return ( this.setState({signedUp: msg}) } />); } } else { Panel = () => { - return (

{this.state.signupMsg}

); + return (

{this.state.signedUp}

); } } diff --git a/src/components/panels/attack_button.jsx b/src/components/panels/attack_button.jsx index 72adfe8..c7a731a 100644 --- a/src/components/panels/attack_button.jsx +++ b/src/components/panels/attack_button.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; class AttackButton extends React.Component { @@ -8,85 +9,86 @@ class AttackButton extends React.Component { message: '' }; - this.sendAttackingStatusRequest(); + this.sendRequest('/attackstatusrequest', {attacker: this.props.attacker}); } render() { - if (this.state.message !== '') { + if (this.state.message) { return (

{this.state.message}

); } else { + //inject something extra + let onClick = (e) => { + this.sendRequest('/attackrequest', {attacker: this.props.attacker, defender: this.props.defender}); + if (this.props.onClick) { + this.props.onClick(e); + } + }; + return ( - + ); } } - sendAttackRequest() { + //gameplay functions + sendRequest(url, args = {}) { //send a unified request, using my credentials //build the XHR let xhr = new XMLHttpRequest(); - xhr.open('POST', '/attackrequest', true); + xhr.open('POST', url, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { let json = JSON.parse(xhr.responseText); - if (json.status === 'attacking') { - this.setState({ message: `Your soldiers are attacking ${json.defender}` }); - } - } else if (xhr.status === 400) { - if (this.props.setWarning) { - this.props.setWarning(xhr.responseText); - } - } - } - } - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(JSON.stringify({ - attacker: this.props.attacker, - defender: this.props.defender, - token: this.props.token - })); - - if (this.props.onClick) { - this.props.onClick(); - } - - this.props.setDisabled(true); - } - - sendAttackingStatusRequest() { - let xhr = new XMLHttpRequest(); - xhr.open('POST', '/attackstatusrequest', true); - - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - let json = JSON.parse(xhr.responseText); + //on success if (json.status === 'attacking') { this.setState({ message: `Your soldiers are attacking ${json.defender}` }); } } + else if (xhr.status === 400 && this.props.setWarning) { + this.props.setWarning(xhr.responseText); + } } - } + }; xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.send(JSON.stringify({ - username: this.props.attacker + id: this.props.id, + token: this.props.token, + ...args })); } }; AttackButton.propTypes = { + id: PropTypes.number.isRequired, + token: PropTypes.number.isRequired, + + attacker: PropTypes.string.isRequired, + defender: PropTypes.string.isRequired, + className: PropTypes.string, style: PropTypes.object, onClick: PropTypes.func, - setWarning: PropTypes.func, - attacker: PropTypes.string.isRequired, - defender: PropTypes.string.isRequired, - token: PropTypes.number.isRequired + setWarning: PropTypes.func }; +const mapStoreToProps = (store) => { + return { + id: store.account.id, + token: store.account.token + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + // + }; +}; + +AttackButton = connect(mapStoreToProps, mapDispatchToProps)(AttackButton); + export default AttackButton; \ No newline at end of file diff --git a/src/components/panels/blurb.jsx b/src/components/panels/blurb.jsx index 52b8b0f..09ebd5b 100644 --- a/src/components/panels/blurb.jsx +++ b/src/components/panels/blurb.jsx @@ -21,5 +21,5 @@ export default class Blurb extends React.Component {
); } -} +}; diff --git a/src/components/panels/combat_log.jsx b/src/components/panels/combat_log.jsx deleted file mode 100644 index e0c0ff8..0000000 --- a/src/components/panels/combat_log.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withRouter } from 'react-router-dom'; - -import PagedCombatLog from './paged_combat_log.jsx'; - -class CombatLog extends React.Component { - constructor(props) { - super(props); - this.state = { - start: parseInt(props.start) || 0, - length: parseInt(props.length) || 20, - fetch: null - }; - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (JSON.stringify(this.state) !== JSON.stringify(prevState)) { - this.state.fetch(); - } - } - - render() { - let ButtonHeader = this.buttonHeader.bind(this); - - return ( -
- - - -
- - ); - } - - buttonHeader() { - return ( -
-
- -
-
- -
-
- ); - } - - increment() { - let start = this.state.start + this.state.length; - - this.props.history.push(`${this.props.location.pathname}?log=${start}`); - } - - decrement() { - let start = Math.max(0, this.state.start - this.state.length); - - //don't decrement too far - if (start === this.state.start) { - return; - } - - this.props.history.push(`${this.props.location.pathname}?log=${start}`); - } - - //bound callbacks - getFetch(fn) { - this.setState({ fetch: fn }); - } - - onReceived(data) { - if (data.length === 0) { - let start = Math.max(0, this.state.start - this.state.length); - - //don't decrement too far - if (start === this.state.start) { - return; - } - - this.props.history.replace(`${this.props.location.pathname}?log=${start}`); - } - } -} - -CombatLog.propTypes = { - username: PropTypes.string.isRequired -}; - -export default withRouter(CombatLog); \ No newline at end of file diff --git a/src/components/panels/common_links.jsx b/src/components/panels/common_links.jsx index 414672f..13ce03c 100644 --- a/src/components/panels/common_links.jsx +++ b/src/components/panels/common_links.jsx @@ -8,14 +8,16 @@ import Logout from './logout.jsx'; class CommonLinks extends React.Component { constructor(props) { super(props); + this.state = { // } } render() { - //render extra stuff + //render any extra stuff let Extra; + if (this.props.extra) { Extra = this.props.extra; } else { @@ -29,20 +31,21 @@ class CommonLinks extends React.Component {

Return Home

Your Kingdom

Game Ladder

+

Combat Log

Change Password

- this.props.history.push('/')} /> + this.props.history.push('/') } />
); } else { //if not logged in return (
+

Return Home

Sign Up

Login

Recover Password

-

Return Home

Game Ladder

@@ -50,9 +53,11 @@ class CommonLinks extends React.Component { ); } } -} +}; CommonLinks.propTypes = { + loggedIn: PropTypes.bool.isRequired, + onClickSignup: PropTypes.func, onClickLogin: PropTypes.func, onClickPasswordRecover: PropTypes.func, @@ -66,13 +71,13 @@ function mapStoreToProps(store) { return { loggedIn: store.account.id !== undefined && store.account.id !== 0 } -} +}; function mapDispatchToProps(dispatch) { return { // } -} +}; CommonLinks = connect(mapStoreToProps, mapDispatchToProps)(CommonLinks); diff --git a/src/components/panels/equipment.jsx b/src/components/panels/equipment.jsx index 5eeaecf..48e0cc5 100644 --- a/src/components/panels/equipment.jsx +++ b/src/components/panels/equipment.jsx @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +//TODO: incomplete + class Equipment extends React.Component { constructor(props) { super(props); diff --git a/src/components/panels/footer.jsx b/src/components/panels/footer.jsx index 01d2a95..c0019bb 100644 --- a/src/components/panels/footer.jsx +++ b/src/components/panels/footer.jsx @@ -1,6 +1,6 @@ import React from 'react'; -export default class Footer extends React.Component { +class Footer extends React.Component { render() { return (
@@ -8,5 +8,6 @@ export default class Footer extends React.Component {
); } -} +}; +export default Footer; \ No newline at end of file diff --git a/src/components/panels/login.jsx b/src/components/panels/login.jsx index 378a7c8..06b9dbf 100644 --- a/src/components/panels/login.jsx +++ b/src/components/panels/login.jsx @@ -1,6 +1,8 @@ import React from 'react'; import { connect } from 'react-redux'; -import { login } from '../../actions/accounts.js'; +import PropTypes from 'prop-types'; + +import { login } from '../../actions/account.js'; import { validateEmail } from '../../../common/utilities.js'; class Login extends React.Component { @@ -14,7 +16,7 @@ class Login extends React.Component { } render() { - let warningStyle = { + let warningStyle = { //TODO: lift the warning out to the page? display: this.state.warning.length > 0 ? 'flex' : 'none' }; @@ -26,15 +28,15 @@ class Login extends React.Component {

{this.state.warning}

-
this.submit(e)}> +
- +
- +
@@ -50,19 +52,26 @@ class Login extends React.Component { return; } - //build the XHR + //build the XHR (around an existing form object) let form = e.target; let formData = new FormData(form); + let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { let json = JSON.parse(xhr.responseText); - this.props.login(json.id, json.email, json.username, json.token); - if (this.props.onSubmit) { - this.props.onSubmit(); + this.props.login( + json.id, + json.email, + json.username, + json.token + ); + + if (this.props.onSuccess) { + this.props.onSuccess(json.msg); //NOTE: could use this as a redirect to a special offer or sonmething } } @@ -94,43 +103,39 @@ class Login extends React.Component { } setWarning(s) { - this.setState({ - warning: s - }); + this.setState({ warning: s }); } clearInput() { - this.setState({ - email: '', - password: '', - warning: '' - }); + this.setState({ email: '', password: '', warning: '' }); } updateEmail(evt) { - this.setState({ - email: evt.target.value - }); + this.setState({ email: evt.target.value }); } updatePassword(evt) { - this.setState({ - password: evt.target.value - }); + this.setState({ password: evt.target.value }); } -} +}; -function mapStoreToProps(store) { +Login.propTypes = { + login: PropTypes.func.isRequired, + + onSubmit: PropTypes.func +}; + +const mapStoreToProps = (store) => { return { // } -} +}; -function mapDispatchToProps(dispatch) { +const mapDispatchToProps = (dispatch) => { return { login: (id, email, username, token) => dispatch(login(id, email, username, token)) } -} +}; Login = connect(mapStoreToProps, mapDispatchToProps)(Login); diff --git a/src/components/panels/logout.jsx b/src/components/panels/logout.jsx index 136dd6a..b993460 100644 --- a/src/components/panels/logout.jsx +++ b/src/components/panels/logout.jsx @@ -1,58 +1,65 @@ import React from 'react'; import { connect } from 'react-redux'; -import { logout } from '../../actions/accounts.js'; import PropTypes from 'prop-types'; +import { logout } from '../../actions/account.js'; + class Logout extends React.Component { constructor(props) { super(props); + + this.state = { + // + } } render() { return ( - + ); } - submit(e) { - e.preventDefault(); - + sendRequest(url, args = {}) { //send a unified request, using my credentials //build the XHR let xhr = new XMLHttpRequest(); - xhr.open('POST', '/logoutrequest', true); + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.send(JSON.stringify({ - email: this.props.email, - token: this.props.token + id: this.props.id, + token: this.props.token, + ...args })); + //Don't wait for a response this.props.logout(); if (this.props.onClick) { this.props.onClick(); } } -} +}; Logout.propTypes = { - email: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, token: PropTypes.number.isRequired, logout: PropTypes.func.isRequired, + onClick: PropTypes.func -} +}; function mapStoreToProps(store) { return { - email: store.account.email, + id: store.account.id, token: store.account.token } -} +}; function mapDispatchToProps(dispatch) { return { logout: () => { dispatch(logout()) } } -} +}; Logout = connect(mapStoreToProps, mapDispatchToProps)(Logout); diff --git a/src/components/panels/news.jsx b/src/components/panels/news.jsx new file mode 100644 index 0000000..e4f49e2 --- /dev/null +++ b/src/components/panels/news.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown/with-html'; +import PropTypes from 'prop-types'; + +class News extends React.Component { + constructor(props) { + super(props); + + this.state = { + // + }; + + if (props.getFetch) { + props.getFetch( () => this.sendRequest('/newsrequest', {length: this.props.length || 10}) ); + } + } + + render() { + return ( +
+ {Object.keys(this.state).map((key) =>
+ +
+
)} +
+ ); + } + + sendRequest(url, args = {}) { //send a unified request, using my credentials + //build the XHR + let xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let json = JSON.parse(xhr.responseText); + + //on success + this.setState(json); + } + else if (xhr.status === 400 && this.props.setWarning) { + this.props.setWarning(xhr.responseText); + } + } + }; + + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(JSON.stringify({ + //NOTE: No id or token needed for the news + ...args + })); + } +}; + +News.propTypes = { + length: PropTypes.number, + setWarning: PropTypes.func, + getFetch: PropTypes.func +}; + +export default News; \ No newline at end of file diff --git a/src/components/panels/news_panel.jsx b/src/components/panels/news_panel.jsx deleted file mode 100644 index 1b4cc6e..0000000 --- a/src/components/panels/news_panel.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import ReactMarkdown from 'react-markdown'; - -class NewsPanel extends React.Component { - constructor(props) { - super(props); - - this.state = { - data: {} - }; - - this.fetchNews(this.props.length || 10); - } - - render() { - return ( -
- {Object.keys(this.state.data).map((key) =>
- -
-
)} -
- ); - } - - fetchNews(max) { - //build the XHR - let xhr = new XMLHttpRequest(); - - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - this.setState({data: JSON.parse(xhr.responseText)}); - } - } - } - - xhr.open('POST', '/newsrequest', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(JSON.stringify({ max: max })); - } -}; - -export default NewsPanel; \ No newline at end of file diff --git a/src/components/panels/paged_combat_log.jsx b/src/components/panels/paged_combat_log.jsx index d29813f..1550624 100644 --- a/src/components/panels/paged_combat_log.jsx +++ b/src/components/panels/paged_combat_log.jsx @@ -1,20 +1,31 @@ import React from 'react'; import { withRouter, Link } from 'react-router-dom'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; class PagedCombatLog extends React.Component { constructor(props) { super(props); + this.state = { - data: {} + // } if (props.getFetch) { - props.getFetch(this.fetchCombatLog.bind(this)); + props.getFetch(() => this.sendRequest('/combatlogrequest', {username: props.username, start: props.start, length: props.length})); } } render() { + //make the enemy name a link + const PrettyName = (props) => { + if (props.name === this.props.username) { + return (

{props.name}

); + } else { + return (

{props.name}

); + } + }; + return (
@@ -28,44 +39,51 @@ class PagedCombatLog extends React.Component {

Gold Transferred

Victor Casualties

- {Object.keys(this.state.data).map((key) =>
-

{ this.parseDate(this.state.data[key].eventTime) }

-

{this.state.data[key].attackerUsername}

-

{this.state.data[key].defenderUsername}

-

{this.state.data[key].attackingUnits}

-

{this.state.data[key].defendingUnits}

-

{this.state.data[key].undefended ? 'yes' : 'no'}

-

{this.state.data[key].victor}

-

{this.state.data[key].spoilsGold}

-

{this.state.data[key].casualtiesVictor}

+ + {Object.keys(this.state).map((key) =>
+

{ this.parseDate(this.state[key].eventTime) }

+ + +

{this.state[key].attackingUnits}

+

{this.state[key].defendingUnits}

+

{this.state[key].undefended ? 'yes' : 'no'}

+

{this.state[key].victor}

+

{this.state[key].spoilsGold}

+

{this.state[key].casualtiesVictor}

)}
); } - fetchCombatLog(username = this.props.username, start = this.props.start, length = this.props.length) { + //gameplay functions + sendRequest(url, args = {}) { //send a unified request, using my credentials //build the XHR let xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { - let data = JSON.parse(xhr.responseText); - this.setState({data: data}); + let json = JSON.parse(xhr.responseText); + + //on success + this.setState(json); if (this.props.onReceived) { - this.props.onReceived(data); + this.props.onReceived(json); } } + else if (xhr.status === 400 && this.props.setWarning) { + this.props.setWarning(xhr.responseText); + } } - } + }; - xhr.open('POST', '/combatlogrequest', true); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.send(JSON.stringify({ - username: username, - start: start, - length: length + id: this.props.id, + token: this.props.token, + ...args })); } @@ -75,14 +93,34 @@ class PagedCombatLog extends React.Component { let date = new Date(eventTime); return `${date.getDate()} ${month[date.getMonth()]}`; } -} +}; PagedCombatLog.propTypes = { + id: PropTypes.number.isRequired, + token: PropTypes.number.isRequired, + username: PropTypes.string.isRequired, start: PropTypes.number.isRequired, length: PropTypes.number.isRequired, + + setWarning: PropTypes.func, getFetch: PropTypes.func, onReceived: PropTypes.func }; +const mapStoreToProps = (store) => { + return { + id: store.account.id, + token: store.account.token + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + // + }; +}; + +PagedCombatLog = connect(mapStoreToProps, mapDispatchToProps)(PagedCombatLog); + export default withRouter(PagedCombatLog); \ No newline at end of file diff --git a/src/components/panels/paged_ladder.jsx b/src/components/panels/paged_ladder.jsx index 8a4dd25..c03a7f2 100644 --- a/src/components/panels/paged_ladder.jsx +++ b/src/components/panels/paged_ladder.jsx @@ -6,11 +6,11 @@ class PagedLadder extends React.Component { constructor(props) { super(props); this.state = { - data: {} + // } if (props.getFetch) { - props.getFetch(this.fetchLadder.bind(this)); + props.getFetch( () => this.sendRequest('/ladderrequest', {start: this.props.start || 0, length: this.props.length || 20}) ); } } @@ -23,45 +23,51 @@ class PagedLadder extends React.Component {

Recruits

Gold

- {Object.keys(this.state.data).map((key) =>
-

{this.state.data[key].username}

-

{this.state.data[key].soldiers}

-

{this.state.data[key].recruits}

-

{this.state.data[key].gold}

+ {Object.keys(this.state).map((key) =>
+

{this.state[key].username}

+

{this.state[key].soldiers}

+

{this.state[key].recruits}

+

{this.state[key].gold}

)}
); } - fetchLadder(start = this.props.start, length = this.props.length) { + sendRequest(url, args = {}) { //send a unified request, using my credentials //build the XHR let xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { - let data = JSON.parse(xhr.responseText); - this.setState({data: data}); + let json = JSON.parse(xhr.responseText); + + //on success + this.setState(json); if (this.props.onReceived) { - this.props.onReceived(data); + this.props.onReceived(json); } } + else if (xhr.status === 400 && this.props.setWarning) { + this.props.setWarning(xhr.responseText); + } } - } + }; - xhr.open('POST', '/ladderrequest', true); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.send(JSON.stringify({ - start: start, - length: length + //NOTE: No id or token needed for the news + ...args })); } -} +}; PagedLadder.propTypes = { - start: PropTypes.number.isRequired, - length: PropTypes.number.isRequired, + start: PropTypes.number, + length: PropTypes.number, + setWarning: PropTypes.func, getFetch: PropTypes.func, onReceived: PropTypes.func }; diff --git a/src/components/panels/password_change.jsx b/src/components/panels/password_change.jsx index 032939d..47bd06f 100644 --- a/src/components/panels/password_change.jsx +++ b/src/components/panels/password_change.jsx @@ -1,10 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; -import { sessionChange } from '../../actions/accounts.js'; +import PropTypes from 'prop-types'; + +import { sessionChange } from '../../actions/account.js'; class PasswordChange extends React.Component { constructor(props) { super(props); + this.state = { password: '', retype: '', @@ -13,7 +16,7 @@ class PasswordChange extends React.Component { } render() { - let warningStyle = { + let warningStyle = { //TODO: lift the warning out to the page? display: this.state.warning.length > 0 ? 'flex' : 'none' }; @@ -25,7 +28,7 @@ class PasswordChange extends React.Component {

{this.state.warning}

- this.submit(e)}> +
@@ -49,23 +52,25 @@ class PasswordChange extends React.Component { return; } - //build the XHR + //build the XHR (around an existing form object) let form = e.target; let formData = new FormData(form); - let xhr = new XMLHttpRequest(); - formData.append('email', this.props.email); + formData.append('id', this.props.id); formData.append('token', this.props.token); + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { let json = JSON.parse(xhr.responseText); + + //on success this.props.sessionChange(json.token); - //DEBUGGING - if (this.props.onPasswordChange) { - this.props.onPasswordChange(json.msg); + if (this.props.onSuccess) { + this.props.onSuccess(json.msg); } } @@ -78,6 +83,8 @@ class PasswordChange extends React.Component { //send the XHR xhr.open('POST', form.action, true); xhr.send(formData); + + this.clearInput(); } validateInput(e) { @@ -94,45 +101,43 @@ class PasswordChange extends React.Component { return true; } - setWarning(s) { - this.setState({ - warning: s - }); - } - clearInput() { - this.setState({ - password: '', - retype: '', - warning: '' - }); + this.setState({ password: '', retype: '', warning: '' }); } updatePassword(evt) { - this.setState({ - password: evt.target.value - }); + this.setState({ password: evt.target.value }); } updateRetype(evt) { - this.setState({ - retype: evt.target.value - }); + this.setState({ retype: evt.target.value }); } -} -function mapStoreToProps(store) { + setWarning(s) { + this.setState({ warning: s }); + } +}; + +PasswordChange.propTypes = { + id: PropTypes.number.isRequired, + token: PropTypes.number.isRequired, + sessionChange: PropTypes.func.isRequired, + + onSuccess: PropTypes.func +}; + +const mapStoreToProps = (store) => { return { - email: store.account.email, + id: store.account.id, token: store.account.token - } -} + }; +}; -function mapDispatchToProps(dispatch) { +const mapDispatchToProps = (dispatch) => { return { - sessionChange: (token) => { dispatch(sessionChange(token)); } - } -} + sessionChange: (token) => dispatch(sessionChange(token)) + }; +}; PasswordChange = connect(mapStoreToProps, mapDispatchToProps)(PasswordChange); diff --git a/src/components/panels/password_recover.jsx b/src/components/panels/password_recover.jsx index 2e72ccb..5c2bead 100644 --- a/src/components/panels/password_recover.jsx +++ b/src/components/panels/password_recover.jsx @@ -1,9 +1,11 @@ import React from 'react'; -import { validateEmail } from '../../../common/utilities.js'; +import { validateEmail } from '../../../common/utilities.js'; //TODO: move utilities to a better position +import PropTypes from 'prop-types'; class PasswordRecover extends React.Component { constructor(props) { super(props); + this.state = { email: '', warning: '' @@ -23,7 +25,7 @@ class PasswordRecover extends React.Component {

{this.state.warning}

- this.submit(e)}> +
@@ -42,17 +44,19 @@ class PasswordRecover extends React.Component { return; } - //build the XHR + //build the XHR (around an existing form object) let form = e.target; let formData = new FormData(form); + let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { - //DEBUGGING - if (this.props.onEmailSent) { - this.props.onEmailSent(xhr.responseText); + let json = JSON.parse(xhr.responseText); + + if (this.props.onSuccess) { + this.props.onSuccess(json.msg); } } @@ -79,23 +83,20 @@ class PasswordRecover extends React.Component { } setWarning(s) { - this.setState({ - warning: s - }); + this.setState({ warning: s }); } clearInput() { - this.setState({ - email: '', - warning: '' - }); + this.setState({ email: '', warning: '' }); } updateEmail(evt) { - this.setState({ - email: evt.target.value - }); + this.setState({ email: evt.target.value }); } -} +}; + +PasswordRecover.propTypes = { + onSuccess: PropTypes.func +}; export default PasswordRecover; \ No newline at end of file diff --git a/src/components/panels/password_reset.jsx b/src/components/panels/password_reset.jsx index 46b012e..2dfec80 100644 --- a/src/components/panels/password_reset.jsx +++ b/src/components/panels/password_reset.jsx @@ -1,11 +1,12 @@ import React from 'react'; import { connect } from 'react-redux'; -import { sessionChange } from '../../actions/accounts.js'; +import { sessionChange } from '../../actions/account.js'; import PropTypes from 'prop-types'; class PasswordReset extends React.Component { constructor(props) { super(props); + this.state = { password: '', retype: '', @@ -26,7 +27,7 @@ class PasswordReset extends React.Component {

{this.state.warning}

- this.submit(e)}> +
@@ -53,16 +54,19 @@ class PasswordReset extends React.Component { //build the XHR let form = e.target; let formData = new FormData(form); - let xhr = new XMLHttpRequest(); formData.append('email', this.props.email); formData.append('token', this.props.token); + let xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { - if (this.props.onPasswordReset) { - this.props.onPasswordReset(xhr.responseText); + let json = JSON.parse(xhr.responseText); + + if (this.props.onSuccess) { + this.props.onSuccess(json.msg); } } @@ -92,35 +96,27 @@ class PasswordReset extends React.Component { } setWarning(s) { - this.setState({ - warning: s - }); + this.setState({ warning: s }); } clearInput() { - this.setState({ - password: '', - retype: '', - warning: '' - }); + this.setState({ password: '', retype: '', warning: '' }); } updatePassword(evt) { - this.setState({ - password: evt.target.value - }); + this.setState({ password: evt.target.value }); } updateRetype(evt) { - this.setState({ - retype: evt.target.value - }); + this.setState({ retype: evt.target.value }); } -} +}; PasswordReset.propTypes = { email: PropTypes.string.isRequired, - token: PropTypes.number.isRequired + token: PropTypes.number.isRequired, + + onSuccess: PropTypes.func }; export default PasswordReset; \ No newline at end of file diff --git a/src/components/panels/signup.jsx b/src/components/panels/signup.jsx index bba489c..edaafef 100644 --- a/src/components/panels/signup.jsx +++ b/src/components/panels/signup.jsx @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; class Signup extends React.Component { constructor(props) { super(props); + this.state = { email: '', username: '', @@ -27,7 +28,7 @@ class Signup extends React.Component {

{this.state.warning}

- this.submit(e)}> +
@@ -64,14 +65,16 @@ class Signup extends React.Component { //build the XHR let form = e.target; let formData = new FormData(form); + let xhr = new XMLHttpRequest(); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { - if (this.props.onSignup) { - this.props.onSignup(xhr.responseText); - console.log('trying to...'); + let json = JSON.parse(xhr.responseText); + + if (this.props.onSuccess) { + this.props.onSuccess(json.msg); } } @@ -114,48 +117,32 @@ class Signup extends React.Component { } setWarning(s) { - this.setState({ - warning: s - }); + this.setState({ warning: s }); } clearInput() { - this.setState({ - email: '', - username: '', - password: '', - retype: '', - warning: '' - }); + this.setState({ email: '', username: '', password: '', retype: '', warning: '' }); } updateEmail(evt) { - this.setState({ - email: evt.target.value - }); + this.setState({ email: evt.target.value }); } updateUsername(evt) { - this.setState({ - username: evt.target.value - }); + this.setState({ username: evt.target.value }); } updatePassword(evt) { - this.setState({ - password: evt.target.value - }); + this.setState({ password: evt.target.value }); } updateRetype(evt) { - this.setState({ - retype: evt.target.value - }); + this.setState({ retype: evt.target.value }); } -} +}; Signup.propTypes = { - onSignup: PropTypes.func + onSuccess: PropTypes.func }; export default Signup; \ No newline at end of file diff --git a/src/reducers/accounts.js b/src/reducers/account.js similarity index 75% rename from src/reducers/accounts.js rename to src/reducers/account.js index 4739fa2..7b0c3f4 100644 --- a/src/reducers/accounts.js +++ b/src/reducers/account.js @@ -1,4 +1,4 @@ -import { LOGIN, LOGOUT, SESSIONCHANGE } from "../actions/accounts.js"; +import { LOGIN, LOGOUT, SESSION_CHANGE } from "../actions/account.js"; const initialStore = { id: 0, @@ -7,7 +7,7 @@ const initialStore = { token: 0 }; -export function accountReducer(store = initialStore, action) { +export const accountReducer = (store = initialStore, action) => { switch(action.type) { case LOGIN: { let newStore = JSON.parse(JSON.stringify(initialStore)); @@ -23,7 +23,7 @@ export function accountReducer(store = initialStore, action) { case LOGOUT: return initialStore; - case SESSIONCHANGE: { + case SESSION_CHANGE: { let newStore = JSON.parse(JSON.stringify(store)); newStore.token = action.token; @@ -33,6 +33,6 @@ export function accountReducer(store = initialStore, action) { default: return store; - } + }; } diff --git a/src/reducers/combat.js b/src/reducers/combat.js deleted file mode 100644 index e7fa7f2..0000000 --- a/src/reducers/combat.js +++ /dev/null @@ -1,18 +0,0 @@ -import { SET_ATTACK_DISABLED } from '../actions/combat.js'; - -const initialStore = { - attackDisabled: false -}; - -export function combatReducer(store = initialStore, action) { - switch(action.type) { - case SET_ATTACK_DISABLED: { - let newStore = JSON.parse(JSON.stringify(initialStore)); - newStore.attackDisabled = action.disabled; - return newStore; - } - - default: - return store; - } -} \ No newline at end of file diff --git a/src/reducers/profile.js b/src/reducers/profile.js new file mode 100644 index 0000000..0291a8d --- /dev/null +++ b/src/reducers/profile.js @@ -0,0 +1,59 @@ +import { + STORE_PROFILE, + STORE_USERNAME, + STORE_GOLD, + STORE_RECRUITS, + STORE_SOLDIERS, + STORE_SPIES, + STORE_SCIENTISTS +} from '../actions/profile.js'; + +const initialStore = { + username: '', + gold: 0, + recruits: 0, + soldiers: 0, + spies: 0, + scientists: 0 +}; + +export const profileReducer = (store = initialStore, action) => { + let newStore = JSON.parse(JSON.stringify(store)); + + switch(action.type) { + case STORE_PROFILE: + newStore.username = action.username; + newStore.gold = action.gold; + newStore.recruits = action.recruits; + newStore.soldiers = action.soldiers; + newStore.spies = action.spies; + newStore.scientists = action.scientists; + break; + + case STORE_USERNAME: + newStore.username = action.username; + break; + + case STORE_GOLD: + newStore.gold = action.gold; + break; + + case STORE_RECRUITS: + newStore.recruits = action.recruits; + break; + + case STORE_SOLDIERS: + newStore.soldiers = action.soldiers; + break; + + case STORE_SPIES: + newStore.spies = action.spies; + break; + + case STORE_SCIENTISTS: + newStore.scientists = action.scientists; + break; + }; + + return newStore; +} \ No newline at end of file diff --git a/src/reducers/reducer.js b/src/reducers/reducer.js index 239655f..56677c6 100644 --- a/src/reducers/reducer.js +++ b/src/reducers/reducer.js @@ -1,10 +1,10 @@ import { combineReducers } from 'redux'; -import { accountReducer } from './accounts.js'; -import { combatReducer } from './combat.js'; +import { accountReducer } from './account.js'; +import { profileReducer } from './profile.js'; //compile all reducers together export default combineReducers({ account: accountReducer, -/* combat: combatReducer */ + profile: profileReducer }); diff --git a/webpack.config.js b/webpack.config.js index 908c145..212c444 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,7 +24,7 @@ module.exports = { ] }, optimization: { - minimize: false, + minimize: true, minimizer: [ new TerserPlugin({ terserOptions: {