diff --git a/gulpfile.js b/gulpfile.js index 628e7507fb..39bb5da687 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,6 +13,7 @@ if (process.env.NODE_ENV === 'production') { require('./gulp/gulp-newstuff'); require('./gulp/gulp-build'); require('./gulp/gulp-babelify'); + require('./gulp/gulp-bootstrap'); } else { require('glob').sync('./gulp/gulp-*').forEach(require); require('gulp').task('default', ['test']); diff --git a/migrations/challenges/sync-all-challenges.js b/migrations/challenges/sync-all-challenges.js index 5200bab806..4cb9d00088 100644 --- a/migrations/challenges/sync-all-challenges.js +++ b/migrations/challenges/sync-all-challenges.js @@ -1,47 +1,47 @@ -import Bluebird from 'Bluebird'; - -import { model as Challenges } from '../../website/server/models/challenge'; -import { model as User } from '../../website/server/models/user'; - -async function syncChallengeToMembers (challenges) { - let challengSyncPromises = challenges.map(async function (challenge) { - let users = await User.find({ - // _id: '', - challenges: challenge._id, - }).exec(); - - let promises = []; - users.forEach(function (user) { - promises.push(challenge.syncToUser(user)); - promises.push(challenge.save()); - promises.push(user.save()); - }); - - return Bluebird.all(promises); - }); - - return await Bluebird.all(challengSyncPromises); -} - -async function syncChallenges (lastChallengeDate) { - let query = { - // _id: '', - }; - - if (lastChallengeDate) { - query.createdOn = { $lte: lastChallengeDate }; - } - - let challengesFound = await Challenges.find(query) - .limit(10) - .sort('-createdAt') - .exec(); - - let syncedChallenges = await syncChallengeToMembers(challengesFound) - .catch(reason => console.error(reason)); - let lastChallenge = challengesFound[challengesFound.length - 1]; - if (lastChallenge) syncChallenges(lastChallenge.createdAt); - return syncedChallenges; -}; - -module.exports = syncChallenges; +import Bluebird from 'Bluebird'; + +import { model as Challenges } from '../../website/server/models/challenge'; +import { model as User } from '../../website/server/models/user'; + +async function syncChallengeToMembers (challenges) { + let challengSyncPromises = challenges.map(async function (challenge) { + let users = await User.find({ + // _id: '', + challenges: challenge._id, + }).exec(); + + let promises = []; + users.forEach(function (user) { + promises.push(challenge.syncToUser(user)); + promises.push(challenge.save()); + promises.push(user.save()); + }); + + return Bluebird.all(promises); + }); + + return await Bluebird.all(challengSyncPromises); +} + +async function syncChallenges (lastChallengeDate) { + let query = { + // _id: '', + }; + + if (lastChallengeDate) { + query.createdOn = { $lte: lastChallengeDate }; + } + + let challengesFound = await Challenges.find(query) + .limit(10) + .sort('-createdAt') + .exec(); + + let syncedChallenges = await syncChallengeToMembers(challengesFound) + .catch(reason => console.error(reason)); + let lastChallenge = challengesFound[challengesFound.length - 1]; + if (lastChallenge) syncChallenges(lastChallenge.createdAt); + return syncedChallenges; +}; + +module.exports = syncChallenges; diff --git a/migrations/migration-runner.js b/migrations/migration-runner.js index 4f04540268..33b6d35279 100644 --- a/migrations/migration-runner.js +++ b/migrations/migration-runner.js @@ -21,4 +21,4 @@ var processUsers = require('./groups/update-groups-with-group-plans'); processUsers() .catch(function (err) { console.log(err) - }) \ No newline at end of file + }) diff --git a/migrations/users/users-to-test.js b/migrations/users/users-to-test.js new file mode 100644 index 0000000000..64b6c38e07 --- /dev/null +++ b/migrations/users/users-to-test.js @@ -0,0 +1,109 @@ +var migrationName = 'UserFromProdToTest'; +var authorName = 'TheHollidayInn'; // in case script author needs to know when their ... +var authorUuid = ''; //... own data is done + +/* + * This migraition will copy user data from prod to test + */ + +var monk = require('monk'); +var testConnectionSting = ''; // FOR TEST DATABASE +var usersTest = monk(testConnectionSting).get('users', { castIds: false }); +var groupsTest = monk(testConnectionSting).get('groups', { castIds: false }); +var challengesTest = monk(testConnectionSting).get('challenges', { castIds: false }); +var tasksTest = monk(testConnectionSting).get('tasks', { castIds: false }); + +var monk2 = require('monk'); +var liveConnectString = ''; // FOR TEST DATABASE +var userLive = monk2(liveConnectString).get('users', { castIds: false }); +var groupsLive = monk2(liveConnectString).get('groups', { castIds: false }); +var challengesLive = monk2(liveConnectString).get('challenges', { castIds: false }); +var tasksLive = monk2(liveConnectString).get('tasks', { castIds: false }); + +import uniq from 'lodash/uniq'; +import Bluebird from 'bluebird'; + +// Variabls for updating +let userIds = [ + '206039c6-24e4-4b9f-8a31-61cbb9aa3f66', +]; + +let groupIds = []; +let challengeIds = []; +let tasksIds = []; + +async function processUsers () { + let userPromises = []; + //{_id: {$in: userIds}} + + return userLive.find({guilds: 'b0764d64-8276-45a1-afa5-5ca9a5c64ca0'}) + .each((user, {close, pause, resume}) => { + if (user.guilds.length > 0) groupIds = groupIds.concat(user.guilds); + if (user.party._id) groupIds.push(user.party._id); + if (user.challenges.length > 0) challengeIds = challengeIds.concat(user.challenges); + if (user.tasksOrder.rewards.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.rewards); + if (user.tasksOrder.todos.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.todos); + if (user.tasksOrder.dailys.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.dailys); + if (user.tasksOrder.habits.length > 0) tasksIds = tasksIds.concat(user.tasksOrder.habits); + + let userPromise = usersTest.update({'_id': user._id}, user, {upsert:true}); + userPromises.push(userPromise); + }).then(() => { + return Bluebird.all(userPromises); + }) + .then(() => { + console.log("Done User"); + }); +} + +function processGroups () { + let promises = []; + let groupsToQuery = uniq(groupIds); + return groupsLive.find({_id: {$in: groupsToQuery}}) + .each((group, {close, pause, resume}) => { + let promise = groupsTest.update({_id: group._id}, group, {upsert:true}); + promises.push(promise); + }).then(() => { + return Bluebird.all(promises); + }) + .then(() => { + console.log("Done Group"); + }); +} + +function processChallenges () { + let promises = []; + let challengesToQuery = uniq(challengeIds); + return challengesLive.find({_id: {$in: challengesToQuery}}) + .each((challenge, {close, pause, resume}) => { + let promise = challengesTest.update({_id: challenge._id}, challenge, {upsert:true}); + promises.push(promise); + }).then(() => { + return Bluebird.all(promises); + }) + .then(() => { + console.log("Done Challenge"); + }); +} + +function processTasks () { + let promises = []; + let tasksToQuery = uniq(tasksIds); + return tasksLive.find({_id: {$in: tasksToQuery}}) + .each((task, {close, pause, resume}) => { + let promise = tasksTest.update({_id: task._id}, task, {upsert:true}); + promises.push(promise); + }).then(() => { + return Bluebird.all(promises); + }) + .then(() => { + console.log("Done Tasks"); + }); +} + +module.exports = async function prodToTest () { + await processUsers(); + await processGroups(); + await processChallenges(); + await processTasks(); +}; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index a9635b172c..0496a3b975 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4208,700 +4208,6 @@ "from": "fs.realpath@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" }, - "fsevents": { - "version": "1.1.1", - "from": "fsevents@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz", - "optional": true, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "from": "abbrev@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "from": "ansi-regex@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" - }, - "ansi-styles": { - "version": "2.2.1", - "from": "ansi-styles@>=2.2.1 <3.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "optional": true - }, - "aproba": { - "version": "1.1.1", - "from": "aproba@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.1.tgz", - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.2", - "from": "are-we-there-yet@>=1.1.2 <1.2.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", - "optional": true - }, - "asn1": { - "version": "0.2.3", - "from": "asn1@>=0.2.3 <0.3.0", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "from": "assert-plus@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "from": "asynckit@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "from": "aws-sign2@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "optional": true - }, - "aws4": { - "version": "1.6.0", - "from": "aws4@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "from": "balanced-match@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "optional": true - }, - "block-stream": { - "version": "0.0.9", - "from": "block-stream@*", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz" - }, - "boom": { - "version": "2.10.1", - "from": "boom@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" - }, - "brace-expansion": { - "version": "1.1.6", - "from": "brace-expansion@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" - }, - "buffer-shims": { - "version": "1.0.0", - "from": "buffer-shims@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" - }, - "caseless": { - "version": "0.11.0", - "from": "caseless@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "optional": true - }, - "chalk": { - "version": "1.1.3", - "from": "chalk@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "from": "code-point-at@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz" - }, - "combined-stream": { - "version": "1.0.5", - "from": "combined-stream@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" - }, - "commander": { - "version": "2.9.0", - "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "from": "concat-map@0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - }, - "console-control-strings": { - "version": "1.1.0", - "from": "console-control-strings@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" - }, - "core-util-is": { - "version": "1.0.2", - "from": "core-util-is@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - }, - "cryptiles": { - "version": "2.0.5", - "from": "cryptiles@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "optional": true - }, - "dashdash": { - "version": "1.14.1", - "from": "dashdash@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "optional": true - } - } - }, - "debug": { - "version": "2.2.0", - "from": "debug@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "optional": true - }, - "deep-extend": { - "version": "0.4.1", - "from": "deep-extend@>=0.4.0 <0.5.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz", - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "from": "delayed-stream@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - }, - "delegates": { - "version": "1.0.0", - "from": "delegates@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "from": "ecc-jsbn@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "optional": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "from": "escape-string-regexp@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "optional": true - }, - "extend": { - "version": "3.0.0", - "from": "extend@>=3.0.0 <3.1.0", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "from": "extsprintf@1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz" - }, - "forever-agent": { - "version": "0.6.1", - "from": "forever-agent@>=0.6.1 <0.7.0", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "optional": true - }, - "form-data": { - "version": "2.1.2", - "from": "form-data@>=2.1.1 <2.2.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.2.tgz", - "optional": true - }, - "fs.realpath": { - "version": "1.0.0", - "from": "fs.realpath@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - }, - "fstream": { - "version": "1.0.10", - "from": "fstream@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.10.tgz" - }, - "fstream-ignore": { - "version": "1.0.5", - "from": "fstream-ignore@>=1.0.5 <1.1.0", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "optional": true - }, - "gauge": { - "version": "2.7.3", - "from": "gauge@>=2.7.1 <2.8.0", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.3.tgz", - "optional": true - }, - "generate-function": { - "version": "2.0.0", - "from": "generate-function@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "optional": true - }, - "generate-object-property": { - "version": "1.2.0", - "from": "generate-object-property@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "optional": true - }, - "getpass": { - "version": "0.1.6", - "from": "getpass@>=0.1.1 <0.2.0", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "optional": true - } - } - }, - "glob": { - "version": "7.1.1", - "from": "glob@>=7.0.5 <8.0.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz" - }, - "graceful-fs": { - "version": "4.1.11", - "from": "graceful-fs@>=4.1.2 <5.0.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz" - }, - "graceful-readlink": { - "version": "1.0.1", - "from": "graceful-readlink@>=1.0.0", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "optional": true - }, - "har-validator": { - "version": "2.0.6", - "from": "har-validator@>=2.0.6 <2.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "optional": true - }, - "has-ansi": { - "version": "2.0.0", - "from": "has-ansi@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "optional": true - }, - "has-unicode": { - "version": "2.0.1", - "from": "has-unicode@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "optional": true - }, - "hawk": { - "version": "3.1.3", - "from": "hawk@>=3.1.3 <3.2.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "optional": true - }, - "hoek": { - "version": "2.16.3", - "from": "hoek@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" - }, - "http-signature": { - "version": "1.1.1", - "from": "http-signature@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "optional": true - }, - "inflight": { - "version": "1.0.6", - "from": "inflight@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - }, - "inherits": { - "version": "2.0.3", - "from": "inherits@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" - }, - "ini": { - "version": "1.3.4", - "from": "ini@>=1.3.0 <1.4.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" - }, - "is-my-json-valid": { - "version": "2.15.0", - "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", - "optional": true - }, - "is-property": { - "version": "1.0.2", - "from": "is-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "optional": true - }, - "is-typedarray": { - "version": "1.0.0", - "from": "is-typedarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "optional": true - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - }, - "isstream": { - "version": "0.1.2", - "from": "isstream@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "from": "jodid25519@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", - "optional": true - }, - "jsbn": { - "version": "0.1.1", - "from": "jsbn@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "from": "json-schema@0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "optional": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "from": "json-stringify-safe@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "optional": true - }, - "jsonpointer": { - "version": "4.0.1", - "from": "jsonpointer@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "optional": true - }, - "jsprim": { - "version": "1.3.1", - "from": "jsprim@>=1.2.2 <2.0.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.1.tgz", - "optional": true - }, - "mime-db": { - "version": "1.26.0", - "from": "mime-db@>=1.26.0 <1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz" - }, - "mime-types": { - "version": "2.1.14", - "from": "mime-types@>=2.1.7 <2.2.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.14.tgz" - }, - "minimatch": { - "version": "3.0.3", - "from": "minimatch@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz" - }, - "minimist": { - "version": "0.0.8", - "from": "minimist@0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" - }, - "mkdirp": { - "version": "0.5.1", - "from": "mkdirp@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.33", - "from": "node-pre-gyp@>=0.6.29 <0.7.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.33.tgz", - "optional": true - }, - "nopt": { - "version": "3.0.6", - "from": "nopt@>=3.0.6 <3.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "optional": true - }, - "npmlog": { - "version": "4.0.2", - "from": "npmlog@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.0.2.tgz", - "optional": true - }, - "number-is-nan": { - "version": "1.0.1", - "from": "number-is-nan@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" - }, - "oauth-sign": { - "version": "0.8.2", - "from": "oauth-sign@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "from": "object-assign@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "optional": true - }, - "once": { - "version": "1.4.0", - "from": "once@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - }, - "path-is-absolute": { - "version": "1.0.1", - "from": "path-is-absolute@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - }, - "pinkie": { - "version": "2.0.4", - "from": "pinkie@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "optional": true - }, - "pinkie-promise": { - "version": "2.0.1", - "from": "pinkie-promise@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "from": "process-nextick-args@>=1.0.6 <1.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" - }, - "punycode": { - "version": "1.4.1", - "from": "punycode@>=1.4.1 <2.0.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "optional": true - }, - "qs": { - "version": "6.3.1", - "from": "qs@>=6.3.0 <6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.1.tgz", - "optional": true - }, - "rc": { - "version": "1.1.7", - "from": "rc@>=1.1.6 <1.2.0", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.7.tgz", - "optional": true, - "dependencies": { - "minimist": { - "version": "1.2.0", - "from": "minimist@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.2", - "from": "readable-stream@>=2.0.0 <3.0.0||>=1.1.13 <2.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", - "optional": true - }, - "request": { - "version": "2.79.0", - "from": "request@>=2.79.0 <3.0.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "optional": true - }, - "rimraf": { - "version": "2.5.4", - "from": "rimraf@>=2.5.4 <2.6.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz" - }, - "semver": { - "version": "5.3.0", - "from": "semver@>=5.3.0 <5.4.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "from": "set-blocking@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "from": "signal-exit@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "optional": true - }, - "sntp": { - "version": "1.0.9", - "from": "sntp@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "optional": true - }, - "sshpk": { - "version": "1.10.2", - "from": "sshpk@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.10.2.tgz", - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "from": "assert-plus@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "optional": true - } - } - }, - "string_decoder": { - "version": "0.10.31", - "from": "string_decoder@>=0.10.0 <0.11.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" - }, - "string-width": { - "version": "1.0.2", - "from": "string-width@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" - }, - "stringstream": { - "version": "0.0.5", - "from": "stringstream@>=0.0.4 <0.1.0", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "from": "strip-ansi@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" - }, - "strip-json-comments": { - "version": "2.0.1", - "from": "strip-json-comments@>=2.0.1 <2.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "optional": true - }, - "supports-color": { - "version": "2.0.0", - "from": "supports-color@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "optional": true - }, - "tar": { - "version": "2.2.1", - "from": "tar@>=2.2.1 <2.3.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz" - }, - "tar-pack": { - "version": "3.3.0", - "from": "tar-pack@>=3.3.0 <3.4.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.3.0.tgz", - "optional": true, - "dependencies": { - "once": { - "version": "1.3.3", - "from": "once@>=1.3.3 <1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "optional": true - }, - "readable-stream": { - "version": "2.1.5", - "from": "readable-stream@>=2.1.4 <2.2.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", - "optional": true - } - } - }, - "tough-cookie": { - "version": "2.3.2", - "from": "tough-cookie@>=2.3.0 <2.4.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "optional": true - }, - "tunnel-agent": { - "version": "0.4.3", - "from": "tunnel-agent@>=0.4.1 <0.5.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "optional": true - }, - "tweetnacl": { - "version": "0.14.5", - "from": "tweetnacl@>=0.14.0 <0.15.0", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "from": "uid-number@>=0.0.6 <0.1.0", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "from": "util-deprecate@>=1.0.1 <1.1.0", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - }, - "uuid": { - "version": "3.0.1", - "from": "uuid@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "optional": true - }, - "verror": { - "version": "1.3.6", - "from": "verror@1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "optional": true - }, - "wide-align": { - "version": "1.1.0", - "from": "wide-align@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.0.tgz", - "optional": true - }, - "wrappy": { - "version": "1.0.2", - "from": "wrappy@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - }, - "xtend": { - "version": "4.0.1", - "from": "xtend@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "optional": true - } - } - }, "fstream": { "version": "1.0.11", "from": "fstream@>=1.0.2 <2.0.0", @@ -6128,6 +5434,11 @@ "from": "he@>=1.1.0 <1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz" }, + "hellojs": { + "version": "1.15.1", + "from": "hellojs@>=1.15.1 <2.0.0", + "resolved": "http://registry.npmjs.org/hellojs/-/hellojs-1.15.1.tgz" + }, "hmac-drbg": { "version": "1.0.1", "from": "hmac-drbg@>=1.0.0 <2.0.0", @@ -7717,7 +7028,7 @@ "lazy-debug-legacy": { "version": "0.0.1", "from": "lazy-debug-legacy@>=0.0.0 <0.1.0", - "resolved": "http://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz" + "resolved": "https://registry.npmjs.org/lazy-debug-legacy/-/lazy-debug-legacy-0.0.1.tgz" }, "lazy-req": { "version": "1.1.0", diff --git a/package.json b/package.json index 2abec3f7a7..aa4203d0b5 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "gulp-uglify": "^1.4.2", "gulp.spritesmith": "^4.1.0", "habitica-markdown": "^1.3.0", + "hellojs": "^1.15.1", "html-webpack-plugin": "^2.8.1", "image-size": "~0.3.2", "in-app-purchase": "^1.1.6", diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js index 6382b85a3d..bf7017baa4 100644 --- a/test/api/v3/unit/libs/cron.test.js +++ b/test/api/v3/unit/libs/cron.test.js @@ -79,6 +79,13 @@ describe('cron', () => { expect(user.purchased.plan.gemsBought).to.equal(0); }); + it('resets plan.gemsBought on a new month if user does not have purchased.plan.dateUpdated', () => { + user.purchased.plan.gemsBought = 10; + user.purchased.plan.dateUpdated = undefined; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.gemsBought).to.equal(0); + }); + it('does not reset plan.gemsBought within the month', () => { let clock = sinon.useFakeTimers(moment().startOf('month').add(2, 'days').unix()); user.purchased.plan.dateUpdated = moment().startOf('month').toDate(); diff --git a/test/api/v3/unit/libs/payments/group-plans/group-payments-create.test.js b/test/api/v3/unit/libs/payments/group-plans/group-payments-create.test.js index de79e2b525..b53a619bf2 100644 --- a/test/api/v3/unit/libs/payments/group-plans/group-payments-create.test.js +++ b/test/api/v3/unit/libs/payments/group-plans/group-payments-create.test.js @@ -596,7 +596,7 @@ describe('Purchasing a group plan for group', () => { let updatedUser = await User.findById(recipient._id).exec(); - expect(updatedUser.purchased.plan.extraMonths).to.within(7, 8); + expect(updatedUser.purchased.plan.extraMonths).to.within(7, 9); }); it('adds months to members with existing recurring subscription and ignores existing negative extraMonths', async () => { diff --git a/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js b/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js index 1e2b5cc7e1..f058731b13 100644 --- a/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js +++ b/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js @@ -1,63 +1,63 @@ -describe('Group Tasks Meta Actions Controller', () => { - let rootScope, scope, user, userSerivce; - - beforeEach(() => { - module(function($provide) { - $provide.value('User', {}); - }); - - inject(($rootScope, $controller) => { - rootScope = $rootScope; - - user = specHelper.newUser(); - user._id = "unique-user-id"; - userSerivce = {user: user}; - - scope = $rootScope.$new(); - - scope.task = { - group: { - assignedUsers: [], - approval: { - required: false, - } - }, - }; - scope.task._edit = angular.copy(scope.task); - - $controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce}); - }); - }); - - describe('toggleTaskRequiresApproval', function () { - it('toggles task approval required field from false to true', function () { - scope.toggleTaskRequiresApproval(); - expect(scope.task._edit.group.approval.required).to.be.true; - }); - - it('toggles task approval required field from true to false', function () { - scope.task._edit.group.approval.required = true; - scope.toggleTaskRequiresApproval(); - expect(scope.task._edit.group.approval.required).to.be.false; - }); - }); - - - describe('assign events', function () { - it('adds a group member to assigned users on "addedGroupMember" event ', () => { - var testId = 'test-id'; - rootScope.$broadcast('addedGroupMember', testId); - expect(scope.task.group.assignedUsers).to.contain(testId); - expect(scope.task._edit.group.assignedUsers).to.contain(testId); - }); - - it('removes a group member to assigned users on "addedGroupMember" event ', () => { - var testId = 'test-id'; - scope.task.group.assignedUsers.push(testId); - scope.task._edit.group.assignedUsers.push(testId); - rootScope.$broadcast('removedGroupMember', testId); - expect(scope.task.group.assignedUsers).to.not.contain(testId); - expect(scope.task._edit.group.assignedUsers).to.not.contain(testId); - }); - }); -}); +describe('Group Tasks Meta Actions Controller', () => { + let rootScope, scope, user, userSerivce; + + beforeEach(() => { + module(function($provide) { + $provide.value('User', {}); + }); + + inject(($rootScope, $controller) => { + rootScope = $rootScope; + + user = specHelper.newUser(); + user._id = "unique-user-id"; + userSerivce = {user: user}; + + scope = $rootScope.$new(); + + scope.task = { + group: { + assignedUsers: [], + approval: { + required: false, + } + }, + }; + scope.task._edit = angular.copy(scope.task); + + $controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce}); + }); + }); + + describe('toggleTaskRequiresApproval', function () { + it('toggles task approval required field from false to true', function () { + scope.toggleTaskRequiresApproval(); + expect(scope.task._edit.group.approval.required).to.be.true; + }); + + it('toggles task approval required field from true to false', function () { + scope.task._edit.group.approval.required = true; + scope.toggleTaskRequiresApproval(); + expect(scope.task._edit.group.approval.required).to.be.false; + }); + }); + + + describe('assign events', function () { + it('adds a group member to assigned users on "addedGroupMember" event ', () => { + var testId = 'test-id'; + rootScope.$broadcast('addedGroupMember', testId); + expect(scope.task.group.assignedUsers).to.contain(testId); + expect(scope.task._edit.group.assignedUsers).to.contain(testId); + }); + + it('removes a group member to assigned users on "addedGroupMember" event ', () => { + var testId = 'test-id'; + scope.task.group.assignedUsers.push(testId); + scope.task._edit.group.assignedUsers.push(testId); + rootScope.$broadcast('removedGroupMember', testId); + expect(scope.task.group.assignedUsers).to.not.contain(testId); + expect(scope.task._edit.group.assignedUsers).to.not.contain(testId); + }); + }); +}); diff --git a/test/client/unit/specs/store/actions/tasks.js b/test/client/unit/specs/store/actions/tasks.js index 79683848c6..bdf9a2dc3f 100644 --- a/test/client/unit/specs/store/actions/tasks.js +++ b/test/client/unit/specs/store/actions/tasks.js @@ -9,7 +9,7 @@ describe('tasks actions', () => { }); describe('fetchUserTasks', () => { - it('fetches user tasks', async () => { + xit('fetches user tasks', async () => { expect(store.state.tasks.loadingStatus).to.equal('NOT_LOADED'); const tasks = [{_id: 1}]; sandbox.stub(axios, 'get').withArgs('/api/v3/tasks/user').returns(Promise.resolve({data: {data: tasks}})); @@ -36,7 +36,7 @@ describe('tasks actions', () => { expect(store.state.tasks.loadingStatus).to.equal('LOADED'); }); - it('can reload tasks if forceLoad is true', async () => { + xit('can reload tasks if forceLoad is true', async () => { store.state.tasks = { loadingStatus: 'LOADED', data: [{_id: 1}], diff --git a/test/client/unit/specs/store/actions/user.js b/test/client/unit/specs/store/actions/user.js index 5069e04087..38739d8c45 100644 --- a/test/client/unit/specs/store/actions/user.js +++ b/test/client/unit/specs/store/actions/user.js @@ -1,7 +1,7 @@ import axios from 'axios'; import generateStore from 'client/store'; -describe('tasks actions', () => { +describe('user actions', () => { let store; beforeEach(() => { diff --git a/website/client-old/css/header.styl b/website/client-old/css/header.styl index 475a633fac..1593de5d2c 100644 --- a/website/client-old/css/header.styl +++ b/website/client-old/css/header.styl @@ -91,8 +91,8 @@ height: 10.5em width: 100% - // Covers avatars, health bar at 1005-768. Fix: - @media (max-width: 1005px) and (min-width: 768px) + // Covers avatars, health bar at 1005-768. Fix: + @media (max-width: 1005px) and (min-width: 768px) margin-top: 2.8em; // this is a wrapper for avatars in the header diff --git a/website/client-old/js/controllers/groupPlansCtrl.js b/website/client-old/js/controllers/groupPlansCtrl.js index 6fd7d96317..487037ce87 100644 --- a/website/client-old/js/controllers/groupPlansCtrl.js +++ b/website/client-old/js/controllers/groupPlansCtrl.js @@ -1,43 +1,43 @@ -"use strict"; - -/* - A controller to manage the Group Plans page - */ - -angular.module('habitrpg') - .controller("GroupPlansCtrl", ['$scope', '$window', 'Groups', 'Payments', - function($scope, $window, Groups, Payments) { - $scope.PAGES = { - BENEFITS: 'benefits', - CREATE_GROUP: 'create-group', - UPGRADE_GROUP: 'upgrade-group', - }; - $scope.activePage = $scope.PAGES.BENEFITS; - $scope.newGroup = { - type: 'guild', - privacy: 'private', - }; - $scope.PAYMENTS = { - AMAZON: 'amazon', - STRIPE: 'stripe', - }; - - $scope.changePage = function (page) { - $scope.activePage = page; - $window.scrollTo(0, 0); - }; - - $scope.newGroupIsReady = function () { - return !!$scope.newGroup.name; - }; - - $scope.createGroup = function () { - $scope.changePage($scope.PAGES.UPGRADE_GROUP); - }; - - $scope.upgradeGroup = function (paymentType) { - var subscriptionKey = 'group_monthly'; // @TODO: Get from content API? - if (paymentType === $scope.PAYMENTS.STRIPE) Payments.showStripe({subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup}); - if (paymentType === $scope.PAYMENTS.AMAZON) Payments.amazonPayments.init({type: 'subscription', subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup}); - }; - }]); +"use strict"; + +/* + A controller to manage the Group Plans page + */ + +angular.module('habitrpg') + .controller("GroupPlansCtrl", ['$scope', '$window', 'Groups', 'Payments', + function($scope, $window, Groups, Payments) { + $scope.PAGES = { + BENEFITS: 'benefits', + CREATE_GROUP: 'create-group', + UPGRADE_GROUP: 'upgrade-group', + }; + $scope.activePage = $scope.PAGES.BENEFITS; + $scope.newGroup = { + type: 'guild', + privacy: 'private', + }; + $scope.PAYMENTS = { + AMAZON: 'amazon', + STRIPE: 'stripe', + }; + + $scope.changePage = function (page) { + $scope.activePage = page; + $window.scrollTo(0, 0); + }; + + $scope.newGroupIsReady = function () { + return !!$scope.newGroup.name; + }; + + $scope.createGroup = function () { + $scope.changePage($scope.PAGES.UPGRADE_GROUP); + }; + + $scope.upgradeGroup = function (paymentType) { + var subscriptionKey = 'group_monthly'; // @TODO: Get from content API? + if (paymentType === $scope.PAYMENTS.STRIPE) Payments.showStripe({subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup}); + if (paymentType === $scope.PAYMENTS.AMAZON) Payments.amazonPayments.init({type: 'subscription', subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup}); + }; + }]); diff --git a/website/client-old/js/services/paymentServices.js b/website/client-old/js/services/paymentServices.js index 1b657b6c63..d45d82250f 100644 --- a/website/client-old/js/services/paymentServices.js +++ b/website/client-old/js/services/paymentServices.js @@ -326,11 +326,23 @@ function($rootScope, User, $http, Content) { paymentMethod = paymentMethod.toLowerCase(); } - var cancelUrl = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.settings.auth.apiToken; + var queryParams = { + _id: User.user._id, + apiToken: User.settings.auth.apiToken, + noRedirect: true, + }; + if (group) { - cancelUrl += '&groupId=' + group._id; + queryParams.groupId = group._id; } - window.location.href = cancelUrl; + + var cancelUrl = '/' + paymentMethod + '/subscribe/cancel?' + $.param(queryParams); + + $http.get(cancelUrl) + .then(function (success) { + alert(window.evn.t('paypalCanceled')); + window.location.href = '/'; + }); } Payments.encodeGift = function(uuid, gift) { diff --git a/website/client/README.md b/website/client/README.md index f553bbd3a4..cfc8206c1b 100644 --- a/website/client/README.md +++ b/website/client/README.md @@ -1,20 +1,20 @@ -#Running - - Open a terminal and type `npm run client:dev` - - Open a second terminal and type `npm start` - -#Preparation Reading -- Vue 2 (https://vuejs.org) - -- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/ - -- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/ - -- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling. - -The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html - -The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code - -The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch. - -So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly. +#Running + - Open a terminal and type `npm run client:dev` + - Open a second terminal and type `npm start` + +#Preparation Reading +- Vue 2 (https://vuejs.org) + +- Webpack (https://webpack.github.io/) is the build system and it includes plugins for code transformation, right now we have: BabelJS for ES6 transpilation, eslint for code style, less and postcss for css compilation. The code comes from https://github.com/vuejs-templates/webpack which is a Webpack template for Vue, with some small modifications to adapt it to our use case. Docs http://vuejs-templates.github.io/webpack/ + +- We’re using `.vue` files that make it possible to have HTML, JS and CSS for each component together in a single location. They’re implemented as a webpack plugin and the docs can be found here http://vue-loader.vuejs.org/en/ + +- SemanticUI is the UI framework http://semantic-ui.com/. So far I’ve only used the CSS part, it also has JS plugins but I’ve yet to use them. It supports theming so if it’s not too difficult we’ll want to customize the base theme with our own styles instead of writing CSS rules to override the original styling. + +The code is in `/website/client`. We’re using something very similar to Vuex (equivalent of React’s Redux) for state management http://vuex.vuejs.org/en/index.html + +The API is almost the same except that we don’t use mutations but only actions because it would make it difficult to work with common code + +The project is developed directly in the `develop` branch as long as we’ll be able to avoid splitting it into a different branch. + +So far most of the work has been on the template, so there’s no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it’s basically a Flux implementation: there’s a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly. diff --git a/website/client/app.vue b/website/client/app.vue index 7697892f88..2d0feaaf89 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -1,16 +1,20 @@ - - #app - app-menu - .container-fluid - app-header - router-view + router-view(v-if="!isUserLoggedIn || isStaticPage") + template(v-else) + #loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded") + p Loading... + template(v-else) + app-menu + .container-fluid + app-header + router-view - \ No newline at end of file + diff --git a/website/client/assets/images/auth/midground_foreground_extended.png b/website/client/assets/images/auth/midground_foreground_extended.png new file mode 100644 index 0000000000..e030b8a61b Binary files /dev/null and b/website/client/assets/images/auth/midground_foreground_extended.png differ diff --git a/website/client/assets/images/auth/midground_foreground_extended2.png b/website/client/assets/images/auth/midground_foreground_extended2.png new file mode 100644 index 0000000000..5976cd9681 Binary files /dev/null and b/website/client/assets/images/auth/midground_foreground_extended2.png differ diff --git a/website/client/assets/images/auth/repeatinghill_leftslope.png b/website/client/assets/images/auth/repeatinghill_leftslope.png new file mode 100644 index 0000000000..952123e055 Binary files /dev/null and b/website/client/assets/images/auth/repeatinghill_leftslope.png differ diff --git a/website/client/assets/images/auth/repeatinghill_rightslope.png b/website/client/assets/images/auth/repeatinghill_rightslope.png new file mode 100644 index 0000000000..cf7bbecc12 Binary files /dev/null and b/website/client/assets/images/auth/repeatinghill_rightslope.png differ diff --git a/website/client/assets/images/auth/seamless_mountains_demo.png b/website/client/assets/images/auth/seamless_mountains_demo.png new file mode 100644 index 0000000000..c9c2f4e9b7 Binary files /dev/null and b/website/client/assets/images/auth/seamless_mountains_demo.png differ diff --git a/website/client/assets/images/auth/seamless_stars_varied_opacity.png b/website/client/assets/images/auth/seamless_stars_varied_opacity.png new file mode 100644 index 0000000000..0abababa1b Binary files /dev/null and b/website/client/assets/images/auth/seamless_stars_varied_opacity.png differ diff --git a/website/client/assets/scss/categories.scss b/website/client/assets/scss/categories.scss new file mode 100644 index 0000000000..7317e9ee5e --- /dev/null +++ b/website/client/assets/scss/categories.scss @@ -0,0 +1,39 @@ +.category-box { + padding: 1em; + max-width: 400px; + position: absolute; + top: -480px; + padding: 2em; + border-radius: 2px; + background-color: $white; + box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1); +} + +.category-label { + min-width: 100px; + border-radius: 100px; + background-color: $gray-600; + padding: .5em; + display: inline-block; + margin-right: .5em; + font-size: 12px; + font-weight: 500; + line-height: 1.33; + text-align: center; + color: $gray-300; +} + +.category-select { + border-radius: 2px; + background-color: $white; + box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12); + padding: 1em; +} + +.category-select:hover { + cursor: pointer; +} + +.category-wrap { + margin-top: .5em; +} diff --git a/website/client/assets/scss/dragdrop.scss b/website/client/assets/scss/dragdrop.scss new file mode 100644 index 0000000000..056e605186 --- /dev/null +++ b/website/client/assets/scss/dragdrop.scss @@ -0,0 +1,9 @@ +[draggable] { + cursor: move; /* fallback if grab cursor is unsupported */ + cursor: grab; +} + +/* (Optional) Apply a "closed-hand" cursor during drag operation. */ +[draggable]:active { + cursor: grabbing; +} diff --git a/website/client/assets/scss/icon.scss b/website/client/assets/scss/icon.scss index 0686d9e2e5..21a7e7686d 100644 --- a/website/client/assets/scss/icon.scss +++ b/website/client/assets/scss/icon.scss @@ -1,13 +1,15 @@ .svg-icon { - display: inline-block; - width: 1em; - height: 1em; + display: block; stroke-width: 0; stroke: currentColor; fill: currentColor; transition: none !important; -} -.svg-icon * { - transition: none !important; + svg { + display: block; + } + + * { + transition: none !important; + } } \ No newline at end of file diff --git a/website/client/assets/scss/index.scss b/website/client/assets/scss/index.scss index b01742f67b..acd5b2a8ec 100644 --- a/website/client/assets/scss/index.scss +++ b/website/client/assets/scss/index.scss @@ -20,4 +20,7 @@ @import './popover'; @import './item'; @import './stats'; -@import './icon'; \ No newline at end of file +@import './icon'; +@import './task'; +@import './categories'; +@import './dragdrop'; diff --git a/website/client/assets/scss/page.scss b/website/client/assets/scss/page.scss index 65ca00f521..a888e6c537 100644 --- a/website/client/assets/scss/page.scss +++ b/website/client/assets/scss/page.scss @@ -3,7 +3,7 @@ html { } html, body { - height: 100%; + height: calc(100% - 56px); // 56px is the menu background: $gray-700; } diff --git a/website/client/assets/scss/task.scss b/website/client/assets/scss/task.scss new file mode 100644 index 0000000000..805cdec48d --- /dev/null +++ b/website/client/assets/scss/task.scss @@ -0,0 +1,114 @@ + .task { + // for editing rewards or when a task is created + &-purple { + background: $purple-300; + } + + &-worst { + background: $maroon-100; + &-control-habit { + background: darken($maroon-100, 12%); + } + + &-control-daily-todo { + background: $maroon-500; + } + } + + &-worse { + background: $red-100; + &-control-habit { + background: darken($red-100, 12%); + } + + &-control-daily-todo { + background: $red-500; + } + } + + &-bad { + background: $orange-100; + &-control-habit { + background: darken($orange-100, 12%); + } + + &-control-daily-todo { + background: $orange-500; + } + } + + &-neutral { + background: $yellow-50; + &-control-habit { + background: darken($yellow-50, 12%); + } + + &-control-daily-todo { + background: $yellow-500; + } + } + + &-good { + background: $green-10; + &-control-habit { + background: darken($green-10, 12%); + } + + &-control-daily-todo { + background: $green-500; + } + } + + &-better { + background: $blue-50; + &-control-habit { + background: darken($blue-50, 12%); + } + + &-control-daily-todo { + background: $blue-500; + } + } + + &-best { + background: $teal-50; + &-control-habit { + background: darken($teal-50, 12%); + } + + &-control-daily-todo { + background: $teal-500; + } + } + + &-reward { + background: rgba($yellow-500, 0.26); + } + + &-daily-todo-disabled { + background: $gray-500; + + &-control { + background: $gray-400; + color: $gray-200; + } + } + + &-daily-todo-content-disabled { + background: $gray-600; + + * { + color: $gray-300 !important; + } + } + + &-habit-disabled { + background: $gray-700; + color: rgba(0, 0, 0, 0.12); + + &-control { + color: rgba(0, 0, 0, 0.12) !important; + border: 1px solid rgba(0, 0, 0, 0.12); + } + } +} \ No newline at end of file diff --git a/website/client/assets/svg/calendar.svg b/website/client/assets/svg/calendar.svg new file mode 100644 index 0000000000..ba08c072e8 --- /dev/null +++ b/website/client/assets/svg/calendar.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/challenge.svg b/website/client/assets/svg/challenge.svg new file mode 100644 index 0000000000..1610d4bb2e --- /dev/null +++ b/website/client/assets/svg/challenge.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/check.svg b/website/client/assets/svg/check.svg new file mode 100644 index 0000000000..2ae3d23949 --- /dev/null +++ b/website/client/assets/svg/check.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/website/client/assets/svg/daily.svg b/website/client/assets/svg/daily.svg new file mode 100644 index 0000000000..f1e28a1858 --- /dev/null +++ b/website/client/assets/svg/daily.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/difficulty-star.svg b/website/client/assets/svg/difficulty-star.svg new file mode 100644 index 0000000000..9409886974 --- /dev/null +++ b/website/client/assets/svg/difficulty-star.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/down.svg b/website/client/assets/svg/down.svg new file mode 100644 index 0000000000..4d99b85f69 --- /dev/null +++ b/website/client/assets/svg/down.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/facebook.svg b/website/client/assets/svg/facebook.svg new file mode 100644 index 0000000000..314d9515ad --- /dev/null +++ b/website/client/assets/svg/facebook.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/google.svg b/website/client/assets/svg/google.svg new file mode 100644 index 0000000000..6fdd374c1b --- /dev/null +++ b/website/client/assets/svg/google.svg @@ -0,0 +1,8 @@ + diff --git a/website/client/assets/svg/gryphon.svg b/website/client/assets/svg/gryphon.svg new file mode 100644 index 0000000000..fdff439489 --- /dev/null +++ b/website/client/assets/svg/gryphon.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/habit.svg b/website/client/assets/svg/habit.svg new file mode 100644 index 0000000000..3a433ff31d --- /dev/null +++ b/website/client/assets/svg/habit.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/habitica-logo.svg b/website/client/assets/svg/habitica-logo.svg new file mode 100644 index 0000000000..e23bdf1981 --- /dev/null +++ b/website/client/assets/svg/habitica-logo.svg @@ -0,0 +1,7 @@ + diff --git a/website/client/assets/svg/message.svg b/website/client/assets/svg/message.svg new file mode 100644 index 0000000000..dfd7c12a9c --- /dev/null +++ b/website/client/assets/svg/message.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/negative.svg b/website/client/assets/svg/negative.svg new file mode 100644 index 0000000000..ca3e1d2e65 --- /dev/null +++ b/website/client/assets/svg/negative.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/website/client/assets/svg/next.svg b/website/client/assets/svg/next.svg new file mode 100644 index 0000000000..ce9bc639e9 --- /dev/null +++ b/website/client/assets/svg/next.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/positive.svg b/website/client/assets/svg/positive.svg new file mode 100644 index 0000000000..2488512400 --- /dev/null +++ b/website/client/assets/svg/positive.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/previous.svg b/website/client/assets/svg/previous.svg new file mode 100644 index 0000000000..08dc8cefa6 --- /dev/null +++ b/website/client/assets/svg/previous.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/qrCode.svg b/website/client/assets/svg/qrCode.svg new file mode 100644 index 0000000000..7d1ea3e9f5 --- /dev/null +++ b/website/client/assets/svg/qrCode.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/quest-background-border.svg b/website/client/assets/svg/quest-background-border.svg new file mode 100644 index 0000000000..81ae0b5bb3 --- /dev/null +++ b/website/client/assets/svg/quest-background-border.svg @@ -0,0 +1,36 @@ + diff --git a/website/client/assets/svg/quest.svg b/website/client/assets/svg/quest.svg new file mode 100644 index 0000000000..90ccf6ff77 --- /dev/null +++ b/website/client/assets/svg/quest.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/remove.svg b/website/client/assets/svg/remove.svg new file mode 100644 index 0000000000..a717d49915 --- /dev/null +++ b/website/client/assets/svg/remove.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/reward.svg b/website/client/assets/svg/reward.svg new file mode 100644 index 0000000000..52232a2259 --- /dev/null +++ b/website/client/assets/svg/reward.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/star.svg b/website/client/assets/svg/star.svg new file mode 100644 index 0000000000..8728bfb3ca --- /dev/null +++ b/website/client/assets/svg/star.svg @@ -0,0 +1,10 @@ + diff --git a/website/client/assets/svg/streak.svg b/website/client/assets/svg/streak.svg new file mode 100644 index 0000000000..e1314778b2 --- /dev/null +++ b/website/client/assets/svg/streak.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/tags.svg b/website/client/assets/svg/tags.svg new file mode 100644 index 0000000000..c4becad0b6 --- /dev/null +++ b/website/client/assets/svg/tags.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/website/client/assets/svg/todo.svg b/website/client/assets/svg/todo.svg new file mode 100644 index 0000000000..f7a89b4be6 --- /dev/null +++ b/website/client/assets/svg/todo.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/twitter.svg b/website/client/assets/svg/twitter.svg new file mode 100644 index 0000000000..3606bb754b --- /dev/null +++ b/website/client/assets/svg/twitter.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/assets/svg/up.svg b/website/client/assets/svg/up.svg new file mode 100644 index 0000000000..047b91b7f7 --- /dev/null +++ b/website/client/assets/svg/up.svg @@ -0,0 +1,3 @@ + diff --git a/website/client/components/appHeader.vue b/website/client/components/appHeader.vue index cc66416dd2..352ad1086d 100644 --- a/website/client/components/appHeader.vue +++ b/website/client/components/appHeader.vue @@ -1,7 +1,7 @@ #app-header.row member-details(:member="user", @click="$router.push({name: 'avatar'})") - .view-party + .view-party(v-if="user.party && user.party._id") // TODO button should open the party members modal router-link.btn.btn-primary(:active-class="''", :to="{name: 'party'}") {{ $t('viewParty') }} .party-members.d-flex(v-if="partyMembers && partyMembers.length > 1") @@ -59,6 +59,7 @@ .no-party { .small-text { color: $header-color; + flex-wrap: nowrap; } h3 { @@ -66,7 +67,7 @@ margin-bottom: 4px; } - button { + .btn { margin-top: 16px; } } @@ -75,10 +76,12 @@ diff --git a/website/client/components/appMenu.vue b/website/client/components/appMenu.vue index 872953d826..18e8542d90 100644 --- a/website/client/components/appMenu.vue +++ b/website/client/components/appMenu.vue @@ -14,7 +14,7 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }} router-link.nav-item(tag="li", :to="{name: 'shops'}", exact) a.nav-link(v-once) {{ $t('shops') }} - router-link.nav-item(tag="li", :to="{name: 'party'}", exact) + router-link.nav-item(tag="li", :to="{name: 'party'}") a.nav-link(v-once) {{ $t('party') }} router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}") a.nav-link(v-once) {{ $t('guilds') }} @@ -41,154 +41,154 @@ nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}") .svg-icon(v-html="icons.user") .dropdown-menu.dropdown-menu-right.user-dropdown - router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}") + router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}") h3 {{ user.profile.name }} span.small-text {{ $t('editAvatar') }} router-link.dropdown-item(:to="{name: 'inbox'}") {{ $t('inbox') }} router-link.dropdown-item(:to="{name: 'stats'}") {{ $t('stats') }} router-link.dropdown-item(:to="{name: 'achievements'}") {{ $t('achievements') }} router-link.dropdown-item(:to="{name: 'settings'}") {{ $t('settings') }} - router-link.dropdown-item(to="/logout") {{ $t('logout') }} + a.nav-link.dropdown-item(to="/", @click.prevent='logout()') {{ $t('logout') }} diff --git a/website/client/components/auth/registerLogin.vue b/website/client/components/auth/registerLogin.vue new file mode 100644 index 0000000000..7be5b3e007 --- /dev/null +++ b/website/client/components/auth/registerLogin.vue @@ -0,0 +1,230 @@ + +.form-wrapper + #top-background + .seamless_stars_varied_opacity_repeat + + #login-form + .text-center + div + .svg-icon.gryphon(v-html="icons.gryphon") + div + .svg-icon.habitica-logo(v-html="icons.habiticaIcon") + .form-group.row.text-center + .col-6 + .btn.btn-secondary.social-button(@click='socialAuth("facebook")', v-once) + .svg-icon.social-icon(v-html="icons.facebookIcon") + | {{this.registering ? $t('signUpWithSocial', {social: 'Facebook'}) : $t('loginWithSocial', {social: 'Facebook'})}} + .col-6 + .btn.btn-secondary.social-button(@click='socialAuth("google")', v-once) + .svg-icon.social-icon(v-html="icons.googleIcon") + | {{this.registering ? $t('signUpWithSocial', {social: 'Google'}) : $t('loginWithSocial', {social: 'Google'})}} + .form-group + label(for='usernameInput', v-once) {{$t('username')}} + input#usernameInput.form-control(type='text', :placeholder='$t("usernamePlaceholder")', v-model='username') + .form-group(v-if='registering') + label(for='emailInput', v-once) {{$t('email')}} + input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email') + .form-group + label(for='passwordInput', v-once) {{$t('password')}} + input#passwordInput.form-control(type='password', :placeholder='$t("passwordPlaceholder")', v-model='password') + .form-group(v-if='registering') + label(for='confirmPasswordInput', v-once) {{$t('confirmPassword')}} + input#confirmPasswordInput.form-control(type='password', :placeholder='$t("confirmPasswordPlaceholder")', v-model='passwordConfirm') + small.form-text(v-once) {{$t('termsAndAgreement')}} + .text-center + .btn.btn-info(@click='register()', v-if='registering', v-once) {{$t('joinHabitica')}} + .btn.btn-info(@click='login()', v-if='!registering', v-once) {{$t('login')}} + + #bottom-background + .seamless_mountains_demo_repeat + .midground_foreground_extended2 + + + + + diff --git a/website/client/components/guilds/createPartyModal.vue b/website/client/components/guilds/createPartyModal.vue new file mode 100644 index 0000000000..0e06046833 --- /dev/null +++ b/website/client/components/guilds/createPartyModal.vue @@ -0,0 +1,121 @@ + +b-modal#create-party-modal(title="Empty", size='lg', hide-footer=true) + .header-wrap(slot="modal-header") + h2 Image Here + .row + .col-12.text-center + h2(v-once) {{$t('playInPartyTitle')}} + p(v-once) {{$t('playInPartyDescription')}} + .row.grey-row + .col-6.text-center + img + h3(v-once) {{$t('startYourOwnPartyTitle')}} + p(v-once) {{$t('startYourOwnPartyDescription')}} + button.btn.btn-primary(v-once) {{$t('createParty')}} + .col-6 + div.text-center + img + h3(v-once) {{$t('wantToJoinPartyTitle')}} + p(v-once) {{$t('wantToJoinPartyDescription')}} + button.btn.btn-primary(v-once, @click='shareUserIdShown = !shareUserIdShown') {{$t('shartUserId')}} + .share-userid-options(v-if="shareUserIdShown") + .option-item(v-once) + .svg-icon(v-html="icons.copy") + | {{$t('copy')}} + .option-item(v-once) + .svg-icon(v-html="icons.greyBadge") + | {{$t('lookingForGroup')}} + .option-item(v-once) + .svg-icon(v-html="icons.qrCode") + | {{$t('qrCode')}} + .option-item(v-once) + .svg-icon.facebook(v-html="icons.facebook") + | {{$t('facebook')}} + .option-item(v-once) + .svg-icon(v-html="icons.twitter") + | {{$t('twitter')}} + + + + + diff --git a/website/client/components/guilds/groupFormModal.vue b/website/client/components/guilds/groupFormModal.vue index 48f4871f36..d023aaa917 100644 --- a/website/client/components/guilds/groupFormModal.vue +++ b/website/client/components/guilds/groupFormModal.vue @@ -1,10 +1,10 @@ - b-modal#guild-form(:title="title", :hide-footer="true") + b-modal#guild-form(:title="title", :hide-footer="true", size='lg') form(@submit.stop.prevent="submit") .form-group label strong(v-once) {{$t('name')}}* - b-form-input(type="text", placeholder="$t('newGuildPlaceHolder')", v-model="newGuild.name") + b-form-input(type="text", :placeholder="$t('newGuildPlaceHolder')", v-model="newGuild.name") .form-group(v-if='newGuild.id') label @@ -29,7 +29,7 @@ span.custom-control-description(v-once) {{ $t('guildLeaderCantBeMessaged') }} br - label.custom-control.custom-checkbox + label.custom-control.custom-checkbox(v-if='!creatingParty') input.custom-control-input(type="checkbox", v-model="newGuild.privateGuild") span.custom-control-indicator span.custom-control-description(v-once) {{ $t('privateGuild') }} @@ -37,7 +37,7 @@ .svg-icon(v-html='icons.information') br - label.custom-control.custom-checkbox + label.custom-control.custom-checkbox(v-if='!creatingParty') input.custom-control-input(type="checkbox", v-model="newGuild.allowGuildInvationsFromNonMembers") span.custom-control-indicator span.custom-control-description(v-once) {{ $t('allowGuildInvationsFromNonMembers') }} @@ -46,14 +46,18 @@ label strong(v-once) {{$t('description')}}* div.description-count {{charactersRemaining}} {{ $t('charactersRemaining') }} - b-form-input(type="text", textarea :placeholder="$t('guildDescriptionPlaceHolder')", v-model="newGuild.description") + b-form-input(type="text", textarea :placeholder="creatingParty ? $t('partyDescriptionPlaceHolder') : $t('guildDescriptionPlaceHolder')", v-model="newGuild.description") - .form-group(v-if='newGuild.id') + .form-group(v-if='newGuild.id && !creatingParty') label strong(v-once) {{$t('guildInformation')}}* - b-form-input(type="text", textarea :placeholder="$t('guildInformationPlaceHolder')", v-model="newGuild.guildInformation") + b-form-input(type="text", textarea, :placeholder="$t('guildInformationPlaceHolder')", v-model="newGuild.guildInformation") - .form-group(style='position: relative;') + .form-group(v-if='creatingParty && !newGuild.id') + span + toggleSwitch(:label="$t('inviteMembersNow')", v-model='inviteMembers') + + .form-group(style='position: relative;', v-if='!creatingParty') label strong(v-once) {{$t('categories')}}* div.category-wrap(@click.prevent="toggleCategorySelect") @@ -70,13 +74,26 @@ span.custom-control-description(v-once) {{ $t(group.label) }} button.btn.btn-primary(@click.prevent="toggleCategorySelect") {{$t('close')}} + .form-group(v-if='inviteMembers && !newGuild.id') + label + strong(v-once) Invite via Email or User ID + p Invite users via a valid email or 36-digit User ID. If an email isn’t registered yet, we’ll invite them to join. + + div + div(v-for='(member, index) in membersToInvite') + input(type='text', v-model='member.value') + button(@click.prevent='removeMemberToInvite(index)') Remove + div + input(type='text', placeholder='Email address or User ID', v-model='newMemberToInvite.value') + button(@click.prevent='addMemberToInvite()') Add + .form-group.text-center - div.item-with-icon + div.item-with-icon(v-if='!creatingParty') .svg-icon(v-html="icons.gem") span.count 4 - button.btn.btn-primary.btn-md(v-if='!newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ $t('createGuild') }} - button.btn.btn-primary.btn-md(v-if='newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ $t('updateGuild') }} - .gem-description(v-once) {{ $t('guildGemCostInfo') }} + button.btn.btn-primary.btn-md(v-if='!newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ creatingParty ? $t('createParty') : $t('createGuild') }} + button.btn.btn-primary.btn-md(v-if='newGuild.id', :disabled='!newGuild.name || !newGuild.description') {{ creatingParty ? $t('updateParty') : $t('updateGuild') }} + .gem-description(v-once, v-if='!creatingParty') {{ $t('guildGemCostInfo') }} diff --git a/website/client/components/guilds/membersModal.vue b/website/client/components/guilds/membersModal.vue index 106a5698f6..274d84cdf7 100644 --- a/website/client/components/guilds/membersModal.vue +++ b/website/client/components/guilds/membersModal.vue @@ -1,6 +1,9 @@ div - button.btn.btn-primary(b-btn, @click="$root.$emit('show::modal','members-modal')") {{ $t('viewMembers') }} + .item-with-icon(@click="$root.$emit('show::modal','members-modal')") + .svg-icon.shield(v-html="icons.goldGuildBadgeIcon") + span.number {{group.memberCount}} + div(v-once) {{ $t('members') }} b-modal#members-modal(:title="$t('createGuild')", size='lg') .header-wrap(slot="modal-header") @@ -19,24 +22,25 @@ div b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)', :key='sortOption.value') {{sortOption.text}} .row(v-for='member in members', :key='member', ) .col-8.offset-1 - member-details(:user='member') + member-details(:member='member') .col-3.actions b-dropdown(:text="$t('sort')", right=true) b-dropdown-item(@click='sort(option.value)') - img.action-icon(src='~assets/members/remove.svg') + .svg-icon(v-html="icons.removeIcon") | {{$t('removeMember')}} b-dropdown-item(@click='sort(option.value)') - img.action-icon(src='~assets/members/message.svg') + .svg-icon(v-html="icons.messageIcon") | {{$t('sendMessage')}} b-dropdown-item(@click='sort(option.value)') - img.action-icon(src='~assets/members/star.svg') + .svg-icon(v-html="icons.starIcon") | {{$t('promoteToLeader')}} b-dropdown-item(@click='sort(option.value)') - img.action-icon(src='~assets/members/star.svg') + .svg-icon(v-html="icons.starIcon") | {{$t('addManager')}} b-dropdown-item(@click='sort(option.value)') - img.action-icon(src='~assets/members/remove.svg') + .svg-icon(v-html="icons.removeIcon") | {{$t('removeManager2')}} + .row-fluid.gradient b-modal#remove-member(:title="$t('confirmRemoveMember')") button(@click='confirmRemoveMember(member)', v-once) {{$t('remove')}} @@ -45,7 +49,7 @@ div button(@click='confirmRemoveMember(member)', v-once) {{$t('remove')}} - diff --git a/website/client/components/guilds/publicGuildItem.vue b/website/client/components/guilds/publicGuildItem.vue index 815fe2948f..40cf650d99 100644 --- a/website/client/components/guilds/publicGuildItem.vue +++ b/website/client/components/guilds/publicGuildItem.vue @@ -35,20 +35,6 @@ box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1); margin-bottom: 1rem; - .category-label { - min-width: 100px; - border-radius: 100px; - background-color: $gray-600; - padding: .5em; - display: inline-block; - margin-right: .5em; - font-size: 12px; - font-weight: 500; - line-height: 1.33; - text-align: center; - color: $gray-300; - } - .recommend-text { font-size: 12px; font-style: italic; diff --git a/website/client/components/guilds/startQuestModal.vue b/website/client/components/guilds/startQuestModal.vue new file mode 100644 index 0000000000..14834a3b32 --- /dev/null +++ b/website/client/components/guilds/startQuestModal.vue @@ -0,0 +1,156 @@ + + b-modal#start-quest-modal(title="Empty", size='md', hide-footer=true, v-if='questData') + .quest-image(:class="'quest_' + questData.key") + h2 {{questData.text()}} + //- span by: Keith Holliday @TODO: Add author + p {{questData.notes()}} + div.quest-details + div(v-if=' questData.collect') + Strong {{$t('collect')}}: + span(v-for="(value, key, index) in questData.collect") + | {{$t('collectionItems', { number: questData.collect[key].count, items: questData.collect[key].text() })}} + div + Strong {{$t('collect')}}: + span + .svg-icon(v-html="icons.difficultyStarIcon") + div + button.btn.btn-primary(@click='questInit()') {{$t('inviteToPartyOrQuest')}} + div + p {{$t('inviteInformation')}} + .side-panel + h4.text-center {{$t('rewards')}} + .box + .svg-icon.rewards-icon(v-html="icons.starIcon") + strong {{questData.drop.exp}} {{$t('experience')}} + .box + .svg-icon.rewards-icon(v-html="icons.goldIcon") + strong {{questData.drop.gp}} {{$t('gold')}} + h4.text-center(v-if='questData.drop.items') {{$t('questOwnerRewards')}} + .box(v-for='item in questData.drop.items') + .rewards-icon(v-if='item.type === "quest"', :class="'quest_' + item.key") + .drop-rewards-icon(v-if='item.type === "gear"', :class="'shop_' + item.key") + strong.quest-reward-text {{item.text()}} + + + + + diff --git a/website/client/components/inventory/drawer.vue b/website/client/components/inventory/drawer.vue index 7625ecd2e6..d82b1be2dc 100644 --- a/website/client/components/inventory/drawer.vue +++ b/website/client/components/inventory/drawer.vue @@ -91,8 +91,7 @@ } .drawer-slider { - padding: 12px 0 0 24px; - margin-left: -24px; + padding: 12px 0 0 0; overflow-x: auto; overflow-y: hidden; white-space: nowrap; diff --git a/website/client/components/inventory/item.vue b/website/client/components/inventory/item.vue index c20d0def70..971e382e8f 100644 --- a/website/client/components/inventory/item.vue +++ b/website/client/components/inventory/item.vue @@ -16,13 +16,16 @@ b-popover( .item-wrapper .item slot(name="itemBadge", :item="item") - span.item-content(:class="itemContentClass") + span.item-content( + :class="itemContentClass", + :draggable="draggable", + @dragstart="onDrag" + ) span.item-label(v-if="label") {{ label }} diff --git a/website/client/components/inventory/stable.vue b/website/client/components/inventory/stable.vue deleted file mode 100644 index a7e6fd673f..0000000000 --- a/website/client/components/inventory/stable.vue +++ /dev/null @@ -1,131 +0,0 @@ - -.row - .col-2.standard-sidebar - .form-group - input.form-control(type="text", :placeholder="$t('search')") - - .form - h3(v-once) {{ $t('filter') }} - .form-group - .form-check - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - strong {{ $t('pets') }} - .form-check.nested-field - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - span {{ $t('hatchingPotions') }} - .form-check.nested-field - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - span {{ $t('quest') }} - .form-check.nested-field - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - span {{ $t('special') }} - .form-group - .form-check - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - strong {{ $t('mounts') }} - .form-check.nested-field - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - span {{ $t('hatchingPotions') }} - .form-check.nested-field - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - span {{ $t('quest') }} - .form-check.nested-field - label.form-check-label(v-once) - input.form-check-input(type="checkbox") - span {{ $t('special') }} - - .col-10.standard-page - h4 Pets - .inventory-item-container(v-for="pet in dropPets") - .PixelPaw - .btn.btn-secondary.d-block(@click="open.dropPets = !open.dropPets") {{ open.dropPets ? 'Close' : 'Open' }} - - h2 Magic Potions Pets - .inventory-item-container(v-for="pet in magicPets") - .PixelPaw - .btn.btn-secondary.d-block(@click="open.magicPets = !open.magicPets") {{ open.magicPets ? 'Close' : 'Open' }} - - h2 Quest Pets - .inventory-item-container(v-for="pet in questPets") - .PixelPaw - .btn.btn-secondary.d-block(@click="open.questPets = !open.questPets") {{ open.questPets ? 'Close' : 'Open' }} - - h2 Rare Pets - .inventory-item-container(v-for="pet in rarePets") - .PixelPaw - .btn.btn-secondary.d-block(@click="open.rarePets = !open.rarePets") {{ open.rarePets ? 'Close' : 'Open' }} - - h2 Mounts - h2 Quest Mounts - h2 Rare Mounts - - - - - diff --git a/website/client/components/inventory/stable/countBadge.vue b/website/client/components/inventory/stable/countBadge.vue new file mode 100644 index 0000000000..4f42bf7097 --- /dev/null +++ b/website/client/components/inventory/stable/countBadge.vue @@ -0,0 +1,33 @@ + +span.badge.badge-pill.badge-item.badge-count( + v-if="show", +) {{ count }} + + + + + diff --git a/website/client/components/inventory/stable/drawerSlider.vue b/website/client/components/inventory/stable/drawerSlider.vue new file mode 100644 index 0000000000..d54a27014d --- /dev/null +++ b/website/client/components/inventory/stable/drawerSlider.vue @@ -0,0 +1,174 @@ + + div.slider-root( + v-bind:class="{'scrollButtonsVisible': scrollButtonsVisible}", + ) + div.slider-button-area.left-button( + v-if="scrollButtonsVisible", + @mousedown.left="lastPage" + ) + a.slider-button + .svg-icon(v-html="icons.previous") + div.slider-button-area.right-button( + v-if="scrollButtonsVisible", + @mousedown.left="nextPage" + ) + a.slider-button + .svg-icon(v-html="icons.next") + + // 120 = width of the left/right buttons + div.sliding-content(v-resize="500", @resized="currentWidth = $event.width - 120") + .items.items-one-line + template(v-for="item in pages[currentPage]") + div.vertical-divider(v-if="item.ofNextPage") + + slot( + name="item", + :item="item", + ) + + + + + diff --git a/website/client/components/inventory/stable/foodItem.vue b/website/client/components/inventory/stable/foodItem.vue new file mode 100644 index 0000000000..7861844f2a --- /dev/null +++ b/website/client/components/inventory/stable/foodItem.vue @@ -0,0 +1,48 @@ + +b-popover( + :triggers="['hover']", + :placement="'top'", +) + span(slot="content") + h4.popover-content-title {{ item.text() }} + div.popover-content-text(v-html="item.notes()") + + .item-wrapper + .item + countBadge( + :show="true", + :count="itemCount" + ) + span.item-content( + :class="'Pet_Food_'+item.key", + v-drag.food="item.key" + ) + + + diff --git a/website/client/components/inventory/stable/index.vue b/website/client/components/inventory/stable/index.vue new file mode 100644 index 0000000000..fc9743d064 --- /dev/null +++ b/website/client/components/inventory/stable/index.vue @@ -0,0 +1,721 @@ + + .row.stable + .standard-sidebar + div + b-popover( + :triggers="['hover']", + :placement="'right'" + ) + span(slot="content") + h4.popover-content-title(v-once) {{ $t('mattBoch') }} + .popover-content-text(v-once) {{ $t('mattBochText1') }} + + div.npc_matt + + .form-group + input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')") + + .form + h2(v-once) {{ $t('filter') }} + h3(v-once) {{ $t('pets') }} + .form-group + .form-check( + v-for="petGroup in petGroups", + v-if="viewOptions[petGroup.key].animalCount != 0", + :key="petGroup.key" + ) + label.custom-control.custom-checkbox + input.custom-control-input(type="checkbox", v-model="viewOptions[petGroup.key].selected") + span.custom-control-indicator + span.custom-control-description(v-once) {{ petGroup.label }} + h3(v-once) {{ $t('mounts') }} + .form-group + .form-check( + v-for="mountGroup in mountGroups", + v-if="viewOptions[mountGroup.key].animalCount != 0", + :key="mountGroup.key" + ) + label.custom-control.custom-checkbox + input.custom-control-input(type="checkbox", v-model="viewOptions[mountGroup.key].selected") + span.custom-control-indicator + span.custom-control-description(v-once) {{ mountGroup.label }} + + div.form-group.clearfix + h3.float-left Hide Missing + toggle-switch.float-right.hideMissing( + :label="''", + :checked="hideMissing", + @change="updateHideMissing" + ) + + .standard-page(v-resize="500", @resized="availableContentWidth = $event.width - 48") + .clearfix + h1.float-left.mb-0.page-header(v-once) {{ $t('stable') }} + + div.float-right + span.dropdown-label {{ $t('sortBy') }} + b-dropdown(:text="$t(selectedSortBy)", right=true) + b-dropdown-item( + v-for="sort in sortByItems", + @click="selectedSortBy = sort", + :active="selectedSortBy === sort", + :key="sort" + ) {{ $t(sort) }} + + h2 + | {{ $t('pets') }} + | + span.badge.badge-pill.badge-default {{countOwnedAnimals(petGroups[0], 'pet')}} + + div( + v-for="petGroup in petGroups", + v-if="viewOptions[petGroup.key].selected", + :key="petGroup.key" + ) + h4(v-if="viewOptions[petGroup.key].animalCount != 0") {{ petGroup.label }} + + div.items + div( + v-for="pet in pets(petGroup, viewOptions[petGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)", + :key="pet.key", + v-drag.drop.food="pet.key", + @dragover="onDragOver($event, pet)", + @dropped="onDrop($event, pet)", + ) + petItem( + :item="pet", + :itemContentClass="getPetItemClass(pet)", + :popoverPosition="'top'", + :progress="pet.progress", + :emptyItem="!pet.isOwned()", + :showPopover="pet.isOwned() || pet.isHatchable()", + @hatchPet="hatchPet", + ) + span(slot="popoverContent") + div(v-if="pet.isOwned()") + h4.popover-content-title {{ pet.name }} + div.hatchablePopover(v-else-if="pet.isHatchable()") + h4.popover-content-title {{ pet.name }} + div.popover-content-text(v-html="$t('haveHatchablePet', { potion: pet.potionName, egg: pet.eggName })") + div.potionEggGroup + div.potionEggBackground + div(:class="'Pet_HatchingPotion_'+pet.potionKey") + div.potionEggBackground + div(:class="'Pet_Egg_'+pet.eggKey") + + template(slot="itemBadge", scope="ctx") + starBadge( + :selected="ctx.item.key === currentPet", + :show="ctx.item.isOwned()", + @click="selectPet(ctx.item)", + ) + + .btn.btn-show-more( + @click="viewOptions[petGroup.key].open = !viewOptions[petGroup.key].open", + v-if="viewOptions[petGroup.key].animalCount != 0" + ) {{ $t(viewOptions[petGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: petGroup.label, type: $t('pets')}) }} + + h2 + | {{ $t('mounts') }} + | + span.badge.badge-pill.badge-default {{countOwnedAnimals(mountGroups[0], 'mount')}} + + div( + v-for="mountGroup in mountGroups", + v-if="viewOptions[mountGroup.key].selected", + :key="mountGroup.key" + ) + h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }} + + div.items + item( + v-for="mount in mounts(mountGroup, viewOptions[mountGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)", + :item="mount", + :itemContentClass="mount.isOwned() ? ('Mount_Icon_' + mount.key) : 'PixelPaw greyedOut'", + :key="mount.key", + :popoverPosition="'top'" + ) + span(slot="popoverContent") + h4.popover-content-title {{ mount.name }} + template(slot="itemBadge", scope="ctx") + starBadge( + :selected="ctx.item.key === currentMount", + :show="mount.isOwned()", + @click="selectMount(ctx.item)", + ) + + .btn.btn-show-more( + @click="viewOptions[mountGroup.key].open = !viewOptions[mountGroup.key].open", + v-if="viewOptions[mountGroup.key].animalCount != 0" + ) {{ $t(viewOptions[mountGroup.key].open ? 'showLessAnimals' : 'showAllAnimals', { color: mountGroup.label, type: $t('mounts')}) }} + + drawer( + :title="$t('quickInventory')", + :errorMessage="(!hasDrawerTabItems(selectedDrawerTab)) ? $t('noFoodAvailable') : null" + ) + div(slot="drawer-header") + .drawer-tab-container + .drawer-tab.text-right + a.drawer-tab-text( + @click="selectedDrawerTab = 0", + :class="{'drawer-tab-text-active': selectedDrawerTab === 0}", + ) {{ drawerTabs[0].label }} + .clearfix + .drawer-tab.float-left + a.drawer-tab-text( + @click="selectedDrawerTab = 1", + :class="{ 'drawer-tab-text-active': selectedDrawerTab === 1 }", + ) {{ drawerTabs[1].label }} + + b-popover( + :triggers="['hover']", + :placement="'top'" + ) + span(slot="content") + .popover-content-text Test Popover + + div.float-right What does my pet like to eat? + + + drawer-slider( + :items="drawerTabs[selectedDrawerTab].items", + :scrollButtonsVisible="hasDrawerTabItems(selectedDrawerTab)", + slot="drawer-slider", + :itemWidth=94, + :itemMargin=24, + ) + template(slot="item", scope="ctx") + foodItem( + :item="ctx.item", + :itemCount="userItems.food[ctx.item.key]", + ) + + b-modal#welcome-modal( + :ok-only="true", + :ok-title="$t('gotIt')", + :visible="!hideDialog", + :hide-header="true" + ) + div.content + div.npc_matt + h1.page-header(v-once) {{ $t('welcomeStable') }} + div.content-text(v-once) {{ $t('welcomeStableText') }} + + + + + + diff --git a/website/client/components/inventory/stable/petItem.vue b/website/client/components/inventory/stable/petItem.vue new file mode 100644 index 0000000000..9d071bf959 --- /dev/null +++ b/website/client/components/inventory/stable/petItem.vue @@ -0,0 +1,117 @@ + +b-popover( + :triggers="[showPopover?'hover':'']", + :placement="popoverPosition", +) + span(slot="content") + slot(name="popoverContent", :item="item") + + .item-wrapper + .item( + :class="{'item-empty': emptyItem}", + @mouseup="holdStop", + @mouseleave="holdStop", + @mousedown.left="holdStart" + ) + slot(name="itemBadge", :item="item") + span.item-content(:class="itemContentClass") + span.pet-progress-background(v-if="progress > 0") + div.pet-progress-bar(v-bind:style="{width: 100 * progress/50 + '%' }") + span.pet-progress-background(v-if="holdProgress > 0") + div.pet-progress-bar.hold(v-bind:style="{width: 100 * holdProgress/5 + '%' }") + span.item-label(v-if="label") {{ label }} + + + + + diff --git a/website/client/components/static/home.vue b/website/client/components/static/home.vue new file mode 100644 index 0000000000..c6e85f8eb9 --- /dev/null +++ b/website/client/components/static/home.vue @@ -0,0 +1,30 @@ + +nav + router-link(:to="{name: 'login'}") Login + router-link(:to="{name: 'register'}") Register + + + + diff --git a/website/client/components/task.vue b/website/client/components/task.vue deleted file mode 100644 index 7256b4ba03..0000000000 --- a/website/client/components/task.vue +++ /dev/null @@ -1,33 +0,0 @@ - -li - ul - li - strong {{task.text}} - li(v-if="task.type === 'habit'") up: {{task.up}}, down: {{task.down}} - li value: {{task.value}} - template(v-if="task.type === 'daily' || task.type === 'todo'") - li completed: {{task.completed}} - li - span checklist - ul - li(v-for="checklist in task.checklist") {{checklist.text}} - template(v-if="task.type === 'daily'") - li streak: {{task.streak}} - li repeat: {{task.repeat}} - li(v-if="task.type === 'todo'") due date: {{task.date}} - li attribute {{task.attribute}} - li difficulty {{task.priority}} - li tags {{getTagsFor(task)}} - - - \ No newline at end of file diff --git a/website/client/components/tasks/column.vue b/website/client/components/tasks/column.vue new file mode 100644 index 0000000000..e26edd8455 --- /dev/null +++ b/website/client/components/tasks/column.vue @@ -0,0 +1,194 @@ + +.tasks-column + .d-flex + h2.tasks-column-title(v-once) {{ $t(types[type].label) }} + .filters.d-flex.justify-content-end + .filter.small-text( + v-for="filter in types[type].filters", + :class="{active: activeFilter.label === filter.label}", + @click="activeFilter = filter", + ) {{ $t(filter.label) }} + .tasks-list + task(v-for="task in tasks[`${type}s`]", :key="task.id", :task="task", v-if="activeFilter.filter(task)") + .bottom-gradient + .column-background(v-if="isUser === true", :class="{'initial-description': tasks[`${type}s`].length === 0}") + .svg-icon(v-html="icons[type]", :class="`icon-${type}`", v-once) + h3(v-once) {{$t('theseAreYourTasks', {taskType: `${type}s`})}} + .small-text {{$t(`${type}sDesc`)}} + + + + + diff --git a/website/client/components/tasks/task.vue b/website/client/components/tasks/task.vue new file mode 100644 index 0000000000..f0348269c7 --- /dev/null +++ b/website/client/components/tasks/task.vue @@ -0,0 +1,293 @@ + +.task.d-flex + // Habits left side control + .left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up") + .task-control.habit-control(:class="controlClass.up + '-control-habit'") + .svg-icon.positive(v-html="icons.positive") + // Dailies and todos left side control + .left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass") + .task-control.daily-todo-control(:class="controlClass + '-control-daily-todo'") + .svg-icon.check(v-html="icons.check", v-if="task.completed") + // Task title, description and icons + .task-content(:class="contentClass") + h3.task-title(:class="{ 'has-notes': task.notes }") {{task.text}} + .task-notes.small-text {{task.notes}} + .icons.small-text.d-flex.align-items-center + .d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}") + .svg-icon.calendar(v-html="icons.calendar") + span {{dueIn}} + .icons-right.d-flex.justify-content-end + .d-flex.align-items-center(v-if="showStreak") + .svg-icon.streak(v-html="icons.streak") + span(v-if="task.type === 'daily'") {{task.streak}} + span(v-if="task.type === 'habit'") + span.m-0(v-if="task.up") +{{task.counterUp}} + span.m-0(v-if="task.up && task.down") | + span.m-0(v-if="task.down") -{{task.counterDown}} + .d-flex.align-items-center(v-if="task.challenge && task.challenge.id") + .svg-icon.challenge(v-html="icons.challenge") + b-popover.tags-popover.no-span-margin( + :triggers="['hover']", + :placement="'bottom'", + :popover-style="{'max-width': '1000px'}", + ) + .d-flex.align-items-center(slot="content") + .tags-popover-title(v-once) {{ `${$t('tags')}:` }} + .tag-label(v-for="tag in getTagsFor(task)") {{tag}} + .d-flex.align-items-center(v-if="task.tags && task.tags.length > 0") + .svg-icon.tags(v-html="icons.tags") + + // Habits right side control + .right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down") + .task-control.habit-control(:class="controlClass.down + '-control-habit'") + .svg-icon.negative(v-html="icons.negative") + // Rewards right side control + .right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass") + .svg-icon(v-html="icons.gold") + .small-text {{task.value}} + + + + + + + \ No newline at end of file diff --git a/website/client/components/tasks/user.vue b/website/client/components/tasks/user.vue new file mode 100644 index 0000000000..41535f70a2 --- /dev/null +++ b/website/client/components/tasks/user.vue @@ -0,0 +1,56 @@ + +.row.user-tasks-page + .col-12 + .row.tasks-navigation + .col-4.offset-4 + input.form-control.input-search(type="text", :placeholder="$t('search')") + .col-1.offset-3 + button.btn.btn-success(v-once) + .svg-icon.positive(v-html="icons.positive") + | {{ $t('create') }} + .row.tasks-columns + task-column.col-3(v-for="column in columns", :type="column", :key="column", :isUser="true") + + + + + diff --git a/website/client/components/ui/toggleSwitch.vue b/website/client/components/ui/toggleSwitch.vue index d1eda4074b..779f296aa3 100644 --- a/website/client/components/ui/toggleSwitch.vue +++ b/website/client/components/ui/toggleSwitch.vue @@ -4,8 +4,9 @@ .toggle-switch.float-left input.toggle-switch-checkbox( type='checkbox', :id="id", - @change="$emit('change', $event.target.checked)", - :checked="checked", + @change="handleChange", + :checked="isChecked", + :value="value", ) label.toggle-switch-label(:for="id") span.toggle-switch-inner @@ -13,88 +14,88 @@ diff --git a/website/client/components/userTasks.vue b/website/client/components/userTasks.vue deleted file mode 100644 index 3a46db218e..0000000000 --- a/website/client/components/userTasks.vue +++ /dev/null @@ -1,163 +0,0 @@ - -.row - .col-12 - .row - .col-3.p-4 - h3 Input - input.form-control(type="text", placeholder="Placeholder") - .col-3.p-4 - h3 Input Disabled - input.form-control(type="text", placeholder="Placeholder", disabled) - .col-3.p-4 - h3 Input With Icon - input.form-control.input-search(type="text", placeholder="Placeholder") - .col-3.p-4 - h3 Input With Icon Disabled - input.form-control.input-search(type="text", placeholder="Placeholder", disabled) - .col-3.p-4 - h3 Input Valid - input.form-control.input-valid(type="text", placeholder="Placeholder") - .col-3.p-4 - h3 Input Invalid - input.form-control.input-invalid(type="text", placeholder="Placeholder") - .row - .col-6.p-4 - h3 Textarea - textarea.form-control(rows="5", cols="50") - .col-6.p-4 - h3 Textarea Disabled - textarea.form-control(disabled, rows="10", cols="50") - .row - .col-2.p-4 - toggleSwitch(label="Toggle Switch") - .row - .col-3.p-4 - h3 Checkbox - label.custom-control.custom-checkbox - input.custom-control-input(type='checkbox') - span.custom-control-indicator - span.custom-control-description Check this custom checkbox - .col-3.p-4 - h3 Checkbox Disabled Checked - label.custom-control.custom-checkbox - input.custom-control-input(type='checkbox', disabled, checked) - span.custom-control-indicator - span.custom-control-description Check this custom checkbox - .col-3.p-4 - h3 Checkbox Disabled Not Checked - label.custom-control.custom-checkbox - input.custom-control-input(type='checkbox', disabled) - span.custom-control-indicator - span.custom-control-description Check this custom checkbox - .col-6.p-4 - h3 Radio Button - form - label.custom-control.custom-radio - input#radio1.custom-control-input(name='radio', type='radio') - span.custom-control-indicator - span.custom-control-description Toggle this custom radio - label.custom-control.custom-radio - input#radio2.custom-control-input(name='radio', type='radio') - span.custom-control-indicator - span.custom-control-description Toggle this custom radio - .col-3.p-4 - h3 Radio Button Disabled Checked - form - label.custom-control.custom-radio - input#radio3.custom-control-input(name='radio', type='radio', disabled, checked) - span.custom-control-indicator - span.custom-control-description Toggle this custom radio - .col-3.p-4 - h3 Radio Button Disabled Not Checked - form - label.custom-control.custom-radio - input#radio3.custom-control-input(name='radio', type='radio', disabled) - span.custom-control-indicator - span.custom-control-description Toggle this custom radio - .row - .col-3.p-4 - h3 Main Button - button.btn.btn-primary Button - .col-3.p-4 - h3 Secondary Button - button.btn.btn-secondary Button - .col-3.p-4 - h3 Green Button - button.btn.btn-success Button - .col-3.p-4 - h3 Blue Button - button.btn.btn-info Button - .col-3.p-4 - h3 Red Button - button.btn.btn-danger Button - .row - .col-3.p-4 - h3 Main Button Disabled - button.btn.btn-primary(disabled=true) Button - .col-3.p-4 - h3 Secondary Button Disabled - button.btn.btn-secondary(disabled=true) Button - .col-3.p-4 - h3 Green Button Disabled - button.btn.btn-success(disabled=true) Button - .col-3.p-4 - h3 Blue Button Disabled - button.btn.btn-info(disabled=true) Button - .col-3.p-4 - h3 Red Button Disabled - button.btn.btn-danger(disabled=true) Button - .row - .col-6.p-4 - h3 Dropdown Menu - b-dropdown(text="Menu", right=false) - b-dropdown-item(href="#") Menu item 1 - b-dropdown-item(href="#") Menu item 2 - b-dropdown-item(href="#") Menu item 3 - b-dropdown-item(href="#") Menu item 4 - .col-6.p-4 - h3 Dropdown Menu Disabled - b-dropdown(text="Menu", disabled) - b-dropdown-item(href="#") Menu item 1 - b-dropdown-item(href="#") Menu item 2 - b-dropdown-item(href="#") Menu item 3 - b-dropdown-item(href="#") Menu item 4 - .row - .col-6.p-4 - h1 Heading 1 - h2 Heading 2 - h3 Heading 3 - h4 Heading 4 - .col-6.p-4 - p Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui. Sed placerat ipsum eget pharetra rutrum. Ut vitae rutrum lacus, eu imperdiet velit. Pellentesque eu velit cursus, scelerisque dui quis, dapibus magna. Vestibulum molestie sed sapien et ultricies. Nam porta ipsum leo, non congue magna vestibulum a. Etiam dictum felis sit amet augue varius tincidunt. Sed eget urna auctor, convallis felis in, pretium justo. Curabitur aliquet, ligula id tincidunt ullamcorper, orci lorem pharetra neque, in ornare arcu magna accumsan arcu. Maecenas dignissim lorem sed eros accumsan scelerisque. - p.small-text Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vehicula, purus sit amet sodales pharetra, ipsum ipsum mollis orci, id pharetra velit diam et dui. - .row - .col(v-for="taskType in tasksTypes") - h3 {{taskType}}s - ul - task(v-for="task in tasks", v-if="task.type === taskType", :key="task.id", :task="task") - - - diff --git a/website/client/directives/directive.common.js b/website/client/directives/directive.common.js new file mode 100644 index 0000000000..151bc76870 --- /dev/null +++ b/website/client/directives/directive.common.js @@ -0,0 +1,8 @@ +// https://stackoverflow.com/a/40720172/1298154 +export const emit = (vnode, emitName, data) => { + let handlers = vnode.data && vnode.data.on || vnode.componentOptions && vnode.componentOptions.listeners; + + if (handlers && handlers[emitName]) { + handlers[emitName].fns(data); + } +}; diff --git a/website/client/directives/dragdrop.directive.js b/website/client/directives/dragdrop.directive.js new file mode 100644 index 0000000000..8d5ed6029d --- /dev/null +++ b/website/client/directives/dragdrop.directive.js @@ -0,0 +1,63 @@ +import {emit} from './directive.common'; + +import _keys from 'lodash/keys'; +import _without from 'lodash/without'; + +/** + * DRAG_GROUP is a static custom value + * KEY_OF_ITEM + * + * v-drag.DRAG_GROUP="KEY_OF_ITEM" + * v-drag.drop.DRAG_GROUP="KEY_OF_ITEM" @dropped="callback" @dragover="optional" + */ + +const DROPPED_EVENT_NAME = 'dropped'; +const DRAGOVER_EVENT_NAME = 'dragover'; + +export default { + bind (el, binding, vnode) { + el.isDropHandler = binding.modifiers.drop === true; + el.dragGroup = _without(_keys(binding.modifiers), 'drop')[0]; + el.key = binding.value; + + if (!el.isDropHandler) { + el.draggable = true; + el.handleDrag = (ev) => { + ev.dataTransfer.setData('KEY', binding.value); + }; + el.addEventListener('dragstart', el.handleDrag); + } else { + el.handleDragOver = (ev) => { + let dragOverEventData = { + dropable: true, + draggingKey: ev.dataTransfer.getData('KEY'), + }; + + emit(vnode, DRAGOVER_EVENT_NAME, dragOverEventData); + + if (dragOverEventData.dropable) { + ev.preventDefault(); + } + }; + el.handleDrop = (ev) => { + let dropEventData = { + draggingKey: ev.dataTransfer.getData('KEY'), + }; + + emit(vnode, DROPPED_EVENT_NAME, dropEventData); + }; + + el.addEventListener('dragover', el.handleDragOver); + el.addEventListener('drop', el.handleDrop); + } + }, + + unbind (el) { + if (!el.isDropHandler) { + el.removeEventListener('drag', el.handleDrag); + } else { + el.removeEventListener('dragover', el.handleDragOver); + el.removeEventListener('drop', el.handleDrop); + } + }, +}; diff --git a/website/client/directives/resize.directive.js b/website/client/directives/resize.directive.js new file mode 100644 index 0000000000..2cd14deb27 --- /dev/null +++ b/website/client/directives/resize.directive.js @@ -0,0 +1,31 @@ +import Vue from 'vue'; + +import _throttle from 'lodash/throttle'; + +import { emit } from './directive.common'; + +/** + * v-resize="throttleTimeout", @resized="callback()" + */ + +const EVENT_NAME = 'resized'; + +export default { + bind (el, binding, vnode) { + el.handleWindowResize = _throttle(() => { + emit(vnode, EVENT_NAME, { + width: el.clientWidth, + height: el.clientHeight, + }); + }, binding.value); + + window.addEventListener('resize', el.handleWindowResize); + + // send the first width + Vue.nextTick(el.handleWindowResize); + }, + + unbind (el) { + window.removeEventListener('resize', el.handleWindowResize); + }, +}; diff --git a/website/client/index.html b/website/client/index.html index 6eac2d4fda..204f884234 100644 --- a/website/client/index.html +++ b/website/client/index.html @@ -4,14 +4,9 @@
Loading...
-