mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-09 11:11:17 -05:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 150c884485 | |||
| 6d6a85aead | |||
| b5a81045c2 | |||
| 03d5978163 | |||
| 3550754132 | |||
| ced2f9eeac | |||
| 6343f32f79 | |||
| 2204885833 | |||
| 225259cd51 | |||
| 2a49d2e30e | |||
| 3108ae388f | |||
| 75b589ed56 | |||
| 30ee26c6ab | |||
| b9482c58d5 | |||
| 52606a0efd | |||
| 917afd06a6 | |||
| 3a5c22b381 | |||
| 2736d8acf3 | |||
| 8fe13dbb23 | |||
| 4581bb9315 | |||
| 2999212379 | |||
| d2bd246e6e | |||
| 1482f6c225 | |||
| 1178da3a26 | |||
| 819ed2b355 | |||
| a92999fc11 | |||
| 3489b88752 | |||
| 94bda30385 | |||
| e8bbdc2cb8 | |||
| d465efaf96 | |||
| 3a08de7ab3 | |||
| e6ffd69148 | |||
| 746fcfff49 | |||
| 8aa343d390 | |||
| d80c43c82a | |||
| 80e4b8617a | |||
| b3fac011a9 | |||
| 8f4d871911 | |||
| 19373ce84d | |||
| 55bfca20d9 | |||
| 2ee2b05d1c | |||
| 3d3db1bdd9 | |||
| a5dff99fa1 | |||
| 25435218e1 | |||
| 7746049bb4 | |||
| 036bf43cf3 | |||
| c4e0127a37 | |||
| 7ae059e243 | |||
| 81b9a0b92d | |||
| a6e87452a6 |
@@ -21,3 +21,4 @@ services:
|
||||
timeout: 30s
|
||||
start_period: 0s
|
||||
retries: 30
|
||||
|
||||
Generated
+169
-281
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "5.47.0",
|
||||
"version": "5.47.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "habitica",
|
||||
"version": "5.47.0",
|
||||
"version": "5.47.8",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
@@ -57,7 +57,7 @@
|
||||
"micromustache": "^8.0.3",
|
||||
"moment": "^2.29.4",
|
||||
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
|
||||
"mongoose": "^8.9.5",
|
||||
"mongoose": "^8.23.0",
|
||||
"morgan": "^1.10.1",
|
||||
"nan": "^2.25.0",
|
||||
"nconf": "^0.12.1",
|
||||
@@ -81,7 +81,6 @@
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.11.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"winston": "^3.10.0",
|
||||
"winston-loggly-bulk": "^3.3.0",
|
||||
"xml2js": "^0.6.2"
|
||||
@@ -2624,6 +2623,19 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/trace-agent/node_modules/gcp-metadata": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
||||
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"gaxios": "^5.0.0",
|
||||
"json-bigint": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/trace-agent/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -3052,9 +3064,9 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
|
||||
"integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
|
||||
"integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
@@ -3265,11 +3277,6 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/@polka/url": {
|
||||
"version": "1.0.0-next.25",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
|
||||
"integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ=="
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
@@ -3932,17 +3939,6 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
|
||||
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
@@ -6316,6 +6312,7 @@
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
|
||||
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readable-stream": "^2.3.5",
|
||||
"safe-buffer": "^5.1.1"
|
||||
@@ -6325,13 +6322,15 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bl/node_modules/readable-stream": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
@@ -6347,6 +6346,7 @@
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
@@ -7751,11 +7751,6 @@
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-0.0.3.tgz",
|
||||
"integrity": "sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw=="
|
||||
},
|
||||
"node_modules/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@@ -11229,18 +11224,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/gcp-metadata": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
||||
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
|
||||
"dependencies": {
|
||||
"gaxios": "^5.0.0",
|
||||
"json-bigint": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
@@ -11984,6 +11967,19 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/google-auth-library/node_modules/gcp-metadata": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
||||
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"gaxios": "^5.0.0",
|
||||
"json-bigint": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/google-auth-library/node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -12516,20 +12512,6 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/gzip-size": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
|
||||
"integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
|
||||
"dependencies": {
|
||||
"duplexer": "^0.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/habitica-markdown": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-4.1.0.tgz",
|
||||
@@ -12819,6 +12801,7 @@
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz",
|
||||
"integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
@@ -12871,7 +12854,8 @@
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
@@ -15557,128 +15541,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
|
||||
"integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bl": "^2.2.1",
|
||||
"bson": "^1.1.4",
|
||||
"denque": "^1.4.1",
|
||||
"optional-require": "^1.1.8",
|
||||
"safe-buffer": "^5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"saslprep": "^1.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"aws4": {
|
||||
"optional": true
|
||||
},
|
||||
"bson-ext": {
|
||||
"optional": true
|
||||
},
|
||||
"kerberos": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-client-encryption": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-extjson": {
|
||||
"optional": true
|
||||
},
|
||||
"snappy": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
||||
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
|
||||
"integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/whatwg-url": "^11.0.2",
|
||||
"whatwg-url": "^14.1.0 || ^13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
|
||||
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz",
|
||||
"integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^5.0.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb/node_modules/bson": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
|
||||
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose": {
|
||||
"version": "8.9.7",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.7.tgz",
|
||||
"integrity": "sha512-mvNXmU0V8qZzMR/qoK2mjT4Ti2ALdtfS0teK+twxhlGkwzOD76V02/zWajTu2MJ7QyEmZe9OWvnJsIY0iAuX3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bson": "^6.10.1",
|
||||
"kareem": "2.6.3",
|
||||
"mongodb": "~6.12.0",
|
||||
"mpath": "0.9.0",
|
||||
"mquery": "5.0.0",
|
||||
"ms": "2.1.3",
|
||||
"sift": "17.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mongoose"
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose/node_modules/mongodb": {
|
||||
"version": "6.12.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz",
|
||||
"integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mongodb-js/saslprep": "^1.1.9",
|
||||
"bson": "^6.10.1",
|
||||
"mongodb-connection-string-url": "^3.0.0"
|
||||
"@mongodb-js/saslprep": "^1.3.0",
|
||||
"bson": "^6.10.4",
|
||||
"mongodb-connection-string-url": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
@@ -15689,7 +15559,7 @@
|
||||
"gcp-metadata": "^5.2.0",
|
||||
"kerberos": "^2.0.1",
|
||||
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||
"snappy": "^7.2.2",
|
||||
"snappy": "^7.3.2",
|
||||
"socks": "^2.7.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
@@ -15716,6 +15586,72 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
||||
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/whatwg-url": "^11.0.2",
|
||||
"whatwg-url": "^14.1.0 || ^13.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^5.1.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose": {
|
||||
"version": "8.23.0",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.0.tgz",
|
||||
"integrity": "sha512-Bul4Ha6J8IqzFrb0B1xpVzkC3S0sk43dmLSnhFOn8eJlZiLwL5WO6cRymmjaADdCMjUcCpj2ce8hZI6O4ZFSug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bson": "^6.10.4",
|
||||
"kareem": "2.6.3",
|
||||
"mongodb": "~6.20.0",
|
||||
"mpath": "0.9.0",
|
||||
"mquery": "5.0.0",
|
||||
"ms": "2.1.3",
|
||||
"sift": "17.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mongoose"
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -15775,6 +15711,56 @@
|
||||
"integrity": "sha512-jSTz73B/+pGTTvhu5Ym8xsG6+QqaWab53UXnXdNNlTijTdLvcHABCLJXudQiJxob5N1Mzr5EOSx5ziwn2sihPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/monk/node_modules/bson": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
|
||||
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/monk/node_modules/mongodb": {
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
|
||||
"integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bl": "^2.2.1",
|
||||
"bson": "^1.1.4",
|
||||
"denque": "^1.4.1",
|
||||
"optional-require": "^1.1.8",
|
||||
"safe-buffer": "^5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"saslprep": "^1.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"aws4": {
|
||||
"optional": true
|
||||
},
|
||||
"bson-ext": {
|
||||
"optional": true
|
||||
},
|
||||
"kerberos": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-client-encryption": {
|
||||
"optional": true
|
||||
},
|
||||
"mongodb-extjson": {
|
||||
"optional": true
|
||||
},
|
||||
"snappy": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/morgan": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
|
||||
@@ -15852,14 +15838,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mrmime": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
|
||||
"integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -17128,19 +17106,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"bin": {
|
||||
"opener": "bin/opener-bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/optional-require": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz",
|
||||
"integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==",
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.10.tgz",
|
||||
"integrity": "sha512-0r3OB9EIQsP+a5HVATHq2ExIy2q/Vaffoo4IAikW1spCYswhLxqWQS0i3GwS3AdY/OIP4SWZHLGz8CMU558PGw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"require-at": "^1.0.6"
|
||||
},
|
||||
@@ -18622,6 +18593,7 @@
|
||||
"resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz",
|
||||
"integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -18920,6 +18892,7 @@
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
@@ -19346,19 +19319,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/sirv": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
|
||||
"integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
|
||||
"dependencies": {
|
||||
"@polka/url": "^1.0.0-next.24",
|
||||
"mrmime": "^2.0.0",
|
||||
"totalist": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
@@ -20849,14 +20809,6 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/totalist": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
|
||||
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/touch": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
|
||||
@@ -21870,50 +21822,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-bundle-analyzer": {
|
||||
"version": "4.10.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz",
|
||||
"integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==",
|
||||
"dependencies": {
|
||||
"@discoveryjs/json-ext": "0.5.7",
|
||||
"acorn": "^8.0.4",
|
||||
"acorn-walk": "^8.0.0",
|
||||
"commander": "^7.2.0",
|
||||
"debounce": "^1.2.1",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"gzip-size": "^6.0.0",
|
||||
"html-escaper": "^2.0.2",
|
||||
"opener": "^1.5.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"sirv": "^2.0.3",
|
||||
"ws": "^7.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"webpack-bundle-analyzer": "lib/bin/analyzer.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-bundle-analyzer/node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-cli": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
|
||||
@@ -22279,26 +22187,6 @@
|
||||
"typedarray-to-buffer": "^3.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-crypto": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-0.10.1.tgz",
|
||||
|
||||
+2
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "5.47.0",
|
||||
"version": "5.47.8",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
@@ -52,7 +52,7 @@
|
||||
"micromustache": "^8.0.3",
|
||||
"moment": "^2.29.4",
|
||||
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
|
||||
"mongoose": "^8.9.5",
|
||||
"mongoose": "^8.23.0",
|
||||
"morgan": "^1.10.1",
|
||||
"nan": "^2.25.0",
|
||||
"nconf": "^0.12.1",
|
||||
@@ -76,7 +76,6 @@
|
||||
"useragent": "^2.1.9",
|
||||
"uuid": "^9.0.0",
|
||||
"validator": "^13.11.0",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"winston": "^3.10.0",
|
||||
"winston-loggly-bulk": "^3.3.0",
|
||||
"xml2js": "^0.6.2"
|
||||
|
||||
@@ -1,560 +0,0 @@
|
||||
/* eslint-disable camelcase */
|
||||
import nconf from 'nconf';
|
||||
import Amplitude from 'amplitude';
|
||||
import * as analyticsService from '../../../../website/server/libs/analyticsService';
|
||||
|
||||
describe('analyticsService', () => {
|
||||
beforeEach(() => {
|
||||
sandbox.stub(Amplitude.prototype, 'track').returns(Promise.resolve());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('#getServiceByEnvironment', () => {
|
||||
it('returns mock methods when not in production', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
||||
expect(analyticsService.getAnalyticsServiceByEnvironment())
|
||||
.to.equal(analyticsService.mockAnalyticsService);
|
||||
});
|
||||
|
||||
it('returns real methods when in production', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
expect(analyticsService.getAnalyticsServiceByEnvironment().track)
|
||||
.to.equal(analyticsService.track);
|
||||
expect(analyticsService.getAnalyticsServiceByEnvironment().trackPurchase)
|
||||
.to.equal(analyticsService.trackPurchase);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#track', () => {
|
||||
let eventType; let
|
||||
data;
|
||||
|
||||
beforeEach(() => {
|
||||
eventType = 'Cron';
|
||||
data = {
|
||||
category: 'behavior',
|
||||
uuid: 'unique-user-id',
|
||||
resting: true,
|
||||
cronCount: 5,
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
user: {
|
||||
preferences: {
|
||||
analyticsConsent: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
context('Amplitude', () => {
|
||||
it('calls out to amplitude', () => analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledOnce;
|
||||
}));
|
||||
|
||||
it('uses a dummy user id if none is provided', () => {
|
||||
delete data.uuid;
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
user_id: 'no-user-id-was-provided',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('platform', () => {
|
||||
it('logs web platform', () => {
|
||||
data.headers = { 'x-client': 'habitica-web' };
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'Web',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs iOS platform', () => {
|
||||
data.headers = { 'x-client': 'habitica-ios' };
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'iOS',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs Android platform', () => {
|
||||
data.headers = { 'x-client': 'habitica-android' };
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'Android',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs 3rd Party platform', () => {
|
||||
data.headers = { 'x-client': 'some-third-party' };
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: '3rd Party',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs unknown if headers are not passed in', () => {
|
||||
delete data.headers;
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'Unknown',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Operating System', () => {
|
||||
it('sets default', () => {
|
||||
data.headers = {
|
||||
'x-client': 'third-party',
|
||||
'user-agent': 'foo',
|
||||
};
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: 'Other',
|
||||
os_version: '0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets iOS', () => {
|
||||
data.headers = {
|
||||
'x-client': 'habitica-ios',
|
||||
'user-agent': 'Habitica/148 (iPhone; iOS 9.3; Scale/2.00)',
|
||||
};
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: 'iOS',
|
||||
os_version: '9.3.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets Android', () => {
|
||||
data.headers = {
|
||||
'x-client': 'habitica-android',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19',
|
||||
};
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: 'Android',
|
||||
os_version: '4.0.4',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets Unknown if headers are not passed in', () => {
|
||||
delete data.headers;
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: undefined,
|
||||
os_version: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends details about event', () => analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
category: 'behavior',
|
||||
resting: true,
|
||||
cronCount: 5,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('sends english item name for gear if itemKey is provided', () => {
|
||||
data.itemKey = 'headAccessory_special_foxEars';
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
itemKey: data.itemKey,
|
||||
itemName: 'Fox Ears',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for egg if itemKey is provided', () => {
|
||||
data.itemKey = 'Wolf';
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
itemKey: data.itemKey,
|
||||
itemName: 'Wolf Egg',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for food if itemKey is provided', () => {
|
||||
data.itemKey = 'Cake_Skeleton';
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
itemKey: data.itemKey,
|
||||
itemName: 'Bare Bones Cake',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for hatching potion if itemKey is provided', () => {
|
||||
data.itemKey = 'Golden';
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
itemKey: data.itemKey,
|
||||
itemName: 'Golden Hatching Potion',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for quest if itemKey is provided', () => {
|
||||
data.itemKey = 'atom1';
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
itemKey: data.itemKey,
|
||||
itemName: 'Attack of the Mundane, Part 1: Dish Disaster!',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends english item name for purchased spell if itemKey is provided', () => {
|
||||
data.itemKey = 'seafoam';
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
itemKey: data.itemKey,
|
||||
itemName: 'Seafoam',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends user data if provided', () => {
|
||||
const stats = {
|
||||
class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30,
|
||||
};
|
||||
const user = {
|
||||
stats,
|
||||
contributor: { level: 1 },
|
||||
purchased: { plan: { planId: 'foo-plan' } },
|
||||
flags: { tour: { intro: -2 } },
|
||||
habits: [{ _id: 'habit' }],
|
||||
dailys: [{ _id: 'daily' }],
|
||||
todos: [{ _id: 'todo' }],
|
||||
rewards: [{ _id: 'reward' }],
|
||||
balance: 12,
|
||||
loginIncentives: 1,
|
||||
preferences: {
|
||||
analyticsConsent: true,
|
||||
},
|
||||
};
|
||||
|
||||
data.user = user;
|
||||
|
||||
return analyticsService.track(eventType, data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
user_properties: {
|
||||
Class: 'wizard',
|
||||
Experience: 5,
|
||||
Gold: 23,
|
||||
Health: 10,
|
||||
Level: 4,
|
||||
Mana: 30,
|
||||
tutorialComplete: true,
|
||||
'Number Of Tasks': {
|
||||
habits: 1,
|
||||
dailys: 1,
|
||||
todos: 1,
|
||||
rewards: 1,
|
||||
},
|
||||
contributorLevel: 1,
|
||||
subscription: 'foo-plan',
|
||||
balance: 12,
|
||||
balanceGemAmount: 48,
|
||||
loginIncentives: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#trackPurchase', () => {
|
||||
let data;
|
||||
|
||||
beforeEach(() => {
|
||||
data = {
|
||||
uuid: 'user-id',
|
||||
sku: 'paypal-checkout',
|
||||
paymentMethod: 'PayPal',
|
||||
itemPurchased: 'Gems',
|
||||
purchaseValue: 8,
|
||||
purchaseType: 'checkout',
|
||||
gift: false,
|
||||
quantity: 1,
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
user: {
|
||||
preferences: {
|
||||
analyticsConsent: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
context('Amplitude', () => {
|
||||
it('calls out to amplitude', () => analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledOnce;
|
||||
}));
|
||||
|
||||
it('uses a dummy user id if none is provided', () => {
|
||||
delete data.uuid;
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
user_id: 'no-user-id-was-provided',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('platform', () => {
|
||||
it('logs web platform', () => {
|
||||
data.headers = { 'x-client': 'habitica-web' };
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'Web',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs iOS platform', () => {
|
||||
data.headers = { 'x-client': 'habitica-ios' };
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'iOS',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs Android platform', () => {
|
||||
data.headers = { 'x-client': 'habitica-android' };
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'Android',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs 3rd Party platform', () => {
|
||||
data.headers = { 'x-client': 'some-third-party' };
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: '3rd Party',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('logs unknown if headers are not passed in', () => {
|
||||
delete data.headers;
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
platform: 'Unknown',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Operating System', () => {
|
||||
it('sets default', () => {
|
||||
data.headers = {
|
||||
'x-client': 'third-party',
|
||||
'user-agent': 'foo',
|
||||
};
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: 'Other',
|
||||
os_version: '0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets iOS', () => {
|
||||
data.headers = {
|
||||
'x-client': 'habitica-ios',
|
||||
'user-agent': 'Habitica/148 (iPhone; iOS 9.3; Scale/2.00)',
|
||||
};
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: 'iOS',
|
||||
os_version: '9.3.0',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets Android', () => {
|
||||
data.headers = {
|
||||
'x-client': 'habitica-android',
|
||||
'user-agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19',
|
||||
};
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: 'Android',
|
||||
os_version: '4.0.4',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets Unknown if headers are not passed in', () => {
|
||||
delete data.headers;
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
os_name: undefined,
|
||||
os_version: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sends details about purchase', () => analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
event_properties: {
|
||||
gift: false,
|
||||
itemPurchased: 'Gems',
|
||||
paymentMethod: 'PayPal',
|
||||
purchaseType: 'checkout',
|
||||
quantity: 1,
|
||||
sku: 'paypal-checkout',
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
it('sends user data if provided', () => {
|
||||
const stats = {
|
||||
class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30,
|
||||
};
|
||||
const user = {
|
||||
stats,
|
||||
contributor: { level: 1 },
|
||||
purchased: { plan: { planId: 'foo-plan' } },
|
||||
flags: { tour: { intro: -2 } },
|
||||
habits: [{ _id: 'habit' }],
|
||||
dailys: [{ _id: 'daily' }],
|
||||
todos: [{ _id: 'todo' }],
|
||||
rewards: [{ _id: 'reward' }],
|
||||
preferences: {
|
||||
analyticsConsent: true,
|
||||
},
|
||||
};
|
||||
|
||||
data.user = user;
|
||||
|
||||
return analyticsService.trackPurchase(data)
|
||||
.then(() => {
|
||||
expect(Amplitude.prototype.track).to.be.calledWithMatch({
|
||||
user_properties: {
|
||||
Class: 'wizard',
|
||||
Experience: 5,
|
||||
Gold: 23,
|
||||
Health: 10,
|
||||
Level: 4,
|
||||
Mana: 30,
|
||||
tutorialComplete: true,
|
||||
'Number Of Tasks': {
|
||||
habits: 1,
|
||||
dailys: 1,
|
||||
todos: 1,
|
||||
rewards: 1,
|
||||
},
|
||||
contributorLevel: 1,
|
||||
subscription: 'foo-plan',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mockAnalyticsService', () => {
|
||||
it('has stubbed track method', () => {
|
||||
expect(analyticsService.mockAnalyticsService).to.respondTo('track');
|
||||
});
|
||||
|
||||
it('has stubbed trackPurchase method', () => {
|
||||
expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase');
|
||||
});
|
||||
});
|
||||
});
|
||||
+116
-136
@@ -13,7 +13,6 @@ import { cron, cronWrapper } from '../../../../website/server/libs/cron';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../website/server/models/task';
|
||||
import common from '../../../../website/common';
|
||||
import * as analytics from '../../../../website/server/libs/analyticsService';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
|
||||
const CRON_TIMEOUT_WAIT = new Date(5 * 60 * 1000).getTime();
|
||||
@@ -41,20 +40,17 @@ describe('cron', async () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
sinon.spy(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (clock !== null) clock.restore();
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
it('updates user.preferences.timezoneOffsetAtLastCron', async () => {
|
||||
const timezoneUtcOffsetFromUserPrefs = -1;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics, timezoneUtcOffsetFromUserPrefs,
|
||||
user, tasksByType, daysMissed, timezoneUtcOffsetFromUserPrefs,
|
||||
});
|
||||
|
||||
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1);
|
||||
@@ -63,7 +59,7 @@ describe('cron', async () => {
|
||||
it('resets user.items.lastDrop.count', async () => {
|
||||
user.items.lastDrop.count = 4;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.items.lastDrop.count).to.equal(0);
|
||||
});
|
||||
@@ -71,26 +67,11 @@ describe('cron', async () => {
|
||||
it('increments user cron count', async () => {
|
||||
const cronCountBefore = user.flags.cronCount;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore);
|
||||
});
|
||||
|
||||
it('calls analytics', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
it('calls analytics when user is sleeping', async () => {
|
||||
user.preferences.sleep = true;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
});
|
||||
expect(analytics.track.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
describe('end of the month perks', async () => {
|
||||
beforeEach(async () => {
|
||||
user.purchased.plan.customerId = 'subscribedId';
|
||||
@@ -101,7 +82,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.dateUpdated = new Date('2018-12-11');
|
||||
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.mysteryItems.length).to.eql(2);
|
||||
const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS');
|
||||
@@ -112,7 +93,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.dateUpdated = new Date('2018-11-11');
|
||||
clock = sinon.useFakeTimers(new Date('2019-01-29'));
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.mysteryItems.length).to.eql(4);
|
||||
const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS');
|
||||
@@ -122,7 +103,7 @@ describe('cron', async () => {
|
||||
it('resets plan.gemsBought on a new month', async () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
@@ -131,7 +112,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
user.purchased.plan.dateUpdated = undefined;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
@@ -142,7 +123,7 @@ describe('cron', async () => {
|
||||
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||
});
|
||||
@@ -150,7 +131,7 @@ describe('cron', async () => {
|
||||
it('resets plan.dateUpdated on a new month', async () => {
|
||||
const currentMonth = moment().startOf('month');
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(moment(user.purchased.plan.dateUpdated).startOf('month').isSame(currentMonth)).to.eql(true);
|
||||
});
|
||||
@@ -158,7 +139,7 @@ describe('cron', async () => {
|
||||
it('increments plan.consecutive.count', async () => {
|
||||
user.purchased.plan.consecutive.count = 0;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.count).to.equal(1);
|
||||
});
|
||||
@@ -166,7 +147,7 @@ describe('cron', async () => {
|
||||
it('increments plan.cumulativeCount', async () => {
|
||||
user.purchased.plan.cumulativeCount = 0;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.cumulativeCount).to.equal(1);
|
||||
});
|
||||
@@ -175,7 +156,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate();
|
||||
user.purchased.plan.consecutive.count = 0;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.count).to.equal(2);
|
||||
});
|
||||
@@ -184,7 +165,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate();
|
||||
user.purchased.plan.cumulativeCount = 0;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.cumulativeCount).to.equal(3);
|
||||
});
|
||||
@@ -196,7 +177,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.consecutive.trinkets = 1;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
|
||||
@@ -206,7 +187,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.consecutive.gemCapExtra = 26;
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
||||
});
|
||||
@@ -214,7 +195,7 @@ describe('cron', async () => {
|
||||
it('does not reset plan stats if we are before the last day of the cancelled month', async () => {
|
||||
user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.customerId).to.exist;
|
||||
});
|
||||
@@ -225,7 +206,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.purchased.plan.customerId).to.not.exist;
|
||||
@@ -264,7 +245,7 @@ describe('cron', async () => {
|
||||
// Add 2 days so that we're sure we're not affected by any start-of-month effects
|
||||
// e.g., from time zone oddness.
|
||||
await cron({
|
||||
user: user1, tasksByType, daysMissed, analytics,
|
||||
user: user1, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
@@ -276,7 +257,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user1, tasksByType, daysMissed, analytics,
|
||||
user: user1, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user1.purchased.plan.consecutive.count).to.equal(10);
|
||||
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||
@@ -311,7 +292,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3, tasksByType, daysMissed, analytics,
|
||||
user: user3, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
@@ -323,7 +304,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3, tasksByType, daysMissed, analytics,
|
||||
user: user3, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user3.purchased.plan.consecutive.count).to.equal(10);
|
||||
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||
@@ -358,7 +339,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user6, tasksByType, daysMissed, analytics,
|
||||
user: user6, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user6.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
@@ -391,7 +372,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user12, tasksByType, daysMissed, analytics,
|
||||
user: user12, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
|
||||
@@ -403,7 +384,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user12, tasksByType, daysMissed, analytics,
|
||||
user: user12, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user12.purchased.plan.consecutive.count).to.equal(10);
|
||||
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
|
||||
@@ -439,7 +420,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3g, tasksByType, daysMissed, analytics,
|
||||
user: user3g, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
|
||||
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
|
||||
@@ -452,7 +433,7 @@ describe('cron', async () => {
|
||||
.add(2, 'days')
|
||||
.toDate());
|
||||
await cron({
|
||||
user: user3g, tasksByType, daysMissed, analytics,
|
||||
user: user3g, tasksByType, daysMissed,
|
||||
});
|
||||
// subscription has been erased by now
|
||||
expect(user3g.purchased.plan.consecutive.count).to.equal(0);
|
||||
@@ -471,7 +452,7 @@ describe('cron', async () => {
|
||||
it('resets plan.gemsBought on a new month', async () => {
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(0);
|
||||
});
|
||||
@@ -482,14 +463,14 @@ describe('cron', async () => {
|
||||
|
||||
user.purchased.plan.gemsBought = 10;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.gemsBought).to.equal(10);
|
||||
});
|
||||
|
||||
it('does not reset plan.dateUpdated on a new month', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.dateUpdated).to.be.empty;
|
||||
});
|
||||
@@ -497,7 +478,7 @@ describe('cron', async () => {
|
||||
it('does not increment plan.consecutive.count', async () => {
|
||||
user.purchased.plan.consecutive.count = 0;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.count).to.equal(0);
|
||||
});
|
||||
@@ -505,7 +486,7 @@ describe('cron', async () => {
|
||||
it('does not increment plan.cumulativeCount', async () => {
|
||||
user.purchased.plan.cumulativeCount = 0;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.cumulativeCount).to.equal(0);
|
||||
});
|
||||
@@ -513,7 +494,7 @@ describe('cron', async () => {
|
||||
it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => {
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.equal(0);
|
||||
});
|
||||
@@ -521,7 +502,7 @@ describe('cron', async () => {
|
||||
it('does not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', async () => {
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0);
|
||||
});
|
||||
@@ -530,7 +511,7 @@ describe('cron', async () => {
|
||||
user.purchased.plan.consecutive.gemCapExtra = 26;
|
||||
user.purchased.plan.consecutive.count = 5;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
|
||||
});
|
||||
@@ -538,7 +519,7 @@ describe('cron', async () => {
|
||||
it('does nothing to plan stats if we are before the last day of the cancelled month', async () => {
|
||||
user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.purchased.plan.customerId).to.not.exist;
|
||||
});
|
||||
@@ -564,7 +545,7 @@ describe('cron', async () => {
|
||||
it('should make uncompleted todos redder', async () => {
|
||||
const valueBefore = tasksByType.todos[0].value;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore);
|
||||
});
|
||||
@@ -573,7 +554,7 @@ describe('cron', async () => {
|
||||
tasksByType.todos[0].completed = true;
|
||||
const valueBefore = tasksByType.todos[0].value;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.todos[0].value).to.equal(valueBefore);
|
||||
});
|
||||
@@ -582,7 +563,7 @@ describe('cron', async () => {
|
||||
tasksByType.todos[0].completed = true;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.history.todos).to.be.lengthOf(1);
|
||||
@@ -608,7 +589,7 @@ describe('cron', async () => {
|
||||
expect(user.tasksOrder.todos).to.be.lengthOf(3);
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
// user.tasksOrder.todos should be filtered while tasks by type remains unchanged
|
||||
@@ -635,7 +616,7 @@ describe('cron', async () => {
|
||||
const original = user.tasksOrder.todos; // Preserve the original order
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
let listsAreEqual = true;
|
||||
@@ -675,7 +656,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].isDue).to.be.false;
|
||||
});
|
||||
@@ -686,7 +667,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
tasksByType.dailys[0].startDate = moment().toDate();
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].isDue).to.exist;
|
||||
});
|
||||
@@ -696,14 +677,14 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].everyX = 5;
|
||||
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].nextDue.length).to.eql(6);
|
||||
});
|
||||
|
||||
it('should add history', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].history).to.be.lengthOf(1);
|
||||
});
|
||||
@@ -711,7 +692,7 @@ describe('cron', async () => {
|
||||
it('should set tasks completed to false', async () => {
|
||||
tasksByType.dailys[0].completed = true;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
@@ -720,7 +701,7 @@ describe('cron', async () => {
|
||||
user.preferences.sleep = true;
|
||||
tasksByType.dailys[0].completed = true;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].completed).to.be.false;
|
||||
});
|
||||
@@ -729,7 +710,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].checklist.push({ title: 'test', completed: false });
|
||||
tasksByType.dailys[0].completed = true;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
@@ -739,7 +720,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].checklist.push({ title: 'test', completed: false });
|
||||
tasksByType.dailys[0].completed = true;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
@@ -749,7 +730,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].checklist.push({ title: 'test', completed: false });
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
|
||||
});
|
||||
@@ -759,7 +740,7 @@ describe('cron', async () => {
|
||||
const hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.stats.hp).to.be.lessThan(hpBefore);
|
||||
});
|
||||
@@ -770,7 +751,7 @@ describe('cron', async () => {
|
||||
const hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.stats.hp).to.equal(hpBefore);
|
||||
});
|
||||
@@ -784,7 +765,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
|
||||
cronOverride({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.hp).to.equal(hpBefore);
|
||||
@@ -797,7 +778,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.hp).to.equal(hpBefore);
|
||||
@@ -808,7 +789,7 @@ describe('cron', async () => {
|
||||
let hpBefore = user.stats.hp;
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
const hpDifferenceOfFullyIncompleteDaily = hpBefore - user.stats.hp;
|
||||
|
||||
@@ -816,7 +797,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].checklist.push({ title: 'test', completed: true });
|
||||
tasksByType.dailys[0].checklist.push({ title: 'test2', completed: false });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
const hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp;
|
||||
|
||||
@@ -829,7 +810,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
|
||||
const progress = await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(progress.down).to.equal(-1);
|
||||
@@ -841,7 +822,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
|
||||
|
||||
const progress = await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(progress.down).to.equal(0);
|
||||
@@ -862,7 +843,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[1].frequency = 'daily';
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.hp).to.equal(48);
|
||||
@@ -886,7 +867,7 @@ describe('cron', async () => {
|
||||
tasksByType.habits[0].down = false;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].value).to.be.lessThan(1);
|
||||
@@ -897,7 +878,7 @@ describe('cron', async () => {
|
||||
tasksByType.habits[0].up = false;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].value).to.be.lessThan(1);
|
||||
@@ -909,7 +890,7 @@ describe('cron', async () => {
|
||||
tasksByType.habits[0].down = true;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].value).to.equal(1);
|
||||
@@ -928,7 +909,7 @@ describe('cron', async () => {
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -941,7 +922,7 @@ describe('cron', async () => {
|
||||
tasksByType.habits[0].counterDown = 1;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -955,7 +936,7 @@ describe('cron', async () => {
|
||||
|
||||
// should not reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
@@ -964,7 +945,7 @@ describe('cron', async () => {
|
||||
// should reset
|
||||
daysMissed = 8;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -988,7 +969,7 @@ describe('cron', async () => {
|
||||
|
||||
// should not reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
@@ -1002,7 +983,7 @@ describe('cron', async () => {
|
||||
|
||||
// should reset after user CDS
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -1026,7 +1007,7 @@ describe('cron', async () => {
|
||||
|
||||
// should not reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
@@ -1036,7 +1017,7 @@ describe('cron', async () => {
|
||||
// should reset
|
||||
daysMissed = 2;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -1060,7 +1041,7 @@ describe('cron', async () => {
|
||||
|
||||
// should reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -1084,7 +1065,7 @@ describe('cron', async () => {
|
||||
|
||||
// should not reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
@@ -1098,7 +1079,7 @@ describe('cron', async () => {
|
||||
|
||||
// should not reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
@@ -1107,7 +1088,7 @@ describe('cron', async () => {
|
||||
// should reset
|
||||
daysMissed = 32;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -1132,7 +1113,7 @@ describe('cron', async () => {
|
||||
|
||||
// should reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -1156,7 +1137,7 @@ describe('cron', async () => {
|
||||
|
||||
// should not reset
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(1);
|
||||
@@ -1166,7 +1147,7 @@ describe('cron', async () => {
|
||||
// should reset
|
||||
daysMissed = 2;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(tasksByType.habits[0].counterUp).to.equal(0);
|
||||
@@ -1199,7 +1180,7 @@ describe('cron', async () => {
|
||||
user.stats.lvl = 2;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.history.exp).to.have.lengthOf(1);
|
||||
@@ -1212,7 +1193,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].isDue = true;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.achievements.perfect).to.equal(1);
|
||||
@@ -1224,7 +1205,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].isDue = false;
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.achievements.perfect).to.equal(0);
|
||||
@@ -1238,7 +1219,7 @@ describe('cron', async () => {
|
||||
const previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
@@ -1256,7 +1237,7 @@ describe('cron', async () => {
|
||||
const previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
@@ -1280,7 +1261,7 @@ describe('cron', async () => {
|
||||
};
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
@@ -1307,7 +1288,7 @@ describe('cron', async () => {
|
||||
};
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
@@ -1333,7 +1314,7 @@ describe('cron', async () => {
|
||||
};
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
@@ -1360,7 +1341,7 @@ describe('cron', async () => {
|
||||
};
|
||||
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.equal(0);
|
||||
@@ -1381,7 +1362,7 @@ describe('cron', async () => {
|
||||
const previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cronOverride({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
@@ -1401,7 +1382,7 @@ describe('cron', async () => {
|
||||
const previousBuffs = user.stats.buffs.toObject();
|
||||
|
||||
cronOverride({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
|
||||
@@ -1420,7 +1401,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].completed = true;
|
||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.stats.mp).to.be.greaterThan(mpBefore);
|
||||
|
||||
@@ -1436,7 +1417,7 @@ describe('cron', async () => {
|
||||
tasksByType.dailys[0].completed = true;
|
||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.stats.mp).to.equal(mpBefore);
|
||||
|
||||
@@ -1449,7 +1430,7 @@ describe('cron', async () => {
|
||||
user.stats.mp = 120;
|
||||
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.stats.mp).to.equal(common.statsComputed(user).maxMP);
|
||||
|
||||
@@ -1482,7 +1463,7 @@ describe('cron', async () => {
|
||||
|
||||
it('resets user progress', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.party.quest.progress.up).to.equal(0);
|
||||
expect(user.party.quest.progress.down).to.equal(0);
|
||||
@@ -1491,7 +1472,7 @@ describe('cron', async () => {
|
||||
|
||||
it('applies the user progress', async () => {
|
||||
const progress = await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(progress.down).to.equal(-1);
|
||||
});
|
||||
@@ -1529,19 +1510,19 @@ describe('cron', async () => {
|
||||
describe('login incentives', async () => {
|
||||
it('increments incentive counter each cron', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
user.lastCron = moment(new Date()).subtract({ days: 1 });
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(2);
|
||||
});
|
||||
|
||||
it('pushes a notification of the day\'s incentive each cron', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.notifications.length).to.eql(1);
|
||||
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
|
||||
@@ -1549,13 +1530,13 @@ describe('cron', async () => {
|
||||
|
||||
it('replaces previous notifications', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
|
||||
const filteredNotifications = user.notifications.filter(n => n.type === 'LOGIN_INCENTIVE');
|
||||
@@ -1566,7 +1547,7 @@ describe('cron', async () => {
|
||||
it('increments loginIncentives by 1 even if days are skipped in between', async () => {
|
||||
daysMissed = 3;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
@@ -1574,14 +1555,14 @@ describe('cron', async () => {
|
||||
it('increments loginIncentives by 1 even if user is sleeping', async () => {
|
||||
user.preferences.sleep = true;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
});
|
||||
|
||||
it('awards user bard robes if login incentive is 1', async () => {
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(1);
|
||||
expect(user.items.gear.owned.armor_special_bardRobes).to.eql(true);
|
||||
@@ -1591,7 +1572,7 @@ describe('cron', async () => {
|
||||
it('awards user incentive backgrounds if login incentive is 2', async () => {
|
||||
user.loginIncentives = 1;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(2);
|
||||
expect(user.purchased.background.blue).to.eql(true);
|
||||
@@ -1605,7 +1586,7 @@ describe('cron', async () => {
|
||||
it('awards user Bard Hat if login incentive is 3', async () => {
|
||||
user.loginIncentives = 2;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(3);
|
||||
expect(user.items.gear.owned.head_special_bardHat).to.eql(true);
|
||||
@@ -1615,7 +1596,7 @@ describe('cron', async () => {
|
||||
it('awards user RoyalPurple Hatching Potion if login incentive is 4', async () => {
|
||||
user.loginIncentives = 3;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(4);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
@@ -1625,7 +1606,7 @@ describe('cron', async () => {
|
||||
it('awards user a Chocolate, Meat and Pink Contton Candy if login incentive is 5', async () => {
|
||||
user.loginIncentives = 4;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(5);
|
||||
|
||||
@@ -1639,7 +1620,7 @@ describe('cron', async () => {
|
||||
it('awards user moon quest if login incentive is 7', async () => {
|
||||
user.loginIncentives = 6;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(7);
|
||||
expect(user.items.quests.moon1).to.eql(1);
|
||||
@@ -1649,7 +1630,7 @@ describe('cron', async () => {
|
||||
it('awards user RoyalPurple Hatching Potion if login incentive is 10', async () => {
|
||||
user.loginIncentives = 9;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(10);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
@@ -1659,7 +1640,7 @@ describe('cron', async () => {
|
||||
it('awards user a Strawberry, Patato and Blue Contton Candy if login incentive is 14', async () => {
|
||||
user.loginIncentives = 13;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(14);
|
||||
|
||||
@@ -1673,7 +1654,7 @@ describe('cron', async () => {
|
||||
it('awards user a bard instrument if login incentive is 18', async () => {
|
||||
user.loginIncentives = 17;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(18);
|
||||
expect(user.items.gear.owned.weapon_special_bardInstrument).to.eql(true);
|
||||
@@ -1683,7 +1664,7 @@ describe('cron', async () => {
|
||||
it('awards user second moon quest if login incentive is 22', async () => {
|
||||
user.loginIncentives = 21;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(22);
|
||||
expect(user.items.quests.moon2).to.eql(1);
|
||||
@@ -1693,7 +1674,7 @@ describe('cron', async () => {
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 26', async () => {
|
||||
user.loginIncentives = 25;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(26);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
@@ -1703,7 +1684,7 @@ describe('cron', async () => {
|
||||
it('awards user Fish, Milk, Rotten Meat and Honey if login incentive is 30', async () => {
|
||||
user.loginIncentives = 29;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(30);
|
||||
|
||||
@@ -1718,7 +1699,7 @@ describe('cron', async () => {
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 35', async () => {
|
||||
user.loginIncentives = 34;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(35);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
@@ -1728,7 +1709,7 @@ describe('cron', async () => {
|
||||
it('awards user the third moon quest if login incentive is 40', async () => {
|
||||
user.loginIncentives = 39;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(40);
|
||||
expect(user.items.quests.moon3).to.eql(1);
|
||||
@@ -1738,7 +1719,7 @@ describe('cron', async () => {
|
||||
it('awards user a RoyalPurple hatching potion if login incentive is 45', async () => {
|
||||
user.loginIncentives = 44;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(45);
|
||||
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
|
||||
@@ -1748,7 +1729,7 @@ describe('cron', async () => {
|
||||
it('awards user a saddle if login incentive is 50', async () => {
|
||||
user.loginIncentives = 49;
|
||||
await cron({
|
||||
user, tasksByType, daysMissed, analytics,
|
||||
user, tasksByType, daysMissed,
|
||||
});
|
||||
expect(user.loginIncentives).to.eql(50);
|
||||
expect(user.items.food.Saddle).to.eql(1);
|
||||
@@ -1766,7 +1747,6 @@ describe('cron wrapper', () => {
|
||||
res = generateRes();
|
||||
req = generateReq();
|
||||
user = await res.locals.user.save();
|
||||
res.analytics = analytics;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import { RegistrationEventModel } from '../../../../website/server/models/analytics/registrationEvent';
|
||||
import { SubscriptionEventModel } from '../../../../website/server/models/analytics/subscriptionEvent';
|
||||
|
||||
describe('localAnalytics', () => {
|
||||
let user;
|
||||
let localAnalytics;
|
||||
before(() => {
|
||||
const nconfGetStub = sandbox.stub(nconf, 'get');
|
||||
nconfGetStub.withArgs('ANALYTICS_DB').returns('analytics');
|
||||
nconfGetStub.withArgs('DISABLE_LOCAL_ANALYTICS').returns(false);
|
||||
localAnalytics = requireAgain('../../../../website/server/libs/localAnalytics');
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username',
|
||||
email: 'email@example.com',
|
||||
},
|
||||
},
|
||||
registeredThrough: 'habitica-web',
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackRegistrationEvent', () => {
|
||||
afterEach(async () => {
|
||||
await RegistrationEventModel.deleteMany({});
|
||||
});
|
||||
|
||||
it('creates a registration event when a user registers', async () => {
|
||||
user._id = '00000000-0000-0000-0000-000000000001';
|
||||
await localAnalytics.trackRegistrationEvent({ user, ipAddress: '127.0.0.1' });
|
||||
|
||||
const registrationEvents = await RegistrationEventModel.find({ userId: user._id });
|
||||
expect(registrationEvents).to.have.lengthOf(1);
|
||||
expect(registrationEvents[0]).to.have.property('userId', user._id);
|
||||
expect(registrationEvents[0]).to.have.property('ipAddress', '127.0.0.1');
|
||||
});
|
||||
|
||||
it('saves the correct data to the database', async () => {
|
||||
user._id = '00000000-0000-0000-0000-000000000002';
|
||||
user.auth.google = { id: 'abc', emails: [{ value: 'email@example.com' }] };
|
||||
await localAnalytics.trackRegistrationEvent({ user, ipAddress: '127.0.0.2' });
|
||||
|
||||
const registrationEvent = await RegistrationEventModel.findOne({ userId: user._id });
|
||||
expect(registrationEvent).to.have.property('userId', user._id);
|
||||
expect(registrationEvent).to.have.property('ipAddress', '127.0.0.2');
|
||||
expect(registrationEvent).to.have.property('authenticationMethod', 'google');
|
||||
});
|
||||
});
|
||||
|
||||
describe('trackSubscriptionEvent', () => {
|
||||
afterEach(async () => {
|
||||
await SubscriptionEventModel.deleteMany({});
|
||||
});
|
||||
|
||||
it('creates a subscription event when a user subscribes', async () => {
|
||||
user._id = '00000000-0000-0000-0000-000000000003';
|
||||
await localAnalytics.trackSubscriptionEvent({
|
||||
eventType: 'subscribed',
|
||||
user,
|
||||
paymentMethod: 'stripe',
|
||||
customerId: 'cus_123',
|
||||
planId: 'plan_123',
|
||||
});
|
||||
|
||||
const subscriptionEvents = await SubscriptionEventModel.find({ userId: user._id });
|
||||
expect(subscriptionEvents).to.have.lengthOf(1);
|
||||
expect(subscriptionEvents[0]).to.have.property('userId', user._id);
|
||||
expect(subscriptionEvents[0]).to.have.property('eventType', 'subscribed');
|
||||
expect(subscriptionEvents[0]).to.have.property('paymentMethod', 'stripe');
|
||||
expect(subscriptionEvents[0]).to.have.property('customerId', 'cus_123');
|
||||
expect(subscriptionEvents[0]).to.have.property('planId', 'plan_123');
|
||||
});
|
||||
|
||||
it('creates a subscription event with cancellation reason when a user cancels', async () => {
|
||||
user._id = '00000000-0000-0000-0000-000000000004';
|
||||
await localAnalytics.trackSubscriptionEvent({
|
||||
eventType: 'cancelled',
|
||||
user,
|
||||
paymentMethod: 'stripe',
|
||||
customerId: 'cus_456',
|
||||
planId: 'plan_456',
|
||||
cancellationReason: 'No longer needed',
|
||||
});
|
||||
|
||||
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id });
|
||||
expect(subscriptionEvent).to.have.property('userId', user._id);
|
||||
expect(subscriptionEvent).to.have.property('eventType', 'cancelled');
|
||||
expect(subscriptionEvent).to.have.property('paymentMethod', 'stripe');
|
||||
expect(subscriptionEvent).to.have.property('customerId', 'cus_456');
|
||||
expect(subscriptionEvent).to.have.property('planId', 'plan_456');
|
||||
expect(subscriptionEvent).to.have.property('cancellationReason', 'No longer needed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,6 @@ import moment from 'moment';
|
||||
import * as sender from '../../../../../website/server/libs/email';
|
||||
import common from '../../../../../website/common';
|
||||
import api from '../../../../../website/server/libs/payments/payments';
|
||||
import * as analytics from '../../../../../website/server/libs/analyticsService';
|
||||
import * as notifications from '../../../../../website/server/libs/pushNotifications';
|
||||
import { model as User } from '../../../../../website/server/models/user';
|
||||
import { translate as t } from '../../../../helpers/api-integration/v3';
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import * as worldState from '../../../../../website/server/libs/worldState';
|
||||
import { TransactionModel } from '../../../../../website/server/models/transaction';
|
||||
import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events';
|
||||
import { SubscriptionEventModel } from '../../../../../website/server/models/analytics/subscriptionEvent';
|
||||
|
||||
describe('payments/index', () => {
|
||||
let user;
|
||||
@@ -36,8 +36,6 @@ describe('payments/index', () => {
|
||||
|
||||
sandbox.stub(sender, 'sendTxn');
|
||||
sandbox.stub(user, 'sendMessage');
|
||||
sandbox.stub(analytics.mockAnalyticsService, 'trackPurchase');
|
||||
sandbox.stub(analytics.mockAnalyticsService, 'track');
|
||||
sandbox.stub(notifications, 'sendNotification');
|
||||
|
||||
data = {
|
||||
@@ -97,6 +95,16 @@ describe('payments/index', () => {
|
||||
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
|
||||
});
|
||||
|
||||
it('tracks subscription events', async () => {
|
||||
await api.createSubscription(data);
|
||||
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: recipient._id });
|
||||
expect(subscriptionEvent).to.exist;
|
||||
expect(subscriptionEvent).to.have.property('eventType', 'subscribed');
|
||||
expect(subscriptionEvent).to.have.property('userId', recipient._id);
|
||||
expect(subscriptionEvent).to.have.property('planId', 'basic_3mo');
|
||||
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
|
||||
});
|
||||
|
||||
it('adds extra months to an existing subscription', async () => {
|
||||
recipient.purchased.plan = plan;
|
||||
|
||||
@@ -298,28 +306,6 @@ describe('payments/index', () => {
|
||||
expect(notifications.sendNotification).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('tracks subscription purchase as gift', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce;
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({
|
||||
uuid: user._id,
|
||||
groupId: undefined,
|
||||
itemPurchased: 'Subscription',
|
||||
sku: 'payment method-subscription',
|
||||
purchaseType: 'subscribe',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: true,
|
||||
purchaseValue: 15,
|
||||
firstPurchase: true,
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('No Active Promotion', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(worldState, 'getCurrentEventList').returns([]);
|
||||
@@ -455,6 +441,16 @@ describe('payments/index', () => {
|
||||
expect(user.purchased.plan.dateCreated).to.exist;
|
||||
});
|
||||
|
||||
it('tracks subscription events', async () => {
|
||||
await api.createSubscription(data);
|
||||
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id });
|
||||
expect(subscriptionEvent).to.exist;
|
||||
expect(subscriptionEvent).to.have.property('userId', user._id);
|
||||
expect(subscriptionEvent).to.have.property('ipAddress');
|
||||
expect(subscriptionEvent).to.have.property('planId', 'basic_3mo');
|
||||
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
|
||||
});
|
||||
|
||||
it('sets plan.dateCreated if it did not previously exist', async () => {
|
||||
expect(user.purchased.plan.dateCreated).to.not.exist;
|
||||
|
||||
@@ -543,29 +539,24 @@ describe('payments/index', () => {
|
||||
expect(sender.sendTxn).to.be.calledWith(data.user, 'subscription-begins');
|
||||
});
|
||||
|
||||
it('tracks subscription purchase', async () => {
|
||||
await api.createSubscription(data);
|
||||
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce;
|
||||
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({
|
||||
uuid: user._id,
|
||||
groupId: undefined,
|
||||
itemPurchased: 'Subscription',
|
||||
sku: 'payment method-subscription',
|
||||
purchaseType: 'subscribe',
|
||||
paymentMethod: data.paymentMethod,
|
||||
quantity: 1,
|
||||
gift: false,
|
||||
purchaseValue: 15,
|
||||
firstPurchase: true,
|
||||
headers: {
|
||||
'x-client': 'habitica-web',
|
||||
'user-agent': '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
context('Upgrades subscription', () => {
|
||||
it('tracks subscription events', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
data.sub.key = 'basic_6mo';
|
||||
data.updatedFrom = { key: 'basic_earned' };
|
||||
await api.createSubscription(data);
|
||||
|
||||
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id, planId: 'basic_6mo' });
|
||||
expect(subscriptionEvent).to.exist;
|
||||
expect(subscriptionEvent).to.have.property('eventType', 'upgraded');
|
||||
expect(subscriptionEvent).to.have.property('userId', user._id);
|
||||
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
|
||||
});
|
||||
|
||||
it('from basic_earned to basic_6mo', async () => {
|
||||
data.sub.key = 'basic_earned';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
@@ -608,6 +599,23 @@ describe('payments/index', () => {
|
||||
});
|
||||
|
||||
context('Downgrades subscription', () => {
|
||||
it('tracks subscription events', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
|
||||
await api.createSubscription(data);
|
||||
|
||||
data.sub.key = 'basic_earned';
|
||||
data.updatedFrom = { key: 'basic_6mo' };
|
||||
await api.createSubscription(data);
|
||||
|
||||
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id, planId: 'basic_earned' });
|
||||
expect(subscriptionEvent).to.exist;
|
||||
expect(subscriptionEvent).to.have.property('eventType', 'downgraded');
|
||||
expect(subscriptionEvent).to.have.property('userId', user._id);
|
||||
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
|
||||
});
|
||||
|
||||
it('from basic_6mo to basic_earned', async () => {
|
||||
data.sub.key = 'basic_6mo';
|
||||
expect(user.purchased.plan.planId).to.not.exist;
|
||||
@@ -1136,6 +1144,15 @@ describe('payments/index', () => {
|
||||
expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days
|
||||
});
|
||||
|
||||
it('tracks subscription events', async () => {
|
||||
await api.cancelSubscription(data);
|
||||
|
||||
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id });
|
||||
expect(subscriptionEvent).to.exist;
|
||||
expect(subscriptionEvent).to.have.property('eventType', 'cancelled');
|
||||
expect(subscriptionEvent).to.have.property('userId', user._id);
|
||||
});
|
||||
|
||||
it('adds extraMonths to dateTerminated value', async () => {
|
||||
user.purchased.plan.extraMonths = 2;
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import nconf from 'nconf';
|
||||
import requireAgain from 'require-again';
|
||||
import {
|
||||
generateRes,
|
||||
generateReq,
|
||||
generateNext,
|
||||
} from '../../../helpers/api-unit.helper';
|
||||
import * as analyticsService from '../../../../website/server/libs/analyticsService';
|
||||
|
||||
describe('analytics middleware', () => {
|
||||
let res; let req; let
|
||||
next;
|
||||
const pathToAnalyticsMiddleware = '../../../../website/server/middlewares/analytics';
|
||||
|
||||
beforeEach(() => {
|
||||
res = generateRes();
|
||||
req = generateReq();
|
||||
next = generateNext();
|
||||
});
|
||||
|
||||
it('attaches analytics object to res', () => {
|
||||
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
|
||||
|
||||
attachAnalytics(req, res, next);
|
||||
|
||||
expect(res.analytics).to.exist;
|
||||
});
|
||||
|
||||
it('attaches stubbed methods for non-prod environments', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
|
||||
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
|
||||
|
||||
attachAnalytics(req, res, next);
|
||||
|
||||
expect(res.analytics.track).to.eql(analyticsService.mockAnalyticsService.track);
|
||||
expect(res.analytics.trackPurchase).to.eql(analyticsService.mockAnalyticsService.trackPurchase);
|
||||
});
|
||||
|
||||
it('attaches real methods for prod environments', () => {
|
||||
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||
|
||||
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
|
||||
|
||||
attachAnalytics(req, res, next);
|
||||
|
||||
expect(res.analytics.track).to.eql(analyticsService.track);
|
||||
expect(res.analytics.trackPurchase).to.eql(analyticsService.trackPurchase);
|
||||
});
|
||||
});
|
||||
@@ -274,13 +274,13 @@ describe('Group Model', () => {
|
||||
|
||||
expect(Group.prototype.sendChat).to.be.calledOnce;
|
||||
expect(Group.prototype.sendChat).to.be.calledWith({
|
||||
message: '`Participating Member attacks Wailing Whale for 5.0 damage. Wailing Whale attacks party for 7.5 damage.`',
|
||||
message: '`Participating Member attacks Wailing Whale for 5 damage. Wailing Whale attacks party for 8 damage.`',
|
||||
info: {
|
||||
bossDamage: '7.5',
|
||||
bossDamage: '8',
|
||||
quest: 'whale',
|
||||
type: 'boss_damage',
|
||||
user: 'Participating Member',
|
||||
userDamage: '5.0',
|
||||
userDamage: '5',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import {
|
||||
generateUser,
|
||||
requester,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
|
||||
|
||||
describe('POST /analytics/track/:eventName', () => {
|
||||
it('calls res.analytics', async () => {
|
||||
const user = await generateUser();
|
||||
sandbox.spy(analytics, 'track');
|
||||
|
||||
const requestWithHeaders = requester(user, { 'x-client': 'habitica-web' });
|
||||
await requestWithHeaders.post('/analytics/track/eventName', { data: 'example' }, { 'x-client': 'habitica-web' });
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledWith('eventName', sandbox.match({ data: 'example' }));
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
@@ -91,6 +91,23 @@ describe('POST /groups/:groupId/quests/accept', () => {
|
||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
|
||||
});
|
||||
|
||||
it('heals stuck RSVPNeeded when group already has the user accepted', async () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
await partyMembers[0].updateOne({ 'party.quest.RSVPNeeded': true });
|
||||
await partyMembers[0].sync();
|
||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.true;
|
||||
|
||||
const res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
expect(res).to.exist;
|
||||
|
||||
await partyMembers[0].sync();
|
||||
await questingGroup.sync();
|
||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.equal(false);
|
||||
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(true);
|
||||
});
|
||||
|
||||
it('does not accept invite for a quest already underway', async () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
@@ -100,6 +100,23 @@ describe('POST /groups/:groupId/quests/reject', () => {
|
||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
|
||||
});
|
||||
|
||||
it('heals stuck RSVPNeeded when group already has the user rejected', async () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
|
||||
|
||||
await partyMembers[0].updateOne({ 'party.quest.RSVPNeeded': true });
|
||||
await partyMembers[0].sync();
|
||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.true;
|
||||
|
||||
const res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
|
||||
expect(res).to.exist;
|
||||
|
||||
await partyMembers[0].sync();
|
||||
await questingGroup.sync();
|
||||
expect(partyMembers[0].party.quest.RSVPNeeded).to.equal(false);
|
||||
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(false);
|
||||
});
|
||||
|
||||
it('return an error when a user rejects an invite already accepted', async () => {
|
||||
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
|
||||
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
|
||||
|
||||
describe('POST /user/sleep', () => {
|
||||
let user;
|
||||
@@ -23,15 +22,4 @@ describe('POST /user/sleep', () => {
|
||||
await user.sync();
|
||||
expect(user.preferences.sleep).to.be.false;
|
||||
});
|
||||
|
||||
it('sends sleep status to analytics service', async () => {
|
||||
sandbox.spy(analytics, 'track');
|
||||
|
||||
await user.post('/user/sleep');
|
||||
await user.sync();
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledWith('sleep', sandbox.match.has('status', user.preferences.sleep));
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import { ApiUser } from '../../../../../helpers/api-integration/api-classes';
|
||||
import { encrypt } from '../../../../../../website/server/libs/encryption';
|
||||
import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent';
|
||||
|
||||
function generateRandomUserName () {
|
||||
return (Date.now() + uuid()).substring(0, 20);
|
||||
@@ -41,6 +42,25 @@ describe('POST /user/auth/local/register', () => {
|
||||
expect(user.newUser).to.eql(true);
|
||||
});
|
||||
|
||||
it('tracks a registration event', async () => {
|
||||
const username = generateRandomUserName();
|
||||
const email = `${username}@example.com`;
|
||||
const password = 'password';
|
||||
|
||||
const user = await api.post('/user/auth/local/register', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
const registrationEvent = await RegistrationEventModel.findOne({ userId: user._id });
|
||||
expect(registrationEvent).to.exist;
|
||||
expect(registrationEvent).to.have.property('userId', user._id);
|
||||
expect(registrationEvent).to.have.property('ipAddress');
|
||||
expect(registrationEvent).to.have.property('authenticationMethod', 'local');
|
||||
});
|
||||
|
||||
it('registers a new user and sets verifiedUsername to true', async () => {
|
||||
const username = generateRandomUserName();
|
||||
const email = `${username}@example.com`;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getProperty,
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import apiErrorMessages from '../../../../../../website/common/script/errors/apiErrorMessages';
|
||||
import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent';
|
||||
|
||||
describe('POST /user/auth/social', () => {
|
||||
let api;
|
||||
@@ -65,6 +66,19 @@ describe('POST /user/auth/social', () => {
|
||||
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
|
||||
});
|
||||
|
||||
it('tracks a registration event', async () => {
|
||||
const socialUser = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
network,
|
||||
});
|
||||
|
||||
const registrationEvent = await RegistrationEventModel.findOne({ userId: socialUser.id });
|
||||
expect(registrationEvent).to.exist;
|
||||
expect(registrationEvent).to.have.property('userId', socialUser.id);
|
||||
expect(registrationEvent).to.have.property('ipAddress');
|
||||
expect(registrationEvent).to.have.property('authenticationMethod', 'google');
|
||||
});
|
||||
|
||||
it('includes sanitized version of provided username', async () => {
|
||||
const response = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
@@ -231,6 +245,17 @@ describe('POST /user/auth/social', () => {
|
||||
expect(response.newUser).to.be.false;
|
||||
});
|
||||
|
||||
it('does not track a registration event for existing users', async () => {
|
||||
const beforeEvents = await RegistrationEventModel.find({ userId: user._id });
|
||||
await user.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
network,
|
||||
});
|
||||
|
||||
const registrationEvents = await RegistrationEventModel.find({ userId: user._id });
|
||||
expect(registrationEvents).to.have.lengthOf(beforeEvents.length);
|
||||
});
|
||||
|
||||
it('does not log into other account if social auth already exists', async () => {
|
||||
const registerResponse = await api.post(endpoint, {
|
||||
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
|
||||
|
||||
@@ -13,7 +13,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
@@ -32,12 +31,6 @@ describe('shared.ops.buy', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
it('returns error when key is not provided', async () => {
|
||||
@@ -51,10 +44,8 @@ describe('shared.ops.buy', () => {
|
||||
|
||||
it('buys health potion', async () => {
|
||||
user.stats.hp = 30;
|
||||
await buy(user, { params: { key: 'potion' } }, analytics);
|
||||
await buy(user, { params: { key: 'potion' } });
|
||||
expect(user.stats.hp).to.eql(45);
|
||||
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('adds equipment to inventory', async () => {
|
||||
|
||||
@@ -29,10 +29,9 @@ describe('shared.ops.buyArmoire', () => {
|
||||
const YIELD_EQUIPMENT = 0.5;
|
||||
const YIELD_FOOD = 0.7;
|
||||
const YIELD_EXP = 0.9;
|
||||
const analytics = { track () {} };
|
||||
|
||||
async function buyArmoire (_user, _req, _analytics) {
|
||||
const buyOp = new BuyArmoireOperation(_user, _req, _analytics);
|
||||
async function buyArmoire (_user, _req) {
|
||||
const buyOp = new BuyArmoireOperation(_user, _req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
@@ -50,12 +49,10 @@ describe('shared.ops.buyArmoire', () => {
|
||||
user.items.food = {};
|
||||
|
||||
sandbox.stub(randomValFns, 'trueRandom');
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
randomValFns.trueRandom.restore();
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
context('failure conditions', () => {
|
||||
@@ -147,7 +144,7 @@ describe('shared.ops.buyArmoire', () => {
|
||||
|
||||
expect(_.size(user.items.gear.owned)).to.equal(2);
|
||||
|
||||
await buyArmoire(user, {}, analytics);
|
||||
await buyArmoire(user, {});
|
||||
|
||||
expect(_.size(user.items.gear.owned)).to.equal(3);
|
||||
|
||||
@@ -155,7 +152,6 @@ describe('shared.ops.buyArmoire', () => {
|
||||
|
||||
expect(armoireCount).to.eql(_.size(getFullArmoire()) - 2);
|
||||
expect(user.stats.gp).to.eql(100);
|
||||
expect(analytics.track).to.be.calledTwice;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
@@ -11,15 +10,14 @@ import i18n from '../../../../website/common/script/i18n';
|
||||
import { BuyGemOperation } from '../../../../website/common/script/ops/buy/buyGem';
|
||||
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
|
||||
|
||||
async function buyGem (user, req, analytics) {
|
||||
const buyOp = new BuyGemOperation(user, req, analytics);
|
||||
async function buyGem (user, req) {
|
||||
const buyOp = new BuyGemOperation(user, req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
|
||||
describe('shared.ops.buyGem', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
const goldPoints = 40;
|
||||
const gemsBought = 40;
|
||||
const userGemAmount = 10;
|
||||
@@ -35,23 +33,16 @@ describe('shared.ops.buyGem', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
context('Gems', () => {
|
||||
it('purchases gems', async () => {
|
||||
const [, message] = await buyGem(user, { params: { type: 'gems', key: 'gem' } }, analytics);
|
||||
const [, message] = await buyGem(user, { params: { type: 'gems', key: 'gem' } });
|
||||
|
||||
expect(message).to.equal(i18n.t('plusGem', { count: 1 }));
|
||||
expect(user.balance).to.equal(userGemAmount + 0.25);
|
||||
expect(user.purchased.plan.gemsBought).to.equal(1);
|
||||
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('purchases gems with a different language than the default', async () => {
|
||||
|
||||
@@ -10,10 +10,9 @@ import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('shared.ops.buyHealthPotion', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
|
||||
async function buyHealthPotion (_user, _req, _analytics) {
|
||||
const buyOp = new BuyHealthPotionOperation(_user, _req, _analytics);
|
||||
async function buyHealthPotion (_user, _req) {
|
||||
const buyOp = new BuyHealthPotionOperation(_user, _req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
@@ -32,19 +31,13 @@ describe('shared.ops.buyHealthPotion', () => {
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
context('Potion', () => {
|
||||
it('recovers 15 hp', async () => {
|
||||
user.stats.hp = 30;
|
||||
await buyHealthPotion(user, {}, analytics);
|
||||
await buyHealthPotion(user, {});
|
||||
expect(user.stats.hp).to.eql(45);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('does not increase hp above 50', async () => {
|
||||
|
||||
@@ -13,15 +13,14 @@ import {
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
|
||||
|
||||
async function buyGear (user, req, analytics) {
|
||||
const buyOp = new BuyMarketGearOperation(user, req, analytics);
|
||||
async function buyGear (user, req) {
|
||||
const buyOp = new BuyMarketGearOperation(user, req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
|
||||
describe('shared.ops.buyMarketGear', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -47,14 +46,12 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
sinon.stub(shared, 'randomVal');
|
||||
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
|
||||
sinon.stub(shared.fns, 'predictableRandom');
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
shared.randomVal.restore();
|
||||
shared.fns.predictableRandom.restore();
|
||||
shared.onboarding.checkOnboardingStatus.restore();
|
||||
analytics.track.restore();
|
||||
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
@@ -65,7 +62,7 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
it('adds equipment to inventory', async () => {
|
||||
user.stats.gp = 31;
|
||||
|
||||
await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
|
||||
await buyGear(user, { params: { key: 'armor_warrior_1' } });
|
||||
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
weapon_warrior_0: true,
|
||||
@@ -92,13 +89,12 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
eyewear_special_whiteHalfMoon: true,
|
||||
eyewear_special_yellowHalfMoon: true,
|
||||
});
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('adds the onboarding achievement to the user and checks the onboarding status', async () => {
|
||||
user.stats.gp = 31;
|
||||
|
||||
await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
|
||||
await buyGear(user, { params: { key: 'armor_warrior_1' } });
|
||||
|
||||
expect(user.addAchievement).to.be.calledOnce;
|
||||
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
|
||||
@@ -111,7 +107,7 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
user.stats.gp = 31;
|
||||
user.achievements.purchasedEquipment = true;
|
||||
|
||||
await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
|
||||
await buyGear(user, { params: { key: 'armor_warrior_1' } });
|
||||
|
||||
expect(user.addAchievement).to.not.be.called;
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
||||
|
||||
describe('shared.ops.buyMysterySet', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
let clock;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -27,11 +26,9 @@ describe('shared.ops.buyMysterySet', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
}
|
||||
@@ -93,7 +90,7 @@ describe('shared.ops.buyMysterySet', () => {
|
||||
context('successful purchases', () => {
|
||||
it('buys Steampunk Accessories Set', async () => {
|
||||
user.purchased.plan.consecutive.trinkets = 1;
|
||||
await buyMysterySet(user, { params: { key: '301404' } }, analytics);
|
||||
await buyMysterySet(user, { params: { key: '301404' } });
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true);
|
||||
@@ -106,7 +103,7 @@ describe('shared.ops.buyMysterySet', () => {
|
||||
it('buys mystery set if it is available', async () => {
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-16'));
|
||||
user.purchased.plan.consecutive.trinkets = 1;
|
||||
await buyMysterySet(user, { params: { key: '201601' } }, analytics);
|
||||
await buyMysterySet(user, { params: { key: '201601' } });
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(user.items.gear.owned).to.have.property('shield_mystery_201601', true);
|
||||
|
||||
@@ -12,10 +12,9 @@ describe('shared.ops.buyQuestGems', () => {
|
||||
let user;
|
||||
let clock;
|
||||
const goldPoints = 40;
|
||||
const analytics = { track () {} };
|
||||
|
||||
async function buyQuest (_user, _req, _analytics) {
|
||||
const buyOp = new BuyQuestWithGemOperation(_user, _req, _analytics);
|
||||
async function buyQuest (_user, _req) {
|
||||
const buyOp = new BuyQuestWithGemOperation(_user, _req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
@@ -25,13 +24,11 @@ describe('shared.ops.buyQuestGems', () => {
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(analytics, 'track');
|
||||
sinon.spy(pinnedGearUtils, 'removeItemByPath');
|
||||
clock = sinon.useFakeTimers(new Date('2024-01-16'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
pinnedGearUtils.removeItemByPath.restore();
|
||||
clock.restore();
|
||||
});
|
||||
|
||||
@@ -12,21 +12,15 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
||||
|
||||
describe('shared.ops.buyQuest', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
|
||||
async function buyQuest (_user, _req, _analytics) {
|
||||
const buyOp = new BuyQuestWithGoldOperation(_user, _req, _analytics);
|
||||
async function buyQuest (_user, _req) {
|
||||
const buyOp = new BuyQuestWithGoldOperation(_user, _req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
it('buys a Quest scroll', async () => {
|
||||
@@ -35,12 +29,11 @@ describe('shared.ops.buyQuest', () => {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
}, analytics);
|
||||
});
|
||||
expect(user.items.quests).to.eql({
|
||||
dilatoryDistress1: 1,
|
||||
});
|
||||
expect(user.stats.gp).to.equal(5);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('if a user\'s count of a quest scroll is negative, it will be reset to 0 before incrementing when they buy a new one.', async () => {
|
||||
@@ -49,10 +42,9 @@ describe('shared.ops.buyQuest', () => {
|
||||
user.items.quests[key] = -1;
|
||||
await buyQuest(user, {
|
||||
params: { key },
|
||||
}, analytics);
|
||||
});
|
||||
expect(user.items.quests[key]).to.equal(1);
|
||||
expect(user.stats.gp).to.equal(5);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('buys a Quest scroll with the right quantity if a string is passed for quantity', async () => {
|
||||
@@ -61,13 +53,13 @@ describe('shared.ops.buyQuest', () => {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
}, analytics);
|
||||
});
|
||||
await buyQuest(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
quantity: '3',
|
||||
}, analytics);
|
||||
});
|
||||
|
||||
expect(user.items.quests).to.eql({
|
||||
dilatoryDistress1: 4,
|
||||
@@ -82,7 +74,7 @@ describe('shared.ops.buyQuest', () => {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
quantity: 'a',
|
||||
}, analytics);
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
@@ -187,12 +179,11 @@ describe('shared.ops.buyQuest', () => {
|
||||
params: {
|
||||
key: 'dilatoryDistress3',
|
||||
},
|
||||
}, analytics);
|
||||
});
|
||||
|
||||
expect(user.items.quests).to.eql({
|
||||
dilatoryDistress3: 1,
|
||||
});
|
||||
expect(user.stats.gp).to.equal(100);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,20 +14,17 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
|
||||
describe('shared.ops.buySpecialSpell', () => {
|
||||
let user;
|
||||
let clock;
|
||||
const analytics = { track () {} };
|
||||
|
||||
async function buySpecialSpell (_user, _req, _analytics) {
|
||||
const buyOp = new BuySpellOperation(_user, _req, _analytics);
|
||||
async function buySpecialSpell (_user, _req) {
|
||||
const buyOp = new BuySpellOperation(_user, _req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
if (clock) {
|
||||
clock.restore();
|
||||
}
|
||||
@@ -78,7 +75,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
params: {
|
||||
key: 'thankyou',
|
||||
},
|
||||
}, analytics);
|
||||
});
|
||||
|
||||
expect(user.stats.gp).to.equal(1);
|
||||
expect(user.items.special.thankyou).to.equal(1);
|
||||
@@ -89,7 +86,6 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
expect(message).to.equal(i18n.t('messageBought', {
|
||||
itemText: item.text(),
|
||||
}));
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('buys a limited card when it is available', async () => {
|
||||
@@ -101,7 +97,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
params: {
|
||||
key: 'nye',
|
||||
},
|
||||
}, analytics);
|
||||
});
|
||||
|
||||
expect(user.stats.gp).to.equal(1);
|
||||
expect(user.items.special.nye).to.equal(1);
|
||||
@@ -112,7 +108,6 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
expect(message).to.equal(i18n.t('messageBought', {
|
||||
itemText: item.text(),
|
||||
}));
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('throws an error if the card is not currently available', async () => {
|
||||
@@ -140,7 +135,7 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
params: {
|
||||
key: 'seafoam',
|
||||
},
|
||||
}, analytics);
|
||||
});
|
||||
|
||||
expect(user.stats.gp).to.equal(1);
|
||||
expect(user.items.special.seafoam).to.equal(1);
|
||||
@@ -151,7 +146,6 @@ describe('shared.ops.buySpecialSpell', () => {
|
||||
expect(message).to.equal(i18n.t('messageBought', {
|
||||
itemText: item.text(),
|
||||
}));
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('throws an error if the spell is not currently available', async () => {
|
||||
|
||||
@@ -13,21 +13,15 @@ import { BuyHourglassMountOperation } from '../../../../website/common/script/op
|
||||
|
||||
describe('common.ops.hourglassPurchase', () => {
|
||||
let user;
|
||||
const analytics = { track () {} };
|
||||
|
||||
async function buyMount (_user, _req, _analytics) {
|
||||
const buyOp = new BuyHourglassMountOperation(_user, _req, _analytics);
|
||||
async function buyMount (_user, _req) {
|
||||
const buyOp = new BuyHourglassMountOperation(_user, _req);
|
||||
|
||||
return buyOp.purchase();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
sinon.stub(analytics, 'track');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
});
|
||||
|
||||
context('failure conditions', () => {
|
||||
@@ -131,12 +125,11 @@ describe('common.ops.hourglassPurchase', () => {
|
||||
it('buys a pet', async () => {
|
||||
user.purchased.plan.consecutive.trinkets = 2;
|
||||
|
||||
const [, message] = await hourglassPurchase(user, { params: { type: 'pets', key: 'MantisShrimp-Base' } }, analytics);
|
||||
const [, message] = await hourglassPurchase(user, { params: { type: 'pets', key: 'MantisShrimp-Base' } });
|
||||
|
||||
expect(message).to.eql(i18n.t('hourglassPurchase'));
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
|
||||
expect(user.items.pets).to.eql({ 'MantisShrimp-Base': 5 });
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('buys a mount', async () => {
|
||||
|
||||
@@ -17,20 +17,17 @@ describe('shared.ops.purchase', () => {
|
||||
let user;
|
||||
let clock;
|
||||
const goldPoints = 40;
|
||||
const analytics = { track () {} };
|
||||
|
||||
before(() => {
|
||||
user = generateUser({ 'stats.class': 'rogue' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sinon.stub(analytics, 'track');
|
||||
sinon.spy(pinnedGearUtils, 'removeItemByPath');
|
||||
clock = sandbox.useFakeTimers(new Date('2024-01-10'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
analytics.track.restore();
|
||||
pinnedGearUtils.removeItemByPath.restore();
|
||||
clock.restore();
|
||||
});
|
||||
@@ -187,11 +184,10 @@ describe('shared.ops.purchase', () => {
|
||||
const type = 'eggs';
|
||||
const key = 'Wolf';
|
||||
|
||||
await purchase(user, { params: { type, key } }, analytics);
|
||||
await purchase(user, { params: { type, key } });
|
||||
|
||||
expect(user.items[type][key]).to.equal(1);
|
||||
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
});
|
||||
|
||||
it('purchases hatchingPotions', async () => {
|
||||
@@ -332,7 +328,7 @@ describe('shared.ops.purchase', () => {
|
||||
const key = 'Wolf';
|
||||
|
||||
try {
|
||||
await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
|
||||
await purchase(user, { params: { type, key }, quantity: 'jamboree' });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
@@ -345,7 +341,7 @@ describe('shared.ops.purchase', () => {
|
||||
user.balance = 10;
|
||||
|
||||
try {
|
||||
await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
|
||||
await purchase(user, { params: { type, key }, quantity: -2 });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
@@ -358,7 +354,7 @@ describe('shared.ops.purchase', () => {
|
||||
user.balance = 10;
|
||||
|
||||
try {
|
||||
await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
|
||||
await purchase(user, { params: { type, key }, quantity: 2.9 });
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('invalidQuantity'));
|
||||
|
||||
@@ -239,14 +239,14 @@ describe('shared.ops.scoreTask', () => {
|
||||
});
|
||||
const firstTaskDelta = ref.afterUser.party.quest.progress.up;
|
||||
expect(firstTaskDelta).to.be.greaterThan(0);
|
||||
expect(ref.afterUser._tmp.quest.progressDelta).to.eql(firstTaskDelta);
|
||||
expect(ref.afterUser._tmp.quest.progressDelta).to.eql(Math.ceil(firstTaskDelta));
|
||||
|
||||
scoreTask({
|
||||
user: ref.afterUser, task: habit, direction: 'up', cron: false,
|
||||
});
|
||||
const secondTaskDelta = ref.afterUser.party.quest.progress.up - firstTaskDelta;
|
||||
expect(secondTaskDelta).to.be.greaterThan(0);
|
||||
expect(ref.afterUser._tmp.quest.progressDelta).to.eql(secondTaskDelta);
|
||||
expect(ref.afterUser._tmp.quest.progressDelta).to.eql(Math.ceil(secondTaskDelta));
|
||||
});
|
||||
|
||||
context('habits', () => {
|
||||
|
||||
@@ -54,19 +54,4 @@ describe('armoire', () => {
|
||||
const febuaryItems = armoire.all;
|
||||
expect(febuaryItems.length).to.equal(384);
|
||||
});
|
||||
|
||||
it('sets have at least 2 items', () => {
|
||||
const setMap = {};
|
||||
forEach(armoire.all, item => {
|
||||
// Gotta have one outlier
|
||||
if (!item.set || item.set.startsWith('armoire-')) return;
|
||||
if (setMap[item.set] === undefined) {
|
||||
setMap[item.set] = 0;
|
||||
}
|
||||
setMap[item.set] += 1;
|
||||
});
|
||||
Object.keys(setMap).forEach(set => {
|
||||
expect(setMap[set], set).to.be.at.least(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -40,7 +40,6 @@ function _requestMaker (user, method, additionalSets = {}) {
|
||||
|| route.indexOf('/paypal') === 0
|
||||
|| route.indexOf('/amazon') === 0
|
||||
|| route.indexOf('/stripe') === 0
|
||||
|| route.indexOf('/analytics') === 0
|
||||
) {
|
||||
url += `${route}`;
|
||||
} else {
|
||||
|
||||
@@ -12,20 +12,12 @@ module.exports = {
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
// TODO find a way to let eslint understand webpack aliases
|
||||
'import/no-unresolved': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'import/extensions': 'off',
|
||||
'prefer-regex-literals': 'warn',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/no-mutating-props': 'warn',
|
||||
// this creates issues with the current way we have to push the process.env vars to webpack
|
||||
// https://github.com/eslint/eslint/issues/14918
|
||||
// https://github.com/webpack/webpack/issues/5392
|
||||
// off for now, because any eslint --fix will then still do it anyway
|
||||
// maybe this can be turned on again once we switch to newer vue/vite
|
||||
// Important! process.env.XYZ should not be destructured
|
||||
'prefer-destructuring': 'off',
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
void: 'never',
|
||||
|
||||
Generated
+20
-589
@@ -41,7 +41,6 @@
|
||||
"vite": "^6.3.6",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-fragment": "^1.6.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.6.5",
|
||||
"vuedraggable": "^2.24.3",
|
||||
@@ -55,9 +54,7 @@
|
||||
"jsdom": "^26.0.0",
|
||||
"mocha": "^11.1.0",
|
||||
"playwright": "^1.50.1",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"vitest": "^3.0.5",
|
||||
"webpack": "^5.94.0"
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@amplitude/analytics-connector": {
|
||||
@@ -2111,8 +2108,9 @@
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
@@ -3634,41 +3632,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint-scope": {
|
||||
"version": "3.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
|
||||
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
@@ -3679,8 +3648,9 @@
|
||||
"version": "24.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -3876,181 +3846,6 @@
|
||||
"vue-template-compiler": "^2.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ast": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
|
||||
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
|
||||
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
|
||||
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
|
||||
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
|
||||
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
|
||||
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
|
||||
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/ieee754": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
|
||||
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/leb128": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
|
||||
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
|
||||
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
|
||||
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-section": "1.14.1",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||
"@webassemblyjs/wasm-opt": "1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "1.14.1",
|
||||
"@webassemblyjs/wast-printer": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-gen": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
|
||||
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/ieee754": "1.13.2",
|
||||
"@webassemblyjs/leb128": "1.13.2",
|
||||
"@webassemblyjs/utf8": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-opt": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
|
||||
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "1.14.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-parser": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
|
||||
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||
"@webassemblyjs/ieee754": "1.13.2",
|
||||
"@webassemblyjs/leb128": "1.13.2",
|
||||
"@webassemblyjs/utf8": "1.13.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@webassemblyjs/wast-printer": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
|
||||
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.14.1",
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xtuc/long": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
@@ -4098,48 +3893,6 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/amplitude-js": {
|
||||
"version": "8.21.9",
|
||||
"resolved": "https://registry.npmjs.org/amplitude-js/-/amplitude-js-8.21.9.tgz",
|
||||
@@ -4617,8 +4370,9 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
@@ -4783,16 +4537,6 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chrome-trace-event": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
||||
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
@@ -4859,8 +4603,9 @@
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
@@ -5196,20 +4941,6 @@
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
|
||||
"integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enquirer": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz",
|
||||
@@ -6384,16 +6115,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
|
||||
@@ -6871,13 +6592,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "13.24.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
|
||||
@@ -6921,13 +6635,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/habitica-markdown": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-4.1.0.tgz",
|
||||
@@ -7726,37 +7433,6 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-worker/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||
@@ -7841,13 +7517,6 @@
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -7911,20 +7580,6 @@
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
|
||||
"integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@@ -8128,13 +7783,6 @@
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@@ -8458,13 +8106,6 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
@@ -9504,63 +9145,6 @@
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
|
||||
"integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"ajv-keywords": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv-keywords": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/secure-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz",
|
||||
@@ -9838,8 +9422,9 @@
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
@@ -10130,20 +9715,6 @@
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-mini": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-mini/-/tar-mini-0.2.0.tgz",
|
||||
@@ -10154,8 +9725,9 @@
|
||||
"version": "5.44.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz",
|
||||
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
|
||||
"devOptional": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
@@ -10169,47 +9741,13 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.14",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
|
||||
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^4.3.0",
|
||||
"serialize-javascript": "^6.0.2",
|
||||
"terser": "^5.31.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -10585,8 +10123,9 @@
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.4",
|
||||
@@ -10965,15 +10504,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-fragment": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-fragment/-/vue-fragment-1.6.0.tgz",
|
||||
"integrity": "sha512-a5T8ZZZK/EQzgVShEl374HbobUJ0a7v12BzOzS6Z/wd/5EE/5SffcyHC+7bf9hP3L7Yc0hhY/GhMdwFQ25O/8A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^2.5.16"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-functional-data-merge": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
|
||||
@@ -11041,20 +10571,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
|
||||
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||
@@ -11065,91 +10581,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.102.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz",
|
||||
"integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.8",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-import-phases": "^1.0.3",
|
||||
"browserslist": "^4.26.3",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.3",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"json-parse-even-better-errors": "^2.3.1",
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.3",
|
||||
"tapable": "^2.3.0",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"watchpack": "^2.4.4",
|
||||
"webpack-sources": "^3.3.3"
|
||||
},
|
||||
"bin": {
|
||||
"webpack": "bin/webpack.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"webpack-cli": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
|
||||
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/acorn-import-phases": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
|
||||
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
"vite": "^6.3.6",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vue": "^2.7.10",
|
||||
"vue-fragment": "^1.6.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.6.5",
|
||||
"vuedraggable": "^2.24.3",
|
||||
@@ -60,8 +59,6 @@
|
||||
"jsdom": "^26.0.0",
|
||||
"mocha": "^11.1.0",
|
||||
"playwright": "^1.50.1",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"vitest": "^3.0.5",
|
||||
"webpack": "^5.94.0"
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,68 @@
|
||||
.quest_lostMasterclasser4 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_lostMasterclasser4.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.quest_windup {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_windup.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.quest_solarSystem {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_solarSystem.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.quest_virtualpet {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_virtualpet.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.quest_alien {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_alien.gif") no-repeat;
|
||||
width: 219px;
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Dessert, .Pet_HatchingPotion_Veggie, .Pet_HatchingPotion_Windup,
|
||||
.Pet_HatchingPotion_VirtualPet, .Pet_HatchingPotion_Fungi, .Pet_HatchingPotion_Cryptid,
|
||||
.Pet_HatchingPotion_Alien {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Dessert {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Dessert.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Veggie {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Windup {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Windup.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_VirtualPet {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_VirtualPet.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Fungi {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Fungi.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Cryptid {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Cryptid.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Alien {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_HatchingPotion_Alien.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Gems {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
@@ -26,7 +91,6 @@
|
||||
margin-left: -3px;
|
||||
margin-top: -18px;
|
||||
}
|
||||
|
||||
.slim_armor_special_0, .broad_armor_special_0, .shield_special_0 {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
@@ -34,6 +98,7 @@
|
||||
|
||||
/* Critical */
|
||||
.weapon_special_critical {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_critical.gif") no-repeat;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-left:-12px;
|
||||
@@ -44,7 +109,6 @@
|
||||
.weapon_special_1 {
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.broad_armor_special_1, .slim_armor_special_1, .head_special_1 {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
@@ -53,15 +117,36 @@
|
||||
.back_special_heroicAureole {
|
||||
width: 114px;
|
||||
height: 90px;
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_special_heroicAureole.gif") no-repeat;
|
||||
}
|
||||
|
||||
.head_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-ShadeHelmet.gif") no-repeat;
|
||||
}
|
||||
.head_special_1 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/ContributorOnly-Equip-CrystalHelmet.gif") no-repeat;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.broad_armor_special_0,.slim_armor_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-ShadeArmor.gif") no-repeat;
|
||||
}
|
||||
.broad_armor_special_1,.slim_armor_special_1 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/ContributorOnly-Equip-CrystalArmor.gif") no-repeat;
|
||||
}
|
||||
|
||||
.shield_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Shield-TormentedSkull.gif") no-repeat;
|
||||
}
|
||||
|
||||
.weapon_special_0 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Weapon-DarkSoulsBlade.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Pet-Wolf-Cerberus {
|
||||
width: 105px;
|
||||
height: 72px;
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Pet-CerberusPup.gif") no-repeat;
|
||||
}
|
||||
|
||||
.broad_armor_special_ks2019, .slim_armor_special_ks2019, .eyewear_special_ks2019, .head_special_ks2019, .shield_special_ks2019 {
|
||||
@@ -69,17 +154,36 @@
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.broad_armor_special_ks2019, .slim_armor_special_ks2019 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-MythicGryphonArmor.gif") no-repeat;
|
||||
}
|
||||
|
||||
.eyewear_special_ks2019 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-MythicGryphonVisor.gif") no-repeat;
|
||||
}
|
||||
|
||||
.head_special_ks2019 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-MythicGryphonHelm.gif") no-repeat;
|
||||
}
|
||||
|
||||
.shield_special_ks2019 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-MythicGryphonShield.gif") no-repeat;
|
||||
}
|
||||
|
||||
.weapon_special_ks2019 {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Equip-MythicGryphonGlaive.gif") no-repeat;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.Pet-Gryphon-Gryphatrice {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Pet-Gryphatrice.gif") no-repeat;
|
||||
width: 81px;
|
||||
height: 99px;
|
||||
}
|
||||
|
||||
.Pet-Gryphatrice-Jubilant {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphatrice-Jubilant.gif") no-repeat;
|
||||
width: 81px;
|
||||
height: 96px;
|
||||
}
|
||||
@@ -89,11 +193,39 @@
|
||||
height: 135px;
|
||||
}
|
||||
|
||||
.Mount_Head_Gryphon-Gryphatrice {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Mount-Head-Gryphatrice.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Mount_Body_Gryphon-Gryphatrice {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/BackerOnly-Mount-Body-Gryphatrice.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Mount_Head_Dragon-Hydra {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Dragon-Hydra.gif") no-repeat;
|
||||
}
|
||||
|
||||
.Mount_Body_Dragon-Hydra {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Dragon-Hydra.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_airship, .background_clocktower, .background_steamworks {
|
||||
width: 141px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
.background_airship {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_airship.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_clocktower {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_clocktower.gif") no-repeat;
|
||||
}
|
||||
|
||||
.background_steamworks {
|
||||
background: url("https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_steamworks.gif") no-repeat;
|
||||
}
|
||||
|
||||
[class*="Mount_Head_"],
|
||||
[class*="Mount_Body_"] {
|
||||
margin-top:18px; /* Sprite accommodates 105x123 box */
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.d-content {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
* {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
@@ -108,15 +108,15 @@ export default {
|
||||
const allEmails = [];
|
||||
if (user.auth.local.email) allEmails.push(user.auth.local.email);
|
||||
if (user.auth.google && user.auth.google.emails) {
|
||||
const emails = user.auth.google.emails;
|
||||
const { emails } = user.auth.google;
|
||||
allEmails.push(...this.findSocialEmails(emails));
|
||||
}
|
||||
if (user.auth.apple && user.auth.apple.emails) {
|
||||
const emails = user.auth.apple.emails;
|
||||
const { emails } = user.auth.apple;
|
||||
allEmails.push(...this.findSocialEmails(emails));
|
||||
}
|
||||
if (user.auth.facebook && user.auth.facebook.emails) {
|
||||
const emails = user.auth.facebook.emails;
|
||||
const { emails } = user.auth.facebook;
|
||||
allEmails.push(...this.findSocialEmails(emails));
|
||||
}
|
||||
return allEmails;
|
||||
|
||||
+1
-1
@@ -609,7 +609,7 @@ import subscriptionBlocks from '@/../../common/script/content/subscriptionBlocks
|
||||
import saveHero from '../mixins/saveHero';
|
||||
import LoadingSpinner from '@/components/ui/loadingSpinner';
|
||||
|
||||
const PLAY_CONSOLE_ORDERS_BASE_URL = import.meta.env.PLAY_CONSOLE_ORDERS_BASE_URL;
|
||||
const { PLAY_CONSOLE_ORDERS_BASE_URL } = import.meta.env;
|
||||
|
||||
const humanReadablePaymentDetails = {
|
||||
customerId: {
|
||||
|
||||
@@ -20,6 +20,29 @@
|
||||
class="form mx-auto"
|
||||
@submit.prevent.stop="register()"
|
||||
>
|
||||
<div v-if="needsEmailField">
|
||||
<input
|
||||
id="emailInput"
|
||||
v-model="email"
|
||||
class="form-control dark"
|
||||
type="text"
|
||||
:placeholder="$t('emailAddress')"
|
||||
:class="{
|
||||
'mb-3': !emailError,
|
||||
'input-invalid input-with-error mb-2': emailError,
|
||||
'input-valid': email && emailValid,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="emailError"
|
||||
class="input-error"
|
||||
>
|
||||
{{ emailError }}
|
||||
</div>
|
||||
<p class="purple-600 mb-3">
|
||||
{{ $t('emailRequiredForSupport') }}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
id="usernameInput"
|
||||
v-model="username"
|
||||
@@ -58,8 +81,9 @@
|
||||
></label>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-info d-block w-100 sign-up mx-auto mb-5"
|
||||
:disabled="!username || usernameInvalid || !privacyAccepted"
|
||||
class="btn btn-info d-flex justify-content-center
|
||||
align-items-center w-100 sign-up mx-auto mb-5"
|
||||
:disabled="!email || emailError || !username || usernameInvalid || !privacyAccepted"
|
||||
type="submit"
|
||||
>
|
||||
{{ $t('getStarted') }}
|
||||
@@ -133,10 +157,12 @@
|
||||
border: 2px solid transparent;
|
||||
box-shadow: 0 1px 3px 0 rgba($black, 0.16), 0 1px 3px 0 rgba($black, 0.24);
|
||||
|
||||
&:focus, &:active {
|
||||
background-color: $blue-50;
|
||||
border: 2px solid $purple-400;
|
||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
||||
&:not(:disabled):not(.disabled) {
|
||||
&:focus, &:active {
|
||||
background-color: $blue-50;
|
||||
border: 2px solid $purple-400;
|
||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,23 +174,19 @@
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
import PrivacyBanner from '@/components/header/banners/privacy';
|
||||
import accountCreation from '@/mixins/accountCreation';
|
||||
import sanitizeRedirect from '@/mixins/sanitizeRedirect';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PrivacyBanner,
|
||||
},
|
||||
mixins: [sanitizeRedirect],
|
||||
mixins: [accountCreation, sanitizeRedirect],
|
||||
data () {
|
||||
return {
|
||||
authData: {},
|
||||
email: '',
|
||||
password: '',
|
||||
passwordConfirm: '',
|
||||
privacyAccepted: false,
|
||||
registrationMethod: null,
|
||||
username: '',
|
||||
usernameIssues: [],
|
||||
needsEmailField: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -183,30 +205,40 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
if (window.sessionStorage.getItem('apple-token')) {
|
||||
this.registrationMethod = 'apple';
|
||||
} else if (!this.$store.state.registrationOptions.registrationMethod) {
|
||||
this.$router.push('/');
|
||||
} else {
|
||||
this.registrationMethod = this.$store.state.registrationOptions.registrationMethod;
|
||||
}
|
||||
this.authData = this.$store.state.registrationOptions.authData;
|
||||
this.email = this.$store.state.registrationOptions.email;
|
||||
this.username = this.$store.state.registrationOptions.username;
|
||||
this.password = this.$store.state.registrationOptions.password;
|
||||
this.passwordConfirm = this.$store.state.registrationOptions.passwordConfirm;
|
||||
|
||||
if (!this.email) {
|
||||
if (window.sessionStorage.getItem('apple-token')) {
|
||||
this.registrationMethod = 'apple';
|
||||
if (!this.email) {
|
||||
this.email = window.sessionStorage.getItem('apple-email');
|
||||
}
|
||||
} else if (!this.$store.state.registrationOptions.registrationMethod) {
|
||||
this.$router.push('/');
|
||||
} else {
|
||||
this.registrationMethod = this.$store.state.registrationOptions.registrationMethod;
|
||||
}
|
||||
|
||||
if (!this.email && this.registrationMethod !== 'apple') {
|
||||
return;
|
||||
}
|
||||
const usernameToCheck = this.email.split('@')[0].replace(/[^a-zA-Z0-9\-_]/g, '');
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username: usernameToCheck,
|
||||
}).then(res => {
|
||||
if (!res.issues) {
|
||||
this.username = usernameToCheck;
|
||||
}
|
||||
});
|
||||
|
||||
if ((!this.email || this.email === '') && this.registrationMethod === 'apple') {
|
||||
this.needsEmailField = true;
|
||||
}
|
||||
if (this.email) {
|
||||
const usernameToCheck = this.email.split('@')[0].replace(/[^a-zA-Z0-9\-_]/g, '');
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username: usernameToCheck,
|
||||
}).then(res => {
|
||||
if (!res.issues) {
|
||||
this.username = usernameToCheck;
|
||||
}
|
||||
});
|
||||
}
|
||||
document.getElementById('usernameInput').focus();
|
||||
},
|
||||
methods: {
|
||||
@@ -237,6 +269,7 @@ export default {
|
||||
idToken: window.sessionStorage.getItem('apple-token'),
|
||||
name: window.sessionStorage.getItem('apple-name'),
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
allowRegister: true,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -189,7 +189,7 @@ export default {
|
||||
this.cancel();
|
||||
return [];
|
||||
}
|
||||
this.currentSearch = regexRes[1];
|
||||
this.currentSearch = regexRes[1]; // eslint-disable-line prefer-destructuring
|
||||
|
||||
if (this.currentSearch.length === 0) return [];
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@ export default {
|
||||
return this.userGuilds.filter(group => {
|
||||
const leaderId = group.leader?._id || group.leader;
|
||||
if (leaderId !== this.user._id) return false;
|
||||
const purchased = group.purchased;
|
||||
const { purchased } = group;
|
||||
if (!purchased?.wasUpgraded) return false;
|
||||
if (this.activeGroupPlanIds.includes(group._id)) return false;
|
||||
if (!purchased.dateTerminated) return false;
|
||||
@@ -492,7 +492,7 @@ export default {
|
||||
},
|
||||
isPartyPreviouslyUpgraded () {
|
||||
if (!this.userParty) return false;
|
||||
const purchased = this.userParty.purchased;
|
||||
const { purchased } = this.userParty;
|
||||
if (!purchased?.wasUpgraded) return false;
|
||||
if (!purchased.dateTerminated) return false;
|
||||
return new Date(purchased.dateTerminated) < new Date();
|
||||
@@ -533,7 +533,7 @@ export default {
|
||||
|
||||
this.$nextTick(() => {
|
||||
if (this.upgradeableGuilds.length > 0) {
|
||||
this.selectedOption = this.upgradeableGuilds[0];
|
||||
[this.selectedOption] = this.upgradeableGuilds;
|
||||
} else if (this.upgradeableParty) {
|
||||
this.selectedOption = this.upgradeableParty;
|
||||
} else {
|
||||
|
||||
@@ -198,7 +198,6 @@ import dailyIcon from '@/assets/svg/daily.svg?raw';
|
||||
import todoIcon from '@/assets/svg/todo.svg?raw';
|
||||
import rewardIcon from '@/assets/svg/reward.svg?raw';
|
||||
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
@@ -438,14 +437,6 @@ export default {
|
||||
return false;
|
||||
},
|
||||
changeMirrorPreference (newVal) {
|
||||
Analytics.track({
|
||||
eventName: 'mirror tasks',
|
||||
eventAction: 'mirror tasks',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
mirror: newVal,
|
||||
group: this.group._id,
|
||||
}, { trackOnClient: true });
|
||||
const groupsToMirror = this.user.preferences.tasks.mirrorGroupTasks || [];
|
||||
if (newVal) { // we're turning copy ON for this group
|
||||
groupsToMirror.push(this.group._id);
|
||||
|
||||
@@ -240,7 +240,6 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import closeX from '../ui/closeX';
|
||||
|
||||
@@ -276,11 +275,6 @@ export default {
|
||||
this.$store.state.party.data = party;
|
||||
this.user.party._id = party._id;
|
||||
|
||||
Analytics.updateUser({
|
||||
partyID: party._id,
|
||||
partySize: 1,
|
||||
});
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||
await this.$router.push('/party');
|
||||
},
|
||||
|
||||
@@ -314,7 +314,6 @@ import extend from 'lodash/extend';
|
||||
import groupUtilities from '@/mixins/groupsUtilities';
|
||||
import styleHelper from '@/mixins/styleHelper';
|
||||
import { mapGetters } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import participantListModal from './participantListModal';
|
||||
import groupFormModal from './groupFormModal';
|
||||
import groupGemsModal from '@/components/groups/groupGemsModal';
|
||||
@@ -560,7 +559,6 @@ export default {
|
||||
|
||||
if (this.isParty) {
|
||||
data.type = 'party';
|
||||
Analytics.updateUser({ partySize: null, partyID: null });
|
||||
this.$store.state.partyMembers = [];
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,6 @@ import orderBy from 'lodash/orderBy';
|
||||
import * as quests from '@/../../common/script/content/quests';
|
||||
import getItemInfo from '@/../../common/script/libs/getItemInfo';
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
import navigationBack from '@/assets/svg/navigation_back.svg?raw';
|
||||
import questDialogContent from '../shops/quests/questDialogContent';
|
||||
@@ -421,11 +420,6 @@ export default {
|
||||
async questInit () {
|
||||
this.loading = true;
|
||||
|
||||
Analytics.updateUser({
|
||||
partyID: this.group._id,
|
||||
partySize: this.group.memberCount,
|
||||
});
|
||||
|
||||
const groupId = this.group._id || this.user.party._id;
|
||||
|
||||
const key = this.selectedQuest;
|
||||
|
||||
@@ -134,10 +134,10 @@
|
||||
></div>
|
||||
{{
|
||||
(Math.ceil(parseFloat(group.quest.progress.hp) * 100) / 100)
|
||||
| localizeNumber(user.preferences.language, { toFixed:2 })
|
||||
| localizeNumber(user.preferences.language, { toFixed: 0 })
|
||||
}} / {{
|
||||
parseFloat(questData.boss.hp)
|
||||
| localizeNumber(user.preferences.language, { toFixed:2 })
|
||||
| localizeNumber(user.preferences.language, { toFixed: 0 })
|
||||
}}
|
||||
<strong>HP</strong>
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
{{
|
||||
(user.party.quest.progress.up || 0)
|
||||
| floor(10)
|
||||
| localizeNumber(user.preferences.language, { toFixed:1 })
|
||||
| localizeNumber(user.preferences.language, { toFixed: 0 })
|
||||
}}
|
||||
{{ $t('pendingDamageLabel') }}
|
||||
</span>
|
||||
@@ -198,7 +198,7 @@
|
||||
class="float-left"
|
||||
>{{ $t('rage') }} {{
|
||||
parseFloat(group.quest.progress.rage)
|
||||
| localizeNumber(user.preferences.language, { toFixed: 2 })
|
||||
| localizeNumber(user.preferences.language, { toFixed: 0 })
|
||||
}} / {{
|
||||
questData.boss.rage.value
|
||||
| localizeNumber(user.preferences.language)
|
||||
|
||||
@@ -123,7 +123,6 @@
|
||||
|
||||
<script>
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { mapGetters, mapActions } from '@/libs/store';
|
||||
import MemberDetails from '../memberDetails';
|
||||
import createPartyModal from '../groups/createPartyModal';
|
||||
@@ -236,22 +235,8 @@ export default {
|
||||
},
|
||||
async createOrInviteParty () {
|
||||
if (this.user.party._id) {
|
||||
await Analytics.track({
|
||||
eventName: 'Header Party CTA',
|
||||
eventAction: 'Header Party CTA',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
state: 'Find Party Members',
|
||||
});
|
||||
this.$router.push('/looking-for-party');
|
||||
} else {
|
||||
await Analytics.track({
|
||||
eventName: 'Header Party CTA',
|
||||
eventAction: 'Header Party CTA',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
state: 'Get Started',
|
||||
});
|
||||
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -416,7 +416,7 @@
|
||||
:aria-label="$t('gold')"
|
||||
v-html="icons.gold"
|
||||
></div>
|
||||
<span>{{ Math.floor(user.stats.gp * 100) / 100 }}</span>
|
||||
<span>{{ Math.floor(user.stats.gp) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline desktop-only">
|
||||
|
||||
@@ -114,7 +114,6 @@ import { mapState } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import guide from '@/mixins/guide';
|
||||
import { CONSTANTS, setLocalSetting } from '@/libs/userlocalManager';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
import yesterdailyModal from './tasks/yesterdailyModal';
|
||||
import newStuff from './news/modal';
|
||||
@@ -648,15 +647,6 @@ export default {
|
||||
// Reset daily analytics actions
|
||||
setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 0);
|
||||
setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 0);
|
||||
} else {
|
||||
// Note a failed cron event, for our records and investigation
|
||||
Analytics.track({
|
||||
eventName: 'cron failed',
|
||||
eventAction: 'cron failed',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
responseCode: response.status,
|
||||
}, { trackOnClient: true });
|
||||
}
|
||||
|
||||
// Sync
|
||||
|
||||
@@ -433,9 +433,6 @@ import lockableLabel from '@/components/tasks/modal-controls/lockableLabel';
|
||||
import notificationsMixin from '@/mixins/notifications';
|
||||
import paymentsMixin from '@/mixins/payments';
|
||||
|
||||
// analytics
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
selectTranslatedArray,
|
||||
@@ -536,16 +533,6 @@ export default {
|
||||
this.close();
|
||||
},
|
||||
submit () {
|
||||
if (this.paymentData.group && !this.paymentData.newGroup) {
|
||||
Analytics.track({
|
||||
hitType: 'event',
|
||||
eventName: 'group plan upgrade',
|
||||
eventAction: 'group plan upgrade',
|
||||
eventCategory: 'behavior',
|
||||
demographics: this.upgradedGroup.demographics,
|
||||
type: this.paymentData.group.type,
|
||||
}, { trackOnClient: true });
|
||||
}
|
||||
this.paymentData = {};
|
||||
this.$root.$emit('bv::hide::modal', 'payments-success-modal');
|
||||
},
|
||||
|
||||
@@ -37,6 +37,9 @@ export default {
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
window.sessionStorage.setItem('apple-token', response.idToken);
|
||||
if (response.email) {
|
||||
window.sessionStorage.setItem('apple-email', response.email);
|
||||
}
|
||||
window.location.href = '/username';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<draggable
|
||||
v-if="taskList.length > 0"
|
||||
v-if="taskList.length > 0 && !rerendering"
|
||||
ref="tasksList"
|
||||
class="sortable-tasks"
|
||||
:disabled="activeFilter.label === 'scheduled' || !canBeDragged()"
|
||||
@@ -432,6 +432,7 @@ export default {
|
||||
|
||||
selectedItemToBuy: {},
|
||||
dragging: false,
|
||||
rerendering: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -548,8 +549,8 @@ export default {
|
||||
if (this.taskListOverride) originTasks = this.taskListOverride;
|
||||
|
||||
// Server
|
||||
const taskIdToReplace = filteredList[data.newIndex];
|
||||
const newIndexOnServer = originTasks.findIndex(taskId => taskId === taskIdToReplace);
|
||||
const taskIdToReplace = filteredList[data.newIndex]._id;
|
||||
const newIndexOnServer = originTasks.findIndex(task => task._id === taskIdToReplace);
|
||||
|
||||
let newOrder;
|
||||
if (taskToMove.group.id && !this.isUser) {
|
||||
@@ -568,6 +569,9 @@ export default {
|
||||
// Client
|
||||
const deleted = originTasks.splice(data.oldIndex, 1);
|
||||
originTasks.splice(data.newIndex, 0, deleted[0]);
|
||||
this.rerendering = true;
|
||||
await this.$nextTick();
|
||||
this.rerendering = false;
|
||||
},
|
||||
async moveTo (task, where) { // where is 'top' or 'bottom'
|
||||
const taskIdToMove = task._id;
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
}, `type_${task.type}`
|
||||
]"
|
||||
@click="castEnd($event, task)"
|
||||
tabindex="0"
|
||||
@keypress.enter="$emit('editTask', task)"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
@@ -98,9 +100,7 @@
|
||||
<div
|
||||
class="task-clickable-area pt-1 pl-75 pb-0"
|
||||
:class="{ 'cursor-auto': !teamManagerAccess }"
|
||||
tabindex="0"
|
||||
@click="edit($event, task)"
|
||||
@keypress.enter="edit($event, task)"
|
||||
>
|
||||
<div class="d-flex justify-content-between">
|
||||
<h3
|
||||
@@ -432,10 +432,6 @@
|
||||
outline: none;
|
||||
transition: none;
|
||||
border: $purple-400 solid 1px;
|
||||
|
||||
:not(task-best-control-inner-habit) { // round icon
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.control-bottom-box {
|
||||
@@ -462,16 +458,13 @@
|
||||
&:hover:not(.task-not-editable.task-not-scoreable),
|
||||
&:focus-within:not(.task-not-editable.task-not-scoreable) {
|
||||
box-shadow: 0 1px 8px 0 rgba($black, 0.12), 0 4px 4px 0 rgba($black, 0.16);
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
|
||||
.task:not(.groupTask) {
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
.left-control, .right-control, .task-content {
|
||||
border-color: $purple-400;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
border: none;
|
||||
outline: 1px solid $purple-400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,11 +515,6 @@
|
||||
&-user {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-radius: 4px;
|
||||
border: $purple-400 solid 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.task-title + .task-dropdown ::v-deep .dropdown-menu {
|
||||
|
||||
@@ -55,11 +55,31 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<lockable-label
|
||||
:class-override="cssClass('headings')"
|
||||
:locked="challengeAccessRequired"
|
||||
:text="`${$t('text')}*`"
|
||||
/>
|
||||
<div class="d-flex align-items-center">
|
||||
<lockable-label
|
||||
class="mr-auto"
|
||||
:class-override="cssClass('headings')"
|
||||
:locked="challengeAccessRequired"
|
||||
:text="`${$t('text')}*`"
|
||||
/>
|
||||
<div
|
||||
id="spi-alert"
|
||||
class="d-flex align-items-center"
|
||||
:class="cssClass('headings')"
|
||||
>
|
||||
<div
|
||||
class="svg svg-icon color icon-16 mr-1"
|
||||
v-html="icons.alert"
|
||||
></div>
|
||||
<small
|
||||
class="my-1"
|
||||
>
|
||||
<a
|
||||
:class="cssClass('headings')"
|
||||
>{{ $t('avoidSPI') }}</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
ref="inputToFocus"
|
||||
v-model="task.text"
|
||||
@@ -79,10 +99,20 @@
|
||||
@keydown.esc="autoCompleteMixinHandleEscape($event)"
|
||||
>
|
||||
</div>
|
||||
<b-popover
|
||||
:target="'spi-alert'"
|
||||
triggers="hover"
|
||||
placement="bottom"
|
||||
offset="-128"
|
||||
>
|
||||
<div
|
||||
v-html="$t('avoidSPIDetails', spiLinkData)">
|
||||
</div>
|
||||
</b-popover>
|
||||
<div
|
||||
class="form-group mb-0"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<div class="d-flex align-items-center">
|
||||
<lockable-label
|
||||
class="mr-auto"
|
||||
:class-override="cssClass('headings')"
|
||||
@@ -147,13 +177,14 @@
|
||||
></div>
|
||||
</div>
|
||||
<input
|
||||
v-model="task.value"
|
||||
v-model="taskValue"
|
||||
class="form-control"
|
||||
type="number"
|
||||
required="required"
|
||||
placeholder="Enter a Value"
|
||||
step="0.01"
|
||||
step="1"
|
||||
min="0"
|
||||
@blur="validateValue()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -963,6 +994,20 @@
|
||||
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
}
|
||||
|
||||
.b-popover {
|
||||
margin-top: -5px;
|
||||
max-width: 330px;
|
||||
}
|
||||
|
||||
.popover-body {
|
||||
text-align: left;
|
||||
|
||||
a {
|
||||
color: $gray-500;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
@@ -1196,6 +1241,7 @@ import chevronIcon from '@/assets/svg/chevron.svg?raw';
|
||||
import calendarIcon from '@/assets/svg/calendar.svg?raw';
|
||||
import gripIcon from '@/assets/svg/grip.svg?raw';
|
||||
import InformationIcon from '@/components/ui/informationIcon.vue';
|
||||
import alertIcon from '@/assets/svg/for-css/alert-white.svg?raw';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -1231,6 +1277,7 @@ export default {
|
||||
streak: streakIcon,
|
||||
calendar: calendarIcon,
|
||||
grip: gripIcon,
|
||||
alert: alertIcon,
|
||||
}),
|
||||
members: [],
|
||||
membersNameAndId: [],
|
||||
@@ -1251,6 +1298,12 @@ export default {
|
||||
{ key: 'per', label: 'perception', description: 'perTaskText' },
|
||||
],
|
||||
calendarHighlights: { dates: [new Date()] },
|
||||
spiLinkData: {
|
||||
firstLink: '<a href="/static/privacy#section_1" target="_blank">',
|
||||
secondLink: '<a href="/static/privacy" target="_blank">',
|
||||
linkClose: '</a>',
|
||||
},
|
||||
taskValue: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -1399,7 +1452,7 @@ export default {
|
||||
this.task.down = !this.task.down;
|
||||
},
|
||||
weekdaysMin (dayNumber) {
|
||||
return moment.weekdaysMin(dayNumber);
|
||||
return this.$t(`weekdaysMin${dayNumber}`);
|
||||
},
|
||||
formattedDate (date) {
|
||||
return moment(date).format('MM/DD/YYYY');
|
||||
@@ -1431,8 +1484,9 @@ export default {
|
||||
if (!this.canSave) return;
|
||||
if (this.newChecklistItem) this.addChecklistItem();
|
||||
|
||||
if (this.task.type === 'reward' && this.task.value === '') {
|
||||
this.task.value = 0;
|
||||
if (this.task.type === 'reward') {
|
||||
this.validateValue();
|
||||
this.task.value = this.taskValue;
|
||||
}
|
||||
|
||||
if (this.purpose === 'create') {
|
||||
@@ -1557,6 +1611,10 @@ export default {
|
||||
|
||||
this.task.tags.push(tagResult.id);
|
||||
},
|
||||
validateValue () {
|
||||
this.taskValue = Number(this.taskValue);
|
||||
this.taskValue = Math.floor(this.taskValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
filters: {
|
||||
statFloor (value) {
|
||||
if (value < 1 && value > 0) {
|
||||
return Math.ceil(value * 10) / 10;
|
||||
return 1;
|
||||
}
|
||||
return Math.floor(value);
|
||||
},
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import round from './round';
|
||||
|
||||
function _convertToThousand (num) {
|
||||
return `${(num / (10 ** 3)).toFixed(1)}k`;
|
||||
return `${(num / (10 ** 3)).toFixed(0)}k`;
|
||||
}
|
||||
|
||||
function _convertToMillion (num) {
|
||||
return `${(num / (10 ** 6)).toFixed(1)}m`;
|
||||
return `${(num / (10 ** 6)).toFixed(0)}m`;
|
||||
}
|
||||
|
||||
function _convertToBillion (num) {
|
||||
return `${(num / (10 ** 9)).toFixed(1)}b`;
|
||||
return `${(num / (10 ** 9)).toFixed(0)}b`;
|
||||
}
|
||||
|
||||
export default function roundBigNumber (num) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import amplitude from 'amplitude-js';
|
||||
import Vue from 'vue';
|
||||
import getStore from '@/store';
|
||||
|
||||
const AMPLITUDE_KEY = import.meta.env.AMPLITUDE_KEY;
|
||||
const { AMPLITUDE_KEY } = import.meta.env;
|
||||
const REQUIRED_FIELDS = ['eventCategory', 'eventAction'];
|
||||
|
||||
let analyticsLoading = false;
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
// Vue plugin to globally expose a '$t' method that calls common/i18n.t.
|
||||
// Can be anywhere inside vue as 'this.$t' or '$t' in templates.
|
||||
|
||||
import moment from 'moment';
|
||||
import i18n from '@/../../common/script/i18n';
|
||||
|
||||
function loadLocale (i18nData) {
|
||||
// Load i18n strings
|
||||
i18n.strings = i18nData.strings;
|
||||
|
||||
// Load Moment.js locale
|
||||
const { language } = i18nData;
|
||||
|
||||
if (language && i18nData.momentLang && language.momentLangCode) {
|
||||
// Make moment available under `window` so that the locale can be set
|
||||
window.moment = moment;
|
||||
|
||||
// Execute the script and set the locale
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.text = i18nData.momentLang;
|
||||
head.appendChild(script);
|
||||
moment.updateLocale(language.momentLangCode);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -43,7 +43,7 @@ export function getSign (number) {
|
||||
}
|
||||
|
||||
export function round (number, nDigits) {
|
||||
return Math.abs(number.toFixed(nDigits || 1));
|
||||
return Math.abs(number.toFixed(nDigits || 0));
|
||||
}
|
||||
|
||||
export function getXPMessage (val) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
NavbarPlugin,
|
||||
CollapsePlugin,
|
||||
} from 'bootstrap-vue';
|
||||
import Fragment from 'vue-fragment';
|
||||
import AppComponent from './app';
|
||||
import { setUpLogging } from '@/libs/logging';
|
||||
import router from './router/index';
|
||||
@@ -44,7 +43,6 @@ Vue.use(FormRadioPlugin);
|
||||
Vue.use(TooltipPlugin);
|
||||
Vue.use(NavbarPlugin);
|
||||
Vue.use(CollapsePlugin);
|
||||
Vue.use(Fragment.Plugin);
|
||||
|
||||
setUpLogging();
|
||||
const store = getStore();
|
||||
|
||||
@@ -4,19 +4,13 @@ import {
|
||||
getDropClass, getXPMessage, getSign, round,
|
||||
} from '@/libs/notifications';
|
||||
|
||||
// See https://stackoverflow.com/questions/4187146/truncate-number-to-two-decimal-places-without-rounding
|
||||
function toFixedWithoutRounding (num, fixed) {
|
||||
const re = new RegExp(`^-?\\d+(?:\.\\d{0,${(fixed || -1)}})?`); // eslint-disable-line no-useless-escape
|
||||
return num.toString().match(re)[0];
|
||||
}
|
||||
|
||||
export const NotificationMixins = {
|
||||
computed: {
|
||||
...mapState({ notifications: 'notificationStore' }),
|
||||
},
|
||||
methods: {
|
||||
coins (money) {
|
||||
return this.round(money, 2);
|
||||
return this.round(money, 0);
|
||||
},
|
||||
crit (val) {
|
||||
const message = `${this.$t('critBonus')} ${Math.round(val)} %`;
|
||||
@@ -57,7 +51,7 @@ export const NotificationMixins = {
|
||||
},
|
||||
mp (val) {
|
||||
const cleanMp = `${val}`.replace('-', '').replace('+', '');
|
||||
this.notify(`${this.sign(val)} ${toFixedWithoutRounding(cleanMp, 1)}`, 'mp', 'glyphicon glyphicon-fire', this.sign(val));
|
||||
this.notify(`${this.sign(val)} ${cleanMp < 0 ? Math.floor(cleanMp) : Math.ceil(cleanMp)}`, 'mp', 'glyphicon glyphicon-fire', this.sign(val));
|
||||
},
|
||||
purchased (itemName) {
|
||||
this.text(this.$t('purchasedItem', {
|
||||
|
||||
@@ -6,9 +6,8 @@ import { mapState } from '@/libs/store';
|
||||
import encodeParams from '@/libs/encodeParams';
|
||||
import notificationsMixin from '@/mixins/notifications';
|
||||
import { CONSTANTS, setLocalSetting } from '@/libs/userlocalManager';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
const STRIPE_PUB_KEY = import.meta.env.STRIPE_PUB_KEY;
|
||||
const { STRIPE_PUB_KEY } = import.meta.env;
|
||||
|
||||
let stripeInstance = null;
|
||||
|
||||
@@ -207,16 +206,6 @@ export default {
|
||||
alert(`Error while redirecting to Stripe: ${checkoutSessionResult.error.message}`);
|
||||
throw checkoutSessionResult.error;
|
||||
}
|
||||
if (paymentType === 'groupPlan') {
|
||||
Analytics.track({
|
||||
hitType: 'event',
|
||||
eventName: 'group plan create',
|
||||
eventAction: 'group plan create',
|
||||
eventCategory: 'behavior',
|
||||
demographics: appState.newGroup.demographics,
|
||||
type: appState.newGroup.type,
|
||||
}, { trackOnClient: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error while redirecting to Stripe', err); // eslint-disable-line
|
||||
alert(`Error while redirecting to Stripe: ${err.message}`);
|
||||
|
||||
@@ -3,7 +3,6 @@ import Vue from 'vue';
|
||||
import scoreTask from '@/../../common/script/ops/scoreTask';
|
||||
import notifications from './notifications';
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
|
||||
|
||||
export default {
|
||||
@@ -58,15 +57,6 @@ export default {
|
||||
|
||||
const tasksScoredCount = getLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT);
|
||||
if (!tasksScoredCount || tasksScoredCount < 2) {
|
||||
Analytics.track({
|
||||
eventName: 'task scored',
|
||||
eventAction: 'task scored',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
uuid: user._id,
|
||||
taskType: task.type,
|
||||
direction,
|
||||
}, { trackOnClient: true });
|
||||
if (!tasksScoredCount) {
|
||||
setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 1);
|
||||
} else {
|
||||
@@ -94,7 +84,7 @@ export default {
|
||||
if (quest && user.party.quest && user.party.quest.key) {
|
||||
const userQuest = Content.quests[user.party.quest.key];
|
||||
if (quest.progressDelta && userQuest.boss) {
|
||||
this.damage(quest.progressDelta.toFixed(1));
|
||||
this.damage(quest.progressDelta.toFixed(0));
|
||||
} else if (quest.collection && userQuest.collect) {
|
||||
user.party.quest.progress.collectedItems += 1;
|
||||
this.quest('questCollection', quest.collection);
|
||||
|
||||
@@ -179,7 +179,7 @@ export default {
|
||||
if (questProgress > 0) {
|
||||
const userQuest = quests.quests[this.user.party.quest.key];
|
||||
if (userQuest.boss) {
|
||||
this.damage(questProgress.toFixed(1));
|
||||
this.damage(questProgress.toFixed(0));
|
||||
} else if (userQuest.collection && userQuest.collect) {
|
||||
this.quest('questCollection', questProgress);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ export default {
|
||||
|
||||
// @TODO: Task modal component is mutating a prop
|
||||
// and that causes issues. We need to not copy the prop similar to group modals
|
||||
if (this.task) this.checklist = clone(this.task.checklist);
|
||||
if (this.task) {
|
||||
this.checklist = clone(this.task.checklist);
|
||||
this.taskValue = this.task.value;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -90,7 +90,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment v-if="allowedToChangeClass">
|
||||
<div class="d-content" v-if="allowedToChangeClass">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -55,7 +55,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -77,7 +77,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -83,7 +83,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -260,6 +260,9 @@ export default {
|
||||
} else if (this.restoreValues[stat] > MAX_FIELD_HARD_CAP) {
|
||||
this.restoreValues[stat] = MAX_FIELD_HARD_CAP;
|
||||
valid = false;
|
||||
} else if (!Number.isInteger(this.restoreValues[stat])) {
|
||||
this.restoreValues[stat] = Math.floor(this.restoreValues[stat]);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr>
|
||||
<td class="settings-label">
|
||||
{{ $t("showHeader") }}
|
||||
@@ -26,7 +26,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -67,7 +67,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-for="network in SOCIAL_AUTH_NETWORKS"
|
||||
:key="network.key"
|
||||
@@ -39,7 +39,7 @@
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -66,7 +66,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -54,7 +54,7 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -48,7 +48,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<div class="d-content">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
@@ -76,7 +76,7 @@
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
:class="{
|
||||
'casting-spell': castingSpell,
|
||||
}"
|
||||
@dragover.prevent
|
||||
>
|
||||
<!-- <banned-account-modal /> -->
|
||||
<amazon-payments-modal v-if="!isStaticPage" />
|
||||
@@ -130,7 +131,6 @@ import PrivacyBanner from '@/components/header/banners/privacy';
|
||||
import AppFooter from '@/components/appFooter';
|
||||
import notificationsDisplay from '@/components/notifications';
|
||||
import { mapState } from '@/libs/store';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import BuyModal from '@/components/shops/buyModal.vue';
|
||||
import SelectMembersModal from '@/components/selectMembersModal.vue';
|
||||
import notifications from '@/mixins/notifications';
|
||||
@@ -276,7 +276,6 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
Analytics.updateUser();
|
||||
return this.loadAllTranslations();
|
||||
}).then(() => {
|
||||
this.$store.state.isUserLoaded = true;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import getStore from '@/store';
|
||||
import handleRedirect from './handleRedirect';
|
||||
|
||||
@@ -11,56 +10,56 @@ import { DEPRECATED_ROUTES } from '@/router/deprecated-routes';
|
||||
|
||||
// NOTE: when adding a page make sure to implement the `common:setTitle` action
|
||||
|
||||
const Logout = () => import(/* webpackChunkName: "auth" */'@/components/auth/logout');
|
||||
const Logout = () => import('@/components/auth/logout');
|
||||
|
||||
// Hall
|
||||
const HallPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/index');
|
||||
const PatronsPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/patrons');
|
||||
const HeroesPage = () => import(/* webpackChunkName: "hall" */'@/components/hall/heroes');
|
||||
const HallPage = () => import('@/components/hall/index');
|
||||
const PatronsPage = () => import('@/components/hall/patrons');
|
||||
const HeroesPage = () => import('@/components/hall/heroes');
|
||||
|
||||
// Admin Pages
|
||||
const AdminContainerPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/container');
|
||||
const AdminPanelPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/admin-panel');
|
||||
const AdminPanelUserPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/admin-panel/user-support');
|
||||
const AdminPanelSearchPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/admin-panel/search');
|
||||
const GroupAdminPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/groups');
|
||||
const GroupAdminGroupPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/groups/group-support');
|
||||
const BlockerPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin/blocker');
|
||||
const AdminContainerPage = () => import('@/components/admin/container');
|
||||
const AdminPanelPage = () => import('@/components/admin/admin-panel');
|
||||
const AdminPanelUserPage = () => import('@/components/admin/admin-panel/user-support');
|
||||
const AdminPanelSearchPage = () => import('@/components/admin/admin-panel/search');
|
||||
const GroupAdminPage = () => import('@/components/admin/groups');
|
||||
const GroupAdminGroupPage = () => import('@/components/admin/groups/group-support');
|
||||
const BlockerPage = () => import('@/components/admin/blocker');
|
||||
|
||||
// Tasks
|
||||
const UserTasks = () => import(/* webpackChunkName: "userTasks" */'@/components/tasks/user');
|
||||
const UserTasks = () => import('@/components/tasks/user');
|
||||
|
||||
// Inventory
|
||||
const InventoryContainer = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/index');
|
||||
const ItemsPage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/items/index');
|
||||
const EquipmentPage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/equipment/index');
|
||||
const StablePage = () => import(/* webpackChunkName: "inventory" */'@/components/inventory/stable/index');
|
||||
const InventoryContainer = () => import('@/components/inventory/index');
|
||||
const ItemsPage = () => import('@/components/inventory/items/index');
|
||||
const EquipmentPage = () => import('@/components/inventory/equipment/index');
|
||||
const StablePage = () => import('@/components/inventory/stable/index');
|
||||
|
||||
// Guilds & Parties
|
||||
const GroupPage = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/group');
|
||||
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ '@/components/static/groupPlans');
|
||||
const LookingForParty = () => import(/* webpackChunkName: "guilds" */ '@/components/groups/lookingForParty');
|
||||
const GroupPage = () => import('@/components/groups/group');
|
||||
const GroupPlansAppPage = () => import('@/components/static/groupPlans');
|
||||
const LookingForParty = () => import('@/components/groups/lookingForParty');
|
||||
|
||||
// Group Plans
|
||||
const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/index');
|
||||
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/taskInformation');
|
||||
const GroupPlanBilling = () => import(/* webpackChunkName: "group-plans" */ '@/components/group-plans/billing');
|
||||
const GroupPlanIndex = () => import('@/components/group-plans/index');
|
||||
const GroupPlanTaskInformation = () => import('@/components/group-plans/taskInformation');
|
||||
const GroupPlanBilling = () => import('@/components/group-plans/billing');
|
||||
|
||||
const MessagesIndex = () => import(/* webpackChunkName: "private-messages" */ '@/pages/private-messages/index.vue');
|
||||
const MessagesIndex = () => import('@/pages/private-messages/index.vue');
|
||||
|
||||
// Challenges
|
||||
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/index');
|
||||
const MyChallenges = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/myChallenges');
|
||||
const FindChallenges = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/findChallenges');
|
||||
const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ '@/components/challenges/challengeDetail');
|
||||
const ChallengeIndex = () => import('@/components/challenges/index');
|
||||
const MyChallenges = () => import('@/components/challenges/myChallenges');
|
||||
const FindChallenges = () => import('@/components/challenges/findChallenges');
|
||||
const ChallengeDetail = () => import('@/components/challenges/challengeDetail');
|
||||
|
||||
// Shops
|
||||
const ShopsContainer = () => import(/* webpackChunkName: "shops" */'@/components/shops/index');
|
||||
const MarketPage = () => import(/* webpackChunkName: "shops-market" */'@/components/shops/market/index');
|
||||
const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/components/shops/quests/index');
|
||||
const CustomizationsPage = () => import(/* webpackChunkName: "shops-customizations" */'@/components/shops/customizations/index');
|
||||
const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index');
|
||||
const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index');
|
||||
const ShopsContainer = () => import('@/components/shops/index');
|
||||
const MarketPage = () => import('@/components/shops/market/index');
|
||||
const QuestsPage = () => import('@/components/shops/quests/index');
|
||||
const CustomizationsPage = () => import('@/components/shops/customizations/index');
|
||||
const SeasonalPage = () => import('@/components/shops/seasonal/index');
|
||||
const TimeTravelersPage = () => import('@/components/shops/timeTravelers/index');
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
@@ -318,15 +317,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
router.app.$root.$emit('update-party');
|
||||
}
|
||||
|
||||
if (to.name === 'lookingForParty') {
|
||||
Analytics.track({
|
||||
hitType: 'event',
|
||||
eventName: 'View Find Members',
|
||||
eventAction: 'View Find Members',
|
||||
eventCategory: 'behavior',
|
||||
}, { trackOnClient: true });
|
||||
}
|
||||
|
||||
// Redirect old guild urls
|
||||
if (to.hash.indexOf('#/options/groups/guilds/') !== -1) {
|
||||
const splits = to.hash.split('/');
|
||||
|
||||
@@ -21,8 +21,8 @@ const NewsPage = () => import('@/components/static/newStuff');
|
||||
const OverviewPage = () => import('@/components/static/overview');
|
||||
const PressKitPage = () => import('@/components/static/pressKit');
|
||||
const PrivacyPage = () => import('@/components/static/privacy');
|
||||
const RegisterLoginReset = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerLoginReset');
|
||||
const RegisterUsername = () => import(/* webpackChunkName: "auth" */'@/components/auth/registerUsername');
|
||||
const RegisterLoginReset = () => import('@/components/auth/registerLoginReset');
|
||||
const RegisterUsername = () => import('@/components/auth/registerUsername');
|
||||
const SubscriptionBenefitsFaq = () => import('@/components/static/subscriptionBenefitsFaq');
|
||||
const TermsPage = () => import('@/components/static/terms');
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ export async function appleAuth (store, params) {
|
||||
id_token: params.idToken,
|
||||
name: params.name,
|
||||
username: params.username,
|
||||
email: params.email,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -109,7 +110,10 @@ export async function appleAuth (store, params) {
|
||||
}
|
||||
|
||||
if (result.data.message && result.data.id_token) {
|
||||
return { idToken: result.data.id_token };
|
||||
return {
|
||||
idToken: result.data.id_token,
|
||||
email: result.data.email,
|
||||
};
|
||||
}
|
||||
|
||||
const user = result.data.data;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import Vue from 'vue';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
export async function getChat (store, payload) {
|
||||
const response = await axios.get(`/api/v4/groups/${payload.groupId}/chat?limit=400`);
|
||||
@@ -17,13 +16,6 @@ export async function postChat (store, payload) {
|
||||
url += `?previousMsg=${payload.previousMsg}`;
|
||||
}
|
||||
|
||||
if (group.type === 'party') {
|
||||
Analytics.updateUser({
|
||||
partyID: group.id,
|
||||
partySize: group.memberCount,
|
||||
});
|
||||
}
|
||||
|
||||
const response = await axios.post(url, {
|
||||
message: payload.message,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import omit from 'lodash/omit';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { loadAsyncResource } from '@/libs/asyncResource';
|
||||
|
||||
export async function getPublicGuilds (store, payload) {
|
||||
@@ -74,7 +73,6 @@ export async function join (store, payload) {
|
||||
if (invitationI !== -1) invitations.parties.splice(invitationI, 1);
|
||||
|
||||
user.party._id = groupId;
|
||||
Analytics.updateUser({ partyID: groupId });
|
||||
// load the party members so that they get shown in the header
|
||||
store.dispatch('party:getMembers');
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import * as shops from './shops';
|
||||
import * as snackbars from './snackbars';
|
||||
import * as worldState from './worldState';
|
||||
import * as news from './news';
|
||||
import * as analytics from './analytics';
|
||||
import * as faq from './faq';
|
||||
import * as blockers from './blockers';
|
||||
|
||||
@@ -44,7 +43,6 @@ const actions = flattenAndNamespace({
|
||||
snackbars,
|
||||
worldState,
|
||||
news,
|
||||
analytics,
|
||||
faq,
|
||||
blockers,
|
||||
});
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
|
||||
// export async function initQuest (store) {
|
||||
// }
|
||||
|
||||
export async function sendAction (store, payload) { // eslint-disable-line import/prefer-default-export, max-len
|
||||
// @TODO: Maybe move this to server
|
||||
let partyData = {};
|
||||
if (store.state.party && store.state.party.data) {
|
||||
partyData = {
|
||||
partyID: store.state.party.data._id,
|
||||
partySize: store.state.party.data.memberCount,
|
||||
};
|
||||
} else {
|
||||
partyData = {
|
||||
partyID: store.state.user.data.party._id,
|
||||
partySize: store.state.partyMembers.data.length,
|
||||
};
|
||||
}
|
||||
|
||||
Analytics.updateUser(partyData);
|
||||
|
||||
const response = await axios.post(`/api/v4/groups/${payload.groupId}/${payload.action}`);
|
||||
|
||||
// @TODO: Update user?
|
||||
|
||||
@@ -3,7 +3,6 @@ import Vue from 'vue';
|
||||
import compact from 'lodash/compact';
|
||||
import omit from 'lodash/omit';
|
||||
import { loadAsyncResource } from '@/libs/asyncResource';
|
||||
import * as Analytics from '@/libs/analytics';
|
||||
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
|
||||
|
||||
export function fetchUserTasks (store, options = {}) {
|
||||
@@ -112,15 +111,6 @@ export async function create (store, createdTask) {
|
||||
}
|
||||
const tasksCreatedCount = getLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT);
|
||||
if (!tasksCreatedCount || tasksCreatedCount < 2) {
|
||||
const uuid = store.state.user.data._id;
|
||||
Analytics.track({
|
||||
eventName: 'task created',
|
||||
eventAction: 'task created',
|
||||
eventCategory: 'behavior',
|
||||
hitType: 'event',
|
||||
uuid,
|
||||
taskType: taskRes.type,
|
||||
}, { trackOnClient: true });
|
||||
if (!tasksCreatedCount) {
|
||||
setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 1);
|
||||
} else {
|
||||
@@ -168,11 +158,15 @@ export async function collapseChecklist (store, task) {
|
||||
}
|
||||
|
||||
export async function destroy (store, task) {
|
||||
const list = store.state.tasks.data[`${task.type}s`];
|
||||
const taskIndex = list.findIndex(t => t._id === task._id);
|
||||
const type = `${task.type}s`;
|
||||
const listIndex = store.state.tasks.data[type].findIndex(t => t._id === task._id);
|
||||
const orderIndex = store.state.user.data.tasksOrder[type].indexOf(task._id);
|
||||
|
||||
if (taskIndex > -1) {
|
||||
list.splice(taskIndex, 1);
|
||||
if (listIndex > -1) {
|
||||
store.state.tasks.data[type].splice(listIndex, 1);
|
||||
}
|
||||
if (orderIndex > -1) {
|
||||
store.state.user.data.tasksOrder[type].splice(orderIndex, 1);
|
||||
}
|
||||
|
||||
await axios.delete(`/api/v4/tasks/${task._id}`);
|
||||
|
||||
@@ -8,14 +8,14 @@ describe('round big number filter', () => {
|
||||
});
|
||||
|
||||
test('can round thousands', () => {
|
||||
expect(roundBigNumberFilter(70065)).to.equal('70.1k');
|
||||
expect(roundBigNumberFilter(70065)).to.equal('70k');
|
||||
});
|
||||
|
||||
test('can round milions', () => {
|
||||
expect(roundBigNumberFilter(10000987)).to.equal('10.0m');
|
||||
expect(roundBigNumberFilter(10000987)).to.equal('10m');
|
||||
});
|
||||
|
||||
test('can round bilions', () => {
|
||||
expect(roundBigNumberFilter(1000000000)).to.equal('1.0b');
|
||||
expect(roundBigNumberFilter(1000000000)).to.equal('1b');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -159,10 +159,6 @@ export default defineConfig({
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
'^/analytics': {
|
||||
target: DEV_BASE_URL,
|
||||
changeOrigin: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -162,5 +162,8 @@
|
||||
"achievementCatsModalText": "Nasbíral jsi všechny kočičí mazlíčky!",
|
||||
"achievementRoughRiderModalText": "Nasbíral jsi všechny základní barvy nepohodlných mazlíčků a mountů!",
|
||||
"achievementRodentRulerModalText": "Nasbíral jsi všechny hlodavce!",
|
||||
"achievementCatsText": "Vylíhly se všechny standardní barvy kočičích mazlíčků: gepard, lev, šavlozubý tygr a tygr!"
|
||||
"achievementCatsText": "Vylíhly se všechny standardní barvy kočičích mazlíčků: gepard, lev, šavlozubý tygr a tygr!",
|
||||
"achievementRodentRuler": "Vládce hlodavců",
|
||||
"achievementCats": "Pasák koček",
|
||||
"achievementDomesticated": "Hejá"
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
"backgroundTavernNotes": "Navštiv krčmu města Habitica.",
|
||||
"backgrounds102015": "Sada 17: zveřejněna v říjnu 2015",
|
||||
"backgroundHarvestMoonText": "Měsíc při sklizni",
|
||||
"backgroundHarvestMoonNotes": "Kdákání pod měsícem při sklizni.",
|
||||
"backgroundHarvestMoonNotes": "Chechtej se pod sklizňovým měsícem.",
|
||||
"backgroundSlimySwampText": "Slizká bažina",
|
||||
"backgroundSlimySwampNotes": "Přebroď se slizkou bažinou.",
|
||||
"backgroundSwarmingDarknessText": "Valící se temnota",
|
||||
@@ -213,7 +213,7 @@
|
||||
"backgroundStormyRooftopsNotes": "Propliž se přes bouřlivé střechy.",
|
||||
"backgroundWindyAutumnText": "Větrný podzim",
|
||||
"backgroundWindyAutumnNotes": "Hoň se za listy během větrného podzimu.",
|
||||
"incentiveBackgrounds": "Prosté pozadí",
|
||||
"incentiveBackgrounds": "Standardní pozadí",
|
||||
"backgroundVioletText": "Fialová",
|
||||
"backgroundVioletNotes": "Živá fialová tapeta.",
|
||||
"backgroundBlueText": "Modrá",
|
||||
@@ -736,7 +736,64 @@
|
||||
"backgroundMaskMakersWorkshopNotes": "Vyzkoušej novou tvář v maskářově dílně.",
|
||||
"backgroundCemeteryGateText": "Hřbitovní brána",
|
||||
"backgroundCemeteryGateNotes": "Straš u hřbitovní brány.",
|
||||
"backgroundAutumnBridgeText": "Podzimní most",
|
||||
"backgroundAutumnBridgeNotes": "Obdivuj krásu podzimního mostu.",
|
||||
"backgroundInsideACrystalText": "Uvnitř krystalu."
|
||||
"backgroundAutumnBridgeText": "Most na podzim",
|
||||
"backgroundAutumnBridgeNotes": "Obdivuj krásu mostu na podzim.",
|
||||
"backgroundInsideACrystalText": "Uvnitř krystalu",
|
||||
"backgrounds032023": "Sada 106: Zveřejněna v březnu 2023",
|
||||
"backgroundOldTimeyBasketballCourtText": "Retro basketbalové hřiště",
|
||||
"backgroundOldTimeyBasketballCourtNotes": "Zaházej si na koš na retro basketbalovém hřišti.",
|
||||
"backgroundJungleWateringHoleText": "Napajedlo v džungli",
|
||||
"backgroundJungleWateringHoleNotes": "Zastav se na doušek u džunglového napajedla.",
|
||||
"backgroundMangroveForestText": "Mangrovový les",
|
||||
"backgroundMangroveForestNotes": "Prozkoumej okraj mangrovového lesa.",
|
||||
"backgrounds052023": "Sada 108: Zveřejněna v květnu 2023",
|
||||
"backgroundInAPaintingText": "V obraze",
|
||||
"backgroundFlyingOverHedgeMazeText": "Let nad labyrintem ze živého plotu",
|
||||
"backgroundFlyingOverHedgeMazeNotes": "Žasněte při letu nad labyrintem ze živého plotu.",
|
||||
"backgroundCretaceousForestText": "Křídový les",
|
||||
"backgroundCretaceousForestNotes": "Vychutnejte si pradávnou zeleň křídového lesa.",
|
||||
"backgroundLeafyTreeTunnelNotes": "Procházejte se tunelem z listnatých stromů.",
|
||||
"backgroundSpringtimeShowerText": "Jarní přeháňka",
|
||||
"backgroundSpringtimeShowerNotes": "Podívejte se na květnatou jarní přeháňku.",
|
||||
"backgroundUnderWisteriaText": "Pod vistérií",
|
||||
"backgrounds022023": "SADA 105: Vydáno v únoru 2023",
|
||||
"backgroundInFrontOfFountainText": "Před Fontánou",
|
||||
"backgroundInFrontOfFountainNotes": "Procházej se před Fontánou.",
|
||||
"backgroundGoldenBirdcageText": "Zlatá klec",
|
||||
"backgroundGoldenBirdcageNotes": "Schovej se v zlaté kleci.",
|
||||
"backgroundFancyBedroomText": "Luxusní ložnice",
|
||||
"backgroundFancyBedroomNotes": "Dopřej si luxus v luxusní ložnici.",
|
||||
"backgrounds042023": "Sada 107: Zveřejněna v dubnu 2023",
|
||||
"backgroundLeafyTreeTunnelText": "Tunel z listnatých stromů",
|
||||
"backgroundUnderWisteriaNotes": "Odpočiňte si pod vistérií.",
|
||||
"backgroundInAPaintingNotes": "Užijte si kreativní činnosti uvnitř obrazu.",
|
||||
"backgrounds012023": "SADA 104: Vydáno v lednu 2023",
|
||||
"backgroundRimeIceText": "Jinovatka",
|
||||
"backgroundRimeIceNotes": "Pokochej se třpytivou jinovatkou.",
|
||||
"backgroundSnowyTempleText": "Zasněžený chrám",
|
||||
"backgroundSnowyTempleNotes": "Pokochej se klidným zasněženým chrámem.",
|
||||
"backgroundWinterLakeWithSwansText": "Zimní jezero s labutěmi",
|
||||
"backgroundWinterLakeWithSwansNotes": "Užij si přírodu u zimního jezera s labutěmi.",
|
||||
"backgrounds122022": "SADA 103: Vydáno v prosinci 2022",
|
||||
"backgroundBranchesOfAHolidayTreeText": "Větve svátečního stromku",
|
||||
"backgroundBranchesOfAHolidayTreeNotes": "Dováděj na větvích svátečního stromku.",
|
||||
"backgroundInsideACrystalNotes": "Vyhlédni z nitra krystalu.",
|
||||
"backgroundSnowyVillageText": "Zasněžená vesnice",
|
||||
"backgroundSnowyVillageNotes": "Pokochej se zasněženou vesnicí.",
|
||||
"backgrounds062023": "Sada 109: Zveřejněna v červnu 2023",
|
||||
"backgroundInAnAquariumText": "V akváriu",
|
||||
"backgroundInAnAquariumNotes": "Zaplavejte si poklidně s rybkami v akváriu.",
|
||||
"backgroundInsideAdventurersHideoutText": "V úkrytu dobrodruhů",
|
||||
"backgroundInsideAdventurersHideoutNotes": "Naplánujte cestu v úkrytu dobrodruhů.",
|
||||
"backgroundCraterLakeText": "Kráterové jezero",
|
||||
"backgroundCraterLakeNotes": "Obdivujte nádherné kráterové jezero.",
|
||||
"backgrounds072023": "Sada 110: Zveřejněna v červenci 2023",
|
||||
"backgroundOnAPaddlewheelBoatText": "Na loďce s lopatkovým kolem",
|
||||
"backgroundOnAPaddlewheelBoatNotes": "Projet se na loďce s lopatkovým kolem.",
|
||||
"backgroundColorfulCoralText": "Barevný korál",
|
||||
"backgroundColorfulCoralNotes": "Potopte se mezi barevné korály.",
|
||||
"backgrounds082023": "Sada 111: zveřejněaa v srpnu 2023",
|
||||
"backgroundBonsaiCollectionText": "Sbírka bonsají",
|
||||
"backgroundBoardwalkIntoSunsetNotes": "Vydejte se po Stezce do západu slunce.",
|
||||
"backgroundBoardwalkIntoSunsetText": "Stezka do západu slunce"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"brokenChaLink": "Nefunkční odkaz na výzvu",
|
||||
"keepIt": "Ponechat",
|
||||
"removeIt": "Odstranit",
|
||||
"brokenChallenge": "Nefunkční odkaz na výzvu: tento úkol byl součástí výzvy, ale ta (nebo skupina, která ji vytvořila) byla odstraněna. Co chceš dělat s osiřelými úkoly?",
|
||||
"brokenChallenge": "Neplatný odkaz na výzvu",
|
||||
"challengeCompleted": "Výzva byla ukončena a vítězem se stal <span class=\"badge\"><%= user %></span>! Co chceš dělat s osiřelými úkoly?",
|
||||
"unsubChallenge": "Nefunkční odkaz na výzvu: tento úkol byl součástí výzvy, ze které jsi se odhlásil/a. Co chceš dělat s osiřelými úkoly?",
|
||||
"challenges": "Výzvy",
|
||||
@@ -85,7 +85,7 @@
|
||||
"summaryRequired": "Je požadováno shrnutí",
|
||||
"summaryTooLong": "Shrnutí je příliš dlouhé",
|
||||
"descriptionRequired": "Je požadován popis",
|
||||
"locationRequired": "Je požadováno vybrat lokaci výzvy ('Přidat k')",
|
||||
"locationRequired": "Je nutné vybrat umístění výzvy („Přidat do“)",
|
||||
"categoiresRequired": "Musí být vybrána jedna nebo více kategorií",
|
||||
"viewProgressOf": "Zobrazit pokrok",
|
||||
"viewProgress": "Zobrazit pokrok",
|
||||
@@ -94,5 +94,16 @@
|
||||
"selectParticipant": "Zvol účastníka",
|
||||
"filters": "Filtry",
|
||||
"wonChallengeDesc": "Vyhrál/a jsi výzvu <%= challengeName %>! Tvá výhra je zaznamenána ve tvých úspěších.",
|
||||
"yourReward": "Tvá odměna"
|
||||
"yourReward": "Tvá odměna",
|
||||
"brokenTaskDescription": "Tento úkol byl součástí výzvy, ale byl z ní odstraněn. Co chceš udělat?",
|
||||
"brokenChallengeDescription": "Tento úkol byl součástí výzvy, ale výzva (nebo skupina) byla smazána. Co chceš udělat s osiřelými úkoly?",
|
||||
"challengeCompletedDescription": "Vítězem je <%= user %>! Co chceš udělat s osiřelými úkoly?",
|
||||
"messageChallengeFlagAlreadyReported": "Tuto výzvu jsi už nahlásil.",
|
||||
"flaggedNotHidden": "Výzva byla nahlášena jednou, není skrytá",
|
||||
"flaggedAndHidden": "Výzva byla nahlášena a je skrytá",
|
||||
"resetFlagCount": "Resetovat počet nahlášení",
|
||||
"deleteChallengeRefundDescription": "Pokud tuto výzvu smažeš, bude ti vrácena odměna v drahokamech a úkoly z výzvy zůstanou na nástěnkách úkolů účastníků.",
|
||||
"messageChallengeFlagOfficial": "Oficiální výzvy nelze nahlásit.",
|
||||
"brokenTask": "Nefunkční odkaz na výzvu",
|
||||
"removeTasks": "Odstranit Úkoly"
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"battleGear": "Bojová výzbroj",
|
||||
"gear": "Výbava",
|
||||
"autoEquipBattleGear": "Automaticky použít nové vybavení",
|
||||
"costume": "Kostým",
|
||||
"costume": "kostým",
|
||||
"useCostume": "Použít kostým",
|
||||
"costumePopoverText": "Vyber \"Použít kostým\", abys vybavil svého avatara, aniž bys nějak ovlivnil statistiky tvé bojové výzbroje! To znamená, že můžeš obléct svého avatara do jakéhokoliv vybavení chceš a stále mít tvojí nejlepší bojovou výzbroj na sobě.",
|
||||
"autoEquipPopoverText": "Zvol tuto možnost pro automatické nasazení koupeného vybavení.",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"stable": "Stáj",
|
||||
"stable": "Mazlíčci a Mounty",
|
||||
"pets": "Mazlíčci",
|
||||
"activePet": "Aktivní mazlíček",
|
||||
"noActivePet": "Bez aktivního mazlíčka",
|
||||
@@ -109,5 +109,7 @@
|
||||
"wackyPets": "Šílená zvířátka",
|
||||
"invalidAmount": "Neplatný počet jídla,je vyžadováno pozitivní celé číslo",
|
||||
"tooMuchFood": "Snažíš se dát svému zvířeti moc jídla, akce byla zrušena",
|
||||
"notEnoughFood": "Nemáš dost jídla"
|
||||
"notEnoughFood": "Nemáš dost jídla",
|
||||
"veteranCactus": "Kaktus Veterán",
|
||||
"veteranDragon": "Drak Veterán"
|
||||
}
|
||||
|
||||
@@ -160,5 +160,25 @@
|
||||
"newPMNotificationTitle": "Nová zpráva od <%= name %>",
|
||||
"displaynameIssueNewline": "Zobrazovaná jména nesmí obsahovat zpětné lomítko následované písmenem N.",
|
||||
"resetAccount": "Resetovat účet",
|
||||
"giftedSubscriptionWinterPromo": "Ahoj <%= username %>, získal/a jsi <%= monthCount %> měsíce/ů předplatného jako součást naší sváteční dárkové akce!"
|
||||
"giftedSubscriptionWinterPromo": "Ahoj <%= username %>, získal/a jsi <%= monthCount %> měsíce/ů předplatného jako součást naší sváteční dárkové akce!",
|
||||
"generalSettings": "Hlavní nastavení",
|
||||
"siteData": "Údaje o webu",
|
||||
"taskSettings": "Nastavení úkolu",
|
||||
"confirmCancelChanges": "Jste si jistí? Neuložené změny přijdou vniveč.",
|
||||
"account": "Účet",
|
||||
"loginMethods": "Možnosti přihlášení",
|
||||
"character": "Postava",
|
||||
"siteLanguage": "Jazyk webu",
|
||||
"showLevelUpModal": "Při dosažení vyšší úrovně",
|
||||
"showHatchPetModal": "Při odchovu zvířátka",
|
||||
"showRaisePetModal": "Jak z domácího mazlíčka vychovat jízdní zvíře",
|
||||
"showStreakModal": "Při dosažení úspěchu v sérii",
|
||||
"baileyAnnouncement": "Nejnovější oznámení společnosti Bailey",
|
||||
"view": "Zobrazit",
|
||||
"feedbackPlaceholder": "Vlož zpětnou vazbu",
|
||||
"downloadCSV": "Stáhni si CSV",
|
||||
"downloadAs": "Ulož jako",
|
||||
"yourUserData": "Tvá uživatelská data",
|
||||
"taskHistory": "Historie",
|
||||
"yourUserDataDisclaimer": "Zde si lze stáhnout výpis historie úkolů nebo kompletní uživatelská data."
|
||||
}
|
||||
|
||||
@@ -3484,7 +3484,7 @@
|
||||
"shieldSpecialWinter2026WarriorText": "Raureif Schild",
|
||||
"shieldSpecialWinter2026WarriorNotes": "Stoppe eiskalt Hindernisse mit diesem praktischen, pieksigen Schild. Erhöht Ausdauer um %= con %>. Limitierte Ausgabe Winterausrüstung 2025-2026.",
|
||||
"headMystery202602Text": "Kirschblüte Fuchsohren",
|
||||
"headMystery202602Notes": " Diese Ohren schärfen dein Gehör so sehr, dass du im nahenden Frühling das Wachsen der Blütenknospen an den Zweigen der Bäume hören kannst. Gewährt keinen Attributbonus. Februar 2026 Abonnentengegenstand.",
|
||||
"headMystery202602Notes": "Diese Ohren schärfen dein Gehör so sehr, dass du im nahenden Frühling das Wachsen der Blütenknospen an den Zweigen der Bäume hören kannst. Gewährt keinen Attributbonus. Februar 2026 Abonnentengegenstand.",
|
||||
"headArmoireLoneCowpokeHatNotes": "Howdy Kumpel! Hasst du’s auch so, wenn du draußen auf dem Schießstand bist, an Aufgaben arbeitest und dir die Sonne in die Augen scheint? Also, gute Sache, dass du dafür jetzt ’nen Hut hast. Erhöht deine Wahrnehmung um <%= per %>. Verzauberter Schrank: Einsamer Cowboy Set (Item 1 of 2)",
|
||||
"shieldSpecialWinter2026HealerText": "Sternenexplosion",
|
||||
"shieldArmoireDoubleBassNotes": "Bom doo bom brrrr brr brr brrrr! Versammle deine Party, um euch zu erden oder zu tanzen, während ihr euch Musik von dieser tiefen Double Bass anhört. Erhört Ausdauer und Stärke um jeweils <%= attrs %>. Verzauberter Schwank: Musikinstrumente Set 2 (Gegenstand 3 von 3)",
|
||||
@@ -3495,5 +3495,11 @@
|
||||
"backMystery202601Notes": "Dieses Zeichen gewährt dem Anwender die Kontrolle über die Elemente der Jahreszeit von Kälte und Frost. Gewährt keinen Attributbonus. Januar 2026 Abonnentengegenstand.",
|
||||
"backMystery202602Text": "Fünf Schweife der Sakura",
|
||||
"backMystery202602Notes": "Diese flauschigen Schweife haben die Farbe der Kirschblüte, eine Erinnerung, dass der Frühling auf dem Weg ist. Gewährt keinen Autobusbonus. Februar 2026 Abonnentengegenstand.",
|
||||
"backArmoireHarpsichordText": "Cembalo"
|
||||
"backArmoireHarpsichordText": "Cembalo",
|
||||
"weaponSpecialSpring2026HealerText": "Schneeglöckchen Stab",
|
||||
"weaponSpecialSpring2026HealerNotes": "Eine Gelegenheit für einen Neuanfang liegt direkt vor dir, und mit diesem prächtigen Stab wirst du bereit sein! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Frühlingsausrüstung 2026.",
|
||||
"armorSpecialSpring2026WarriorText": "Froschrüstung",
|
||||
"armorSpecialSpring2026WarriorNotes": "Hüpf in Aktion, sobald der Schnee taut. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Frühlingsausrüstung 2026.",
|
||||
"armorSpecialSpring2026RogueText": "Birkenrinde Rüstung",
|
||||
"armorSpecialSpring2026RogueNotes": "Trotze dem unvermeidlichen Frühlingsregen ebenso wie leichten Brisen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Frühlingsausrüstung 2026."
|
||||
}
|
||||
|
||||
@@ -187,5 +187,7 @@
|
||||
"learnMore": "Learn More",
|
||||
"translateHabitica": "Translate Habitica",
|
||||
"whatToCallYou": "What should we call you?",
|
||||
"acceptPrivacyTOS": "You confirm that you are at least 18 years old, and that you have read and agree to our <a href='/static/terms' target='_blank'>Terms of Service</a> and <a href='/static/privacy' target='_blank'>Privacy Policy</a>"
|
||||
"acceptPrivacyTOS": "You confirm that you are at least 18 years old, and that you have read and agree to our <a href='/static/terms' target='_blank'>Terms of Service</a> and <a href='/static/privacy' target='_blank'>Privacy Policy</a>",
|
||||
"emailAddress": "Email address",
|
||||
"emailRequiredForSupport": "We require an email address for user support. Please enter an email address to continue creating your account."
|
||||
}
|
||||
|
||||
@@ -243,5 +243,7 @@
|
||||
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habitica’s Community Guidelines.",
|
||||
"targetUserNotExist": "Target User: '<%= userName %>' does not exist.",
|
||||
"rememberToBeKind": "Please remember to be kind, respectful, and follow the <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>.",
|
||||
"confirmPurchase": "Confirm Purchase"
|
||||
"confirmPurchase": "Confirm Purchase",
|
||||
"avoidSPI": "Avoid SPI",
|
||||
"avoidSPIDetails": "For your privacy, avoid including <%= firstLink %>sensitive personal information<%= linkClose %> (SPI) when using Habitica. Your account data, including tasks, is stored on our servers so you can access it from any device.<br><br>To learn more, review our <%= secondLink %>Privacy Policy<%= linkClose %>."
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user