Squashed commit of the following:
commit 16d8b87e90655c5444d0138e3c9df432b6dab8d9 Merge: 07387faf486bea232d47Author: negue <eugen.bolz@gmail.com> Date: Thu Sep 14 22:30:00 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 07387faf48c0060c493904a3d4639a20e6358b62 Author: negue <eugen.bolz@gmail.com> Date: Wed Sep 13 23:38:37 2023 +0200 remove generate promoCode from ui commit6bea232d47Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 11 12:55:31 2023 -0400 build(deps): bump core-js from 3.32.1 to 3.32.2 in /website/client (#14867) Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.32.1 to 3.32.2. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.32.2/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commitcebb3f0f25Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 11 12:43:49 2023 -0400 build(deps): bump webpack from 4.46.0 to 4.47.0 in /website/client (#14868) Bumps [webpack](https://github.com/webpack/webpack) from 4.46.0 to 4.47.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v4.46.0...v4.47.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit ea8563cd17bb2a60c74eb518bbce105b66a8ecd5 Merge: 3e16584dcf 6259955891 Author: negue <eugen.bolz@gmail.com> Date: Tue Aug 29 21:23:02 2023 +0200 Merge remote-tracking branch 'origin/negue/ui/setting' into negue/ui/setting commit 3e16584dcf70dc844d806f5f487bf94c171f91ad Author: negue <eugen.bolz@gmail.com> Date: Tue Aug 29 21:22:06 2023 +0200 fix PR comments commit 84ba44fb192c1e6ccd895beae067468285f438a3 Author: negue <eugen.bolz@gmail.com> Date: Tue Aug 29 20:38:54 2023 +0200 fix PR comments commit 6259955891a6469760768dcc5e6ae204fa3b44bd Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 25 11:20:26 2023 -0400 update form.scss commit da82bd8e68fb1d6567b987a61c2d15176ffa81c2 Author: negue <eugen.bolz@gmail.com> Date: Thu Aug 24 21:40:02 2023 +0200 remove ending commit 82e5fd2a83e9df85e0268e0937dd83be62bcc73d Author: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 22:25:41 2023 +0200 fix spacing commit 9ad06ea88bbb6b806823184a370366392ff7ad01 Author: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 22:09:22 2023 +0200 clean up debug row for login methods commit 41cde37675d6132425b3bb4bb0ac5badb9ba0447 Merge: 8c568060f982ebe71eb4Author: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 21:51:22 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 8c568060f93bf43b3dc82e1e02944bc4b86d5cce Author: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 21:49:31 2023 +0200 fix PR comments commit 36f7a4711da5a9da0e39d284500572f38a57d383 Merge: d279af7897647b27c55fAuthor: negue <eugen.bolz@gmail.com> Date: Fri Aug 11 20:04:15 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit d279af7897179dd9c020a795f535dcd25b50dfc0 Merge: ffbed3e044 b20ea44d49 Author: negue <eugen.bolz@gmail.com> Date: Wed Aug 9 21:13:37 2023 +0200 Merge branch 'negue/refactor/routes' into negue/ui/setting commit b20ea44d495fe9675360856a6f30c39cd9d3a1bd Author: negue <eugen.bolz@gmail.com> Date: Wed Aug 9 21:04:12 2023 +0200 Split Vue.Router routes commit ffbed3e0447fb9e3475a21d67ddb014e5b05aabf Author: negue <eugen.bolz@gmail.com> Date: Sun Jul 23 00:00:24 2023 +0200 remove console commit 4c350b01803797308bca78970077e9031175529d Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 23:34:20 2023 +0200 update Bailey Notification Text + fix popover commit c105b9ecf97dda77ec5374f5bec21dff39efb161 Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 23:21:53 2023 +0200 fix change password setting commit 06410b4807710af4cbb0ee714c692484a8d003fd Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 22:50:00 2023 +0200 fix reset account texts commit ccfdd9bb9c4217dbbd6c9cd0677e9125daf0daa9 Merge: 35c75304f18558dcc3a8Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 22:48:13 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 35c75304f1ff8ee317499c177a7ddd840468952e Author: negue <eugen.bolz@gmail.com> Date: Sun Jul 2 20:16:06 2023 +0200 more fixes commit 203e961464330eeb232e28773e2f57869be0507e Author: negue <eugen.bolz@gmail.com> Date: Sun Jul 2 19:45:17 2023 +0200 fix notification settings commit ec946047918bfbd9976529792a7251b934761afb Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 22:00:45 2023 +0200 applied same styling to promoCode.vue commit 0177b3a76b140157d999b8d7e1d26c90efd79fc4 Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 21:41:05 2023 +0200 move promoCode.vue to pages/settings commit 8fbb600273d3e52a39ae75d277067d64e526f5a9 Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 21:40:35 2023 +0200 saveCancelButtons.vue allow to hide the cancel part commit 4915f2a3fb0b7b7747b7a98fbb69d2270c41b418 Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 21:09:07 2023 +0200 Hide Transactions Page again commit 8b5ae17f02095cf85988fadc4fd1e36dc26f4005 Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 20:52:03 2023 +0200 also check for invalid arguments in the password settings commit aa97ed5299de6dd2dd19182a5cd2fd2c24705d2d Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 20:25:53 2023 +0200 fix localhost externalLinks check commit 87a4e4931bd0230b3af07574c7e2771c87becf2f Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 20:01:31 2023 +0200 show notification on username change + fix userEmail checks commit 6a6f55f6fcdb69e1ca0dcfb63ea7dc96c764cda9 Merge: f9ff5e5c55e49d26eacdAuthor: negue <eugen.bolz@gmail.com> Date: Sat Jun 24 22:54:00 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit f9ff5e5c55dbc8e519304308adde14748f878a0a Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 22:41:42 2023 +0200 check password inputs and mark invalid for "password change" setting commit 4497514eebcb0ae168b385774ca47da654bb9e71 Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 21:59:21 2023 +0200 show notification when chaning display name commit 3232f12f0dc39a71cc05a2d5c4b3f0c465383577 Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 21:55:25 2023 +0200 check current password valid style in "delete account" and "reset account" commit 582a2f1304dcad57cb26da6211d81a278c0fcd53 Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 21:27:20 2023 +0200 mark password field of email setting as invalid on wrong password commit 8e3b8a962a8da871825b894776e567ea739635fc Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 21:24:46 2023 +0200 refactor currentPasswordInput.vue to use validatedTextInput.vue commit 61521507a49f9a2998668a9ca2a9b2274b6d944d Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 20:20:56 2023 +0200 fix username setting: - unsaved values check - @ char must be first in input, otherwise not remove it for checks commit f74c29a065126e802d1301602d1f7aa2164644f6 Merge: c4b6f0c39cd4a5823916Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 19:54:06 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit c4b6f0c39c0526458faf0e5baed9999dfdacdd8b Merge: 37eee140ad6e3a367832Author: negue <eugen.bolz@gmail.com> Date: Fri May 12 22:08:08 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 37eee140ad20f453350b718d5ed149de4905c61b Author: negue <eugen.bolz@gmail.com> Date: Fri May 12 21:57:27 2023 +0200 delete account without password commit 48a6801f4e606388cbecca124bc7a3c28a969ac3 Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 22:06:29 2023 +0200 fix duplicate json entry commit 47a2189f497ac548f1aee184a4ea9faaad29e91f Merge: a56b4a445749f45d27e3Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 21:48:21 2023 +0200 Merge remote-tracking branch 'origin/release' into negue/ui/setting commit a56b4a445734c9d069ab7f476e15fabd644a9127 Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 21:37:31 2023 +0200 show current class on setting panel commit 9c973cca2a92918a36d2a7578bae64169a6167a9 Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 21:15:46 2023 +0200 fix selectDifficulty.vue - refactor selectList.vue commit 95b37b3ba3f74e9b2b14fdc991f79dee44d68ce9 Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 20:45:09 2023 +0200 migrate restoreValues fix to new setting component commit 7947b1c67d4324166b3e044a2e880c5104cb0beb Merge: ad3e4d604a71e165433aAuthor: negue <eugen.bolz@gmail.com> Date: Mon May 8 20:41:31 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit ad3e4d604a21dcfad410cd77c31b4fc58cddd6a3 Author: negue <eugen.bolz@gmail.com> Date: Sat Apr 29 01:18:25 2023 +0200 style fixes commit cea13d5bc3782b6e3f8a7ffae9c74a5da43cca6a Merge: 73a5e5fcabb159182188Author: negue <eugen.bolz@gmail.com> Date: Fri Apr 28 23:58:09 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 73a5e5fcabccf6a0909d0be5a81d8df95a2fd7ef Author: negue <eugen.bolz@gmail.com> Date: Tue Apr 25 20:51:14 2023 +0200 style / padding issues commit 0a10eb32ccbea0963dc35467314d877ed4beb458 Author: negue <eugen.bolz@gmail.com> Date: Sat Apr 15 20:54:08 2023 +0200 fix "setting new password" invalid check commit a79bec3fa5979df9415ad57421afa05ed701bbfc Author: negue <eugen.bolz@gmail.com> Date: Tue Apr 11 23:15:15 2023 +0200 add password for other logins commit 9ff17fd6ddca543846140f5eaf8046d457dd8dbb Author: negue <eugen.bolz@gmail.com> Date: Tue Apr 11 23:05:19 2023 +0200 "fix values" use keydown event to mark as change commit 1f470942a95466adb27450600f61992bcb005237 Author: negue <eugen.bolz@gmail.com> Date: Thu Apr 6 00:19:18 2023 +0200 delete old api.vue commit b4904a8b84d273e73014659c5f15821deb7e26a7 Merge: b5da7ccc70c8b98678d0Author: negue <eugen.bolz@gmail.com> Date: Thu Apr 6 00:18:07 2023 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit b5da7ccc70cbad6a3c769f63f6b220a0f8f5e105 Author: negue <eugen.bolz@gmail.com> Date: Thu Apr 6 00:11:36 2023 +0200 refactor webhook ui to use save/cancel buttons commit f49f67ff5cd6e0a46b2343e871bbaa168768f12d Author: negue <eugen.bolz@gmail.com> Date: Wed Apr 5 22:56:37 2023 +0200 remove unused settings commit cc73b44b25d1f3dd6507754d2a68652e111d8703 Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 29 23:40:30 2023 +0200 remove advancedCollapsed settings to start it opened commit e0300e87104f36d3e57809b570cd314319b027d5 Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 29 22:58:09 2023 +0200 remove displayInviteToPartyWhenPartyIs1 setting commit 1741ddfc6484ae2f7da26050a8453c89e9925229 Author: negue <eugen.bolz@gmail.com> Date: Mon Mar 20 23:00:17 2023 +0100 webhook margins commit 24a43d027c9847663db4255d2b2cd472c8ce02c7 Author: negue <eugen.bolz@gmail.com> Date: Mon Mar 20 22:40:19 2023 +0100 userid tooltip commit 42fcb20bc4a06d8d6cb72263481c1127a7fac51d Author: negue <eugen.bolz@gmail.com> Date: Thu Mar 16 00:51:10 2023 +0100 remove balance for choosing class commit 160848473d5bf24fe3b6bf327ff698584120b18c Author: negue <eugen.bolz@gmail.com> Date: Thu Mar 16 00:20:56 2023 +0100 show real class setting modal if enough gems available commit f74ba9738dad0171136b8e3c1e5c3eee5a7630a0 Author: negue <eugen.bolz@gmail.com> Date: Thu Mar 16 00:10:53 2023 +0100 update apple icon and size commit bf961bc7283b3ba05011b5d3ca50127da5bc9850 Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:59:42 2023 +0100 Copied API Token Notification commit 28f0220b4ea889bf841c8ca542eb23af3bd22cab Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:53:33 2023 +0100 remove blue color of setting links commit b53ccace9593e32ecd3ed5df28d81b3c899f31fc Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:43:06 2023 +0100 fix username/email setting input width commit 1dfa5b275dabe4baf7c887e2eb5c42e864a2e8a9 Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:11:32 2023 +0100 developer mode commit 776618d2db3d95d29bb8a49171740267b0238861 Author: negue <eugen.bolz@gmail.com> Date: Tue Mar 14 21:11:52 2023 +0100 Add new Pause Dailies Setting commit 576c80af7ef75a8d2ebb2c2ddb34fb4c32eed122 Merge: dec1a1159d377b152ffdAuthor: negue <eugen.bolz@gmail.com> Date: Tue Mar 14 21:04:05 2023 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit dec1a1159df17325c23718b929edf3244e033284 Author: negue <eugen.bolz@gmail.com> Date: Tue Mar 14 21:00:52 2023 +0100 developer mode dummy row commit 1e80a7d145e6fce8a95a98645728d548cc9d4a51 Author: negue <eugen.bolz@gmail.com> Date: Sat Mar 11 00:03:33 2023 +0100 WIP webhook row commit cc4bedbe2d40811724a98dd8191bb551af884c70 Author: negue <eugen.bolz@gmail.com> Date: Fri Mar 10 20:28:57 2023 +0100 add spritely login creds message to the new api-row / redirect old url to the new one commit f9833aa78a2cdd6aa08cfd72aa6ef2e7815f4b62 Author: negue <eugen.bolz@gmail.com> Date: Thu Mar 9 02:23:39 2023 +0100 API Token Row commit 123c9b9bb1706041b2a73b254a80db6f5b6a07c8 Author: negue <eugen.bolz@gmail.com> Date: Mon Mar 6 22:46:50 2023 +0100 "Your User Data" Row instead of Page commit 0ade5663ae6d83967876b66075411752c29454c9 Author: negue <eugen.bolz@gmail.com> Date: Fri Mar 3 22:43:03 2023 +0100 userid row commit b4f2236ab8d54b6d89066abaee4d087d0a92e570 Author: negue <eugen.bolz@gmail.com> Date: Fri Mar 3 22:22:32 2023 +0100 rename folder of setting rows commit 3b050861c45d8680d62894b652cd60912bbb3480 Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 21:11:48 2023 +0100 move remaining setting to generalSettings.vue - delete site.vue - start with siteData.vue commit b09298fb0171ccf70e63b56d2a6a9ef054719597 Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 20:56:03 2023 +0100 move taskSettings.vue and add it to the settings list commit 5ed25066ecaca2a42c7e80163968ec6912d710a5 Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 20:06:13 2023 +0100 size/margin for transactions commit 25e77cbd9559e75813f2a925cf743b69321ba91c Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 19:52:12 2023 +0100 move purchaseHistory.vue commit 8e4e1bcb0f3d960edc1b9dadc71cd9e4be615024 Merge: bb14d09aa4 85c50d50e9 Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 19:04:31 2023 +0100 Merge remote-tracking branch 'origin/negue/ui/setting' into negue/ui/setting commit 85c50d50e9606b8518584d9924620262a018a520 Author: SabreCat <sabe@habitica.com> Date: Thu Feb 16 14:23:27 2023 -0600 fix(css): remove redundant formatting for a elements commit bb14d09aa494954f47a6f0cce000aedd6eea4668 Author: negue <eugen.bolz@gmail.com> Date: Thu Feb 16 01:34:09 2023 +0100 remove console commit 8c5e722c7215928701a01207a29fbe560bcc2c4d Author: negue <eugen.bolz@gmail.com> Date: Thu Feb 16 01:26:43 2023 +0100 first try with the refactored UI of Login Methods commit 9c8770051db4ac3acf0c79eb9c10b966590748b6 Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 19:13:16 2023 +0100 fix dayStartAdjustmentSetting.vue for 0 value commit ee2ff3881b0a914a0858940088e345a2a38a4c25 Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 18:37:46 2023 +0100 fix color after refactor commit 121e7485ca075f5a8cf5f5ef5072b8221ba6521a Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 18:29:00 2023 +0100 mark audioThemeSetting as changed commit 98c65700039cd86f571f35fd534b472659b767ff Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 18:05:55 2023 +0100 fix ul/li style in resetAccount.vue commit fed824f7052a971a8f40b55ebc2c3937953b3af2 Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 17:49:36 2023 +0100 fix color of gem price commit 80365e537d6eeee28c94dc82a0ca4234b5245362 Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 17:44:55 2023 +0100 fix "fixValuesSetting.vue" commit d3e15c541325d911b9db746246f047231df385a1 Author: negue <eugen.bolz@gmail.com> Date: Wed Feb 8 01:06:27 2023 +0100 open forgot password in new tab commit 31edec9ec55d77840e8c991fcc53572efc2354d6 Author: negue <eugen.bolz@gmail.com> Date: Wed Feb 8 01:03:19 2023 +0100 move validatedTextInput.vue to shared components + fix check pos/size + input-error cleanup commit 2adfd8c2593e6a5b75ab2f29b88858a5e9d7a6dc Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 20:19:30 2023 +0100 hide class setting until level 10 commit 64fb4c0cf9c4cfbca8733966cc72ca7a6c1ab742 Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 19:32:40 2023 +0100 delete old modals (refactored into new settings ui) commit b5be137a8d072c483d06466773c82c1a469ece51 Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 19:27:26 2023 +0100 enable forgot password link in settings commit bec75c6e129b8dfe0d429a60e4f92b2562b0843d Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 18:52:54 2023 +0100 reset account + password required in api commit 64f7e7a1d9b06fe9bb56a8b729e7c88a3732926e Author: negue <eugen.bolz@gmail.com> Date: Mon Jan 30 23:22:55 2023 +0100 fix compile commit 7ffb5101beca7246f1563d589bb85e14b85367ae Merge: 2bfb130b929f64633a57Author: negue <eugen.bolz@gmail.com> Date: Mon Jan 30 22:47:05 2023 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 2bfb130b92f350667b9be682e80cb02499e9034b Author: negue <eugen.bolz@gmail.com> Date: Mon Jan 30 22:44:23 2023 +0100 remove restore-modal and replace it with the finished fix values setting commit 89530a133ce8e58228edeb44be34d8b5db80248f Author: negue <eugen.bolz@gmail.com> Date: Wed Jan 18 19:22:36 2023 +0100 wip fix values commit 428647fc71e35a9112d69282694da4ce2dbb408d Author: negue <eugen.bolz@gmail.com> Date: Sat Jan 14 21:50:22 2023 +0100 refactor change class to design update + clean up old site.vue settings commit 1f16819bc140e727d35ac81f97fa9f917602db14 Author: negue <eugen.bolz@gmail.com> Date: Wed Jan 11 22:41:05 2023 +0100 WIP fix values commit 6fef3d057958e6f79ff369fc68267d84679ad086 Author: negue <eugen.bolz@gmail.com> Date: Sat Jan 7 22:51:30 2023 +0100 check for unsaved changes when pressing cancel commit bef8a4cdfc79d60548bdb86b73654d11d4a7559c Merge: 494f32c3e3c7aadede4dAuthor: negue <eugen.bolz@gmail.com> Date: Sat Jan 7 22:10:53 2023 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 494f32c3e3f68334199846049ee4f12cecb1e011 Author: negue <eugen.bolz@gmail.com> Date: Wed Dec 21 00:55:31 2022 +0100 Class Setting commit bda210cfbb8b8bb19ba01cd5e87dff859b0f0306 Author: negue <eugen.bolz@gmail.com> Date: Tue Dec 20 23:01:41 2022 +0100 removes username, email and display name from site.vue commit 38198d7df6e6dd3fa01c709d29627610373c9d7c Author: negue <eugen.bolz@gmail.com> Date: Tue Dec 20 22:36:27 2022 +0100 WIP class setting commit dddcfa637f5bbb5da9a2a8c3454edcc26b13dee9 Author: negue <eugen.bolz@gmail.com> Date: Tue Dec 20 22:31:36 2022 +0100 fix styles commit ce0a5cf97417487b1fb2b7a3aa6b489b61966c0c Author: negue <eugen.bolz@gmail.com> Date: Sun Dec 11 23:57:07 2022 +0100 Scroll into opened Setting commit 7e0a95ddff3a4f12f71ba2aaa1a5c603015a9925 Author: negue <eugen.bolz@gmail.com> Date: Sun Dec 11 23:43:44 2022 +0100 Audio Theme Setting commit 9c556662fe98ca3d664a86b5ff669b127de8fc32 Author: negue <eugen.bolz@gmail.com> Date: Sun Dec 11 00:25:30 2022 +0100 prepare header settings but still hidden commit 30d8b27534080c55fff532ca2b0cb11a6c9bd713 Merge: a1d1a788b2580139ff69Author: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 23:36:36 2022 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit a1d1a788b26eff59d9494905083606ad13994279 Author: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 23:34:33 2022 +0100 DayStartAdjustmentSetting commit ddee94a3939c9e36541f1f5ffcc58a0de4c3b93a Author: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 20:00:12 2022 +0100 disable reset account button when password empty commit 30a6db4c2dee2f902fea3c5b8522c0efe8cf230b Author: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 19:54:21 2022 +0100 hide & reset previous setting when switching to a different one commit 78093848d7ee76c7c9be187896ed10fd941491cb Author: negue <eugen.bolz@gmail.com> Date: Wed Dec 7 22:19:15 2022 +0100 validated text input (in/valid border color + icon) commit e1b444ea63bf41d33d5d36fc330158aeecd262ae Author: negue <eugen.bolz@gmail.com> Date: Tue Dec 6 22:09:54 2022 +0100 re-enable box-shadow on hover commit 96dc4e47aeae0d7c86f756cea9c370c2d0e02c77 Author: negue <negue@users.noreply.github.com> Date: Mon Nov 28 01:13:47 2022 +0100 remove console log commit 69ad07daadfa42635137640c7e4d5df1a7756223 Author: negue <negue@users.noreply.github.com> Date: Mon Nov 28 01:01:17 2022 +0100 dateFormatSetting commit bc11c0cf759eb8f64c6eb0bede7a2b9456b93c5a Author: negue <negue@users.noreply.github.com> Date: Mon Nov 28 00:49:24 2022 +0100 move shared components / mixins commit 0d1a189c6449f0019c744788b9176b05b6e21039 Author: negue <negue@users.noreply.github.com> Date: Mon Nov 28 00:44:21 2022 +0100 language Setting + imports cleanup commit 29ebd89030eef3810654c5b683c3905dc58f1222 Author: negue <negue@users.noreply.github.com> Date: Sun Nov 27 23:23:02 2022 +0100 fix icon size + fix display name valid checks commit 5c7747517b89e24a0dab0330f9c71bd5c3f7c14e Merge: fd5cbc302690b34c4dacAuthor: negue <negue@users.noreply.github.com> Date: Sun Nov 27 23:08:35 2022 +0100 Merge remote-tracking branch 'origin/release' into negue/ui/setting commit fd5cbc30260d574455ad3014e64c0994accf4f89 Author: negue <negue@users.noreply.github.com> Date: Wed Nov 23 00:14:21 2022 +0100 fix conflicts commit 49361217b0c55efedb15c154b8c121e68c40d94f Merge: edb427158f04e2a39a9fAuthor: negue <negue@users.noreply.github.com> Date: Wed Nov 23 00:12:38 2022 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit edb427158fd69213eb34c57cd156de1ba08ba8bc Author: negue <negue@users.noreply.github.com> Date: Wed Nov 23 00:03:19 2022 +0100 disable save button if nothing was changed commit c7e40e9446f67dbdd8e12ee81d94d069f60278c8 Author: negue <negue@users.noreply.github.com> Date: Tue Nov 22 23:36:37 2022 +0100 delete account row commit 4bf740c531b6917a283c7278dc391fd014cbc26d Author: negue <negue@users.noreply.github.com> Date: Tue Nov 22 23:14:24 2022 +0100 Shared Modal Visible State commit d718153717c84bc8bd861e5e7eb303333f22bbbf Author: negue <negue@users.noreply.github.com> Date: Sun Nov 20 18:06:20 2022 +0100 resetAccount commit e25922f8b33f3dad2b13110d6fdbaba2a67d87f0 Author: negue <negue@users.noreply.github.com> Date: Wed Nov 16 23:39:26 2022 +0100 rename functional components for compiler commit fdbc2c0eee04146cf7ca8a458b9acc28c01bacbf Author: negue <negue@users.noreply.github.com> Date: Wed Nov 16 01:44:50 2022 +0100 password setting row commit 5fd5e6275aebce235cd7ced7bfc1d57b8b9cd8df Author: negue <negue@users.noreply.github.com> Date: Tue Nov 15 17:35:44 2022 +0100 update package-lock.json again commit 9d742fd9a1ae3b99dbeec48a24007473ae59d696 Author: negue <negue@users.noreply.github.com> Date: Tue Nov 15 17:24:15 2022 +0100 update package-lock.json commit cd588e74d5fbbb3a7e0da68f0850dbb542c7aa57 Author: negue <negue@users.noreply.github.com> Date: Mon Nov 14 02:12:39 2022 +0100 displayNameSetting.vue commit 265970c5efd001dbbd4957a122efdd7e8b278f3e Author: negue <negue@users.noreply.github.com> Date: Mon Nov 14 02:09:47 2022 +0100 fix lint commit a2b510caca7ec873c5ea5a694fec6904779b9085 Merge: 0bae5fbe024dca69f14bAuthor: negue <negue@users.noreply.github.com> Date: Mon Nov 14 01:15:02 2022 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 0bae5fbe0234841a4e58a78fcc8aaaa01196853b Author: negue <negue@users.noreply.github.com> Date: Sun Nov 13 22:00:34 2022 +0100 userEmailSetting commit 23da70fa2e8d759db5222aac659ed12436fecee2 Author: negue <negue@users.noreply.github.com> Date: Sun Nov 13 20:38:14 2022 +0100 extract save / cancel buttons and the shared inlineSetting "logic" commit 82047380f3ff60e293fad8843547665c1128f681 Author: negue <negue@users.noreply.github.com> Date: Sun Nov 13 20:18:21 2022 +0100 first setting (username) in the new layout commit 39150349c7a37108da06aa4928284164197cb355 Author: negue <negue@users.noreply.github.com> Date: Wed Nov 2 21:42:12 2022 +0100 Working on M1 - will be reverted on full merge commit f7787b318c43948176b6fb7e29bd971683d33c36 Merge: 4c0ecc993853fb28cc48Author: negue <negue@users.noreply.github.com> Date: Tue Nov 1 14:20:24 2022 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 4c0ecc99380cd67b7ae5c40fb1ff27981c80c4a9 Merge: 2f53613a4562b4315b3dAuthor: negue <negue@users.noreply.github.com> Date: Sun Oct 30 12:49:34 2022 +0100 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit 2f53613a459639f9e9e314a1bd77459225392efa Author: negue <eugen.bolz@gmail.com> Date: Mon Oct 10 22:54:41 2022 +0200 split routes for ease of dev commit 390f0fc69df10e90161d7f7bf1cb1e70c637d592 Merge: cf222ee63a137f7d53dcAuthor: negue <eugen.bolz@gmail.com> Date: Mon Oct 10 22:50:43 2022 +0200 Merge remote-tracking branch 'origin/develop' into negue/ui/setting commit cf222ee63af051fbc9d7aee2a1ba7322ae9c56f6 Author: negue <eugen.bolz@gmail.com> Date: Sun Oct 2 23:15:35 2022 +0200 Update remaining Notification labels commit f837cce125ad77465c2812615456dcf1c2111371 Author: negue <eugen.bolz@gmail.com> Date: Sun Oct 2 22:45:12 2022 +0200 move site popup settings to notifications commit fc5181c3a7670eb97918f4d30c3327a5ed8c090a Author: negue <eugen.bolz@gmail.com> Date: Sun Oct 2 21:12:24 2022 +0200 fix styling in notification settings commit 7b5568ed2343d7c7fc427ba53d9cbb684d0c907c Author: negue <eugen.bolz@gmail.com> Date: Sat Sep 10 16:00:56 2022 +0200 wip notification settings
666
package-lock.json
generated
|
|
@ -36,6 +36,7 @@
|
|||
"gulp-babel": "^8.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-nodemon": "^2.5.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"gulp.spritesmith": "^6.13.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"helmet": "^4.6.0",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ describe('POST /user/reset', () => {
|
|||
type: 'habit',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
|
|
@ -39,7 +41,9 @@ describe('POST /user/reset', () => {
|
|||
type: 'daily',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
|
|
@ -57,7 +61,9 @@ describe('POST /user/reset', () => {
|
|||
type: 'todo',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
|
|
@ -75,7 +81,9 @@ describe('POST /user/reset', () => {
|
|||
type: 'reward',
|
||||
});
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||
|
|
@ -87,6 +95,26 @@ describe('POST /user/reset', () => {
|
|||
expect(user.tasksOrder.rewards).to.be.empty;
|
||||
});
|
||||
|
||||
it('does not allow to reset if the password is missing', async () => {
|
||||
await expect(user.post('/user/reset', {
|
||||
password: '',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('missingPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not allow to reset if the password is wrong', async () => {
|
||||
await expect(user.post('/user/reset', {
|
||||
password: 'passdw',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('wrongPassword'),
|
||||
});
|
||||
});
|
||||
|
||||
it('does not delete challenge or group tasks', async () => {
|
||||
const guild = await generateGroup(user, {}, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||
const challenge = await generateChallenge(user, guild);
|
||||
|
|
@ -102,7 +130,9 @@ describe('POST /user/reset', () => {
|
|||
});
|
||||
await user.post(`/tasks/${groupTask._id}/assign`, [user._id]);
|
||||
|
||||
await user.post('/user/reset');
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
await user.sync();
|
||||
|
||||
await user.put('/user', {
|
||||
|
|
@ -133,7 +163,9 @@ describe('POST /user/reset', () => {
|
|||
},
|
||||
});
|
||||
|
||||
await hero.post('/user/reset');
|
||||
await user.post('/user/reset', {
|
||||
password: 'password',
|
||||
});
|
||||
|
||||
const heroRes = await admin.get(`/hall/heroes/${hero.auth.local.username}`);
|
||||
|
||||
|
|
|
|||
|
|
@ -126,13 +126,5 @@ describe('shared.ops.addTask', () => {
|
|||
expect(addTask(user)._editing).not.be.ok;
|
||||
expect(addTask(user)._edit).to.not.be.ok;
|
||||
});
|
||||
|
||||
it('respects advancedCollapsed preference', () => {
|
||||
user.preferences.advancedCollapsed = true;
|
||||
expect(addTask(user)._advanced).not.be.ok;
|
||||
|
||||
user.preferences.advancedCollapsed = false;
|
||||
expect(addTask(user)._advanced).to.be.ok;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ module.exports = {
|
|||
'import/no-unresolved': 'off',
|
||||
'import/extensions': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/no-mutating-props': 'warn',
|
||||
'vue/html-self-closing': ['error', {
|
||||
html: {
|
||||
void: 'never',
|
||||
|
|
|
|||
|
|
@ -55,3 +55,13 @@ in a separate `.add('function of component', ...`
|
|||
### Storybook Build
|
||||
|
||||
After each client build, storybook build is also triggered and will be available in `dist/storybook`
|
||||
|
||||
### Vue Structure
|
||||
|
||||
Currently pages and components are mixed in `/src/components` this is not a good way to find the files easy.
|
||||
|
||||
Thats why each changed / upcoming page / component should be put in either `/src/components` or in the `/src/pages` directory.
|
||||
|
||||
Inside Pages, each page can have a subfolder which contains sub-components only needed for that page - otherwise it has to be added to the normal components folder.
|
||||
|
||||
At the end of all the changes - the components should only contain components needed between all pages
|
||||
|
|
|
|||
665
website/client/package-lock.json
generated
|
|
@ -14679,32 +14679,57 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"assert": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
||||
"integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz",
|
||||
"integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==",
|
||||
"requires": {
|
||||
"object-assign": "^4.1.1",
|
||||
"util": "0.10.3"
|
||||
"object.assign": "^4.1.4",
|
||||
"util": "^0.10.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-properties": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
|
||||
"integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
|
||||
"requires": {
|
||||
"has-property-descriptors": "^1.0.0",
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
||||
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
"has-symbols": "^1.0.3",
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
|
||||
"requires": {
|
||||
"inherits": "2.0.1"
|
||||
"inherits": "2.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15543,9 +15568,9 @@
|
|||
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
||||
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||
},
|
||||
"bonjour": {
|
||||
"version": "3.5.0",
|
||||
|
|
@ -15762,7 +15787,7 @@
|
|||
"brorand": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
||||
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
|
||||
},
|
||||
"browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
|
|
@ -15834,9 +15859,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
@ -15915,12 +15940,12 @@
|
|||
"buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
|
||||
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
|
||||
},
|
||||
"builtin-status-codes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
||||
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug="
|
||||
"integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="
|
||||
},
|
||||
"cacache": {
|
||||
"version": "12.0.3",
|
||||
|
|
@ -16793,7 +16818,7 @@
|
|||
"constants-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U="
|
||||
"integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ=="
|
||||
},
|
||||
"contains-path": {
|
||||
"version": "0.1.0",
|
||||
|
|
@ -16907,9 +16932,9 @@
|
|||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.32.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.1.tgz",
|
||||
"integrity": "sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ=="
|
||||
"version": "3.32.2",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.2.tgz",
|
||||
"integrity": "sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ=="
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.11.0",
|
||||
|
|
@ -17037,9 +17062,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -17376,7 +17401,7 @@
|
|||
"de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
|
||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
|
|
@ -17670,9 +17695,9 @@
|
|||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"des.js": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
|
||||
"integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
|
||||
"integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0"
|
||||
|
|
@ -17819,9 +17844,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -18598,13 +18623,29 @@
|
|||
}
|
||||
},
|
||||
"eslint-plugin-vue": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz",
|
||||
"integrity": "sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ==",
|
||||
"version": "7.20.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.20.0.tgz",
|
||||
"integrity": "sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==",
|
||||
"requires": {
|
||||
"eslint-utils": "^2.1.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
"semver": "^5.6.0",
|
||||
"vue-eslint-parser": "^7.0.0"
|
||||
"semver": "^6.3.0",
|
||||
"vue-eslint-parser": "^7.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
|
|
@ -20578,9 +20619,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
@ -20724,7 +20765,7 @@
|
|||
"hmac-drbg": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
||||
"integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
|
||||
"requires": {
|
||||
"hash.js": "^1.0.3",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
|
|
@ -21030,7 +21071,7 @@
|
|||
"https-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
|
||||
"integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
|
|
@ -21886,7 +21927,7 @@
|
|||
"is-window": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz",
|
||||
"integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0="
|
||||
"integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg=="
|
||||
},
|
||||
"is-windows": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -23254,9 +23295,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -23323,7 +23364,7 @@
|
|||
"minimalistic-crypto-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
||||
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
|
|
@ -24388,7 +24429,7 @@
|
|||
"punycode": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
||||
"integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -24787,7 +24828,7 @@
|
|||
"os-browserify": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
|
||||
"integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc="
|
||||
"integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
|
|
@ -25089,9 +25130,9 @@
|
|||
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="
|
||||
},
|
||||
"pbkdf2": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
||||
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
|
||||
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
|
||||
"requires": {
|
||||
"create-hash": "^1.1.2",
|
||||
"create-hmac": "^1.1.4",
|
||||
|
|
@ -26147,9 +26188,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -26350,7 +26391,7 @@
|
|||
"querystring-es3": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
|
||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
|
||||
"integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "2.2.0",
|
||||
|
|
@ -29503,7 +29544,7 @@
|
|||
"to-arraybuffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
|
||||
"integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M="
|
||||
"integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA=="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
|
|
@ -29779,7 +29820,7 @@
|
|||
"tty-browserify": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
|
||||
"integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw=="
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
|
|
@ -30249,7 +30290,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -30285,7 +30326,7 @@
|
|||
"uuid-browser": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz",
|
||||
"integrity": "sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA="
|
||||
"integrity": "sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -30579,29 +30620,90 @@
|
|||
}
|
||||
},
|
||||
"vue-eslint-parser": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-yR0dLxsTT7JfD2YQo9BhnQ6bUTLsZouuzt9SKRP7XNaZJV459gvlsJo4vT2nhZ/2dH9j3c53bIx9dnqU2prM9g==",
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
|
||||
"integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-scope": "^5.1.1",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
"espree": "^6.1.2",
|
||||
"esquery": "^1.0.1",
|
||||
"lodash": "^4.17.15"
|
||||
"espree": "^6.2.1",
|
||||
"esquery": "^1.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
|
||||
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"requires": {
|
||||
"esrecurse": "^4.1.0",
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"espree": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
|
||||
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
|
||||
"requires": {
|
||||
"acorn": "^7.1.1",
|
||||
"acorn-jsx": "^5.2.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"esquery": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
|
||||
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
|
||||
"requires": {
|
||||
"estraverse": "^5.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"estraverse": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"esrecurse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"requires": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"estraverse": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"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",
|
||||
|
|
@ -30666,6 +30768,340 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"vue-template-babel-compiler": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-babel-compiler/-/vue-template-babel-compiler-2.0.0.tgz",
|
||||
"integrity": "sha512-O0GOktQ5TZCZ5sWVl8CbyLBFriwwai7xDBtpdUI1xZSbbVVNf5Um/mDHYJXaHX6vfhmeAuohggXxIi0RPgXZ4g==",
|
||||
"requires": {
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.14.2",
|
||||
"@babel/plugin-transform-arrow-functions": "^7.14.5",
|
||||
"@babel/plugin-transform-block-scoping": "^7.14.5",
|
||||
"@babel/plugin-transform-computed-properties": "^7.14.5",
|
||||
"@babel/plugin-transform-destructuring": "^7.14.5",
|
||||
"@babel/plugin-transform-parameters": "^7.14.5",
|
||||
"@babel/plugin-transform-spread": "^7.14.5",
|
||||
"@babel/types": "^7.14.5",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
"integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz",
|
||||
"integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz",
|
||||
"integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==",
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.2",
|
||||
"@babel/helper-compilation-targets": "^7.20.0",
|
||||
"@babel/helper-module-transforms": "^7.20.2",
|
||||
"@babel/helpers": "^7.20.1",
|
||||
"@babel/parser": "^7.20.2",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.2",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.1",
|
||||
"semver": "^6.3.0"
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.20.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz",
|
||||
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.2",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"jsesc": "^2.5.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"version": "7.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz",
|
||||
"integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==",
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.20.0",
|
||||
"@babel/helper-validator-option": "^7.18.6",
|
||||
"browserslist": "^4.21.3",
|
||||
"semver": "^6.3.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-environment-visitor": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
|
||||
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg=="
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
|
||||
"integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/types": "^7.19.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-hoist-variables": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
|
||||
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
|
||||
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
|
||||
"integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-plugin-utils": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz",
|
||||
"integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ=="
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-skip-transparent-expression-wrappers": {
|
||||
"version": "7.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz",
|
||||
"integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
|
||||
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
|
||||
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
|
||||
},
|
||||
"@babel/helper-validator-option": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
|
||||
"integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw=="
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
|
||||
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.0"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
|
||||
"integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.20.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
|
||||
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg=="
|
||||
},
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz",
|
||||
"integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.18.6",
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-object-rest-spread": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz",
|
||||
"integrity": "sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==",
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.20.1",
|
||||
"@babel/helper-compilation-targets": "^7.20.0",
|
||||
"@babel/helper-plugin-utils": "^7.20.2",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-transform-parameters": "^7.20.1"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-arrow-functions": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz",
|
||||
"integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.18.6"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-block-scoping": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz",
|
||||
"integrity": "sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-computed-properties": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz",
|
||||
"integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.18.9"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-destructuring": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz",
|
||||
"integrity": "sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-parameters": {
|
||||
"version": "7.20.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz",
|
||||
"integrity": "sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-spread": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz",
|
||||
"integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==",
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.19.0",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.18.9"
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
|
||||
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/parser": "^7.18.10",
|
||||
"@babel/types": "^7.18.10"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
|
||||
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.1",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.20.1",
|
||||
"@babel/types": "^7.20.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
|
||||
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.21.4",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
||||
"integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001400",
|
||||
"electron-to-chromium": "^1.4.251",
|
||||
"node-releases": "^2.0.6",
|
||||
"update-browserslist-db": "^1.0.9"
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001434",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001434.tgz",
|
||||
"integrity": "sha512-aOBHrLmTQw//WFa2rcF1If9fa3ypkC1wzqqiKHgfdrXTWcU8C4gKVZT77eQAPWN1APys3+uQ0Df07rKauXGEYA=="
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.284",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
|
||||
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
|
||||
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-template-compiler": {
|
||||
"version": "2.7.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz",
|
||||
|
|
@ -30728,9 +31164,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"anymatch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
||||
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
|
|
@ -30753,19 +31189,19 @@
|
|||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.5.0"
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
|
|
@ -30778,15 +31214,15 @@
|
|||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
|
||||
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"optional": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
|
|
@ -30808,9 +31244,9 @@
|
|||
"optional": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
|
|
@ -30863,9 +31299,9 @@
|
|||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "4.46.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz",
|
||||
"integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==",
|
||||
"version": "4.47.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz",
|
||||
"integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==",
|
||||
"requires": {
|
||||
"@webassemblyjs/ast": "1.9.0",
|
||||
"@webassemblyjs/helper-module-context": "1.9.0",
|
||||
|
|
@ -30890,37 +31326,6 @@
|
|||
"terser-webpack-plugin": "^1.4.3",
|
||||
"watchpack": "^1.7.4",
|
||||
"webpack-sources": "^1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||
"requires": {
|
||||
"cacache": "^12.0.2",
|
||||
"find-cache-dir": "^2.1.0",
|
||||
"is-wsl": "^1.1.0",
|
||||
"schema-utils": "^1.0.0",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^4.1.2",
|
||||
"webpack-sources": "^1.4.0",
|
||||
"worker-farm": "^1.7.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-bundle-analyzer": {
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@
|
|||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-vue": "^2.23.1",
|
||||
"chai": "^4.3.7",
|
||||
"core-js": "^3.32.1",
|
||||
"core-js": "^3.32.2",
|
||||
"dompurify": "^3.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-habitrpg": "^6.2.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"habitica-markdown": "^3.0.0",
|
||||
"hellojs": "^1.20.0",
|
||||
"inspectpack": "^4.7.1",
|
||||
|
|
@ -58,12 +58,14 @@
|
|||
"validator": "^13.9.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-cli-plugin-storybook": "2.1.0",
|
||||
"vue-fragment": "^1.6.0",
|
||||
"vue-mugen-scroll": "^0.2.6",
|
||||
"vue-router": "^3.6.5",
|
||||
"vue-template-compiler": "^2.7.10",
|
||||
"vue-template-babel-compiler": "^2.0.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
|
||||
"webpack": "^4.46.0"
|
||||
"webpack": "^4.47.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
&:hover, &:focus {
|
||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
||||
|
||||
&:disabled, &.disabled, &.btn-flat {
|
||||
&.btn-flat {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -264,6 +264,10 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
color: $blue-10;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
|
||||
.dropdown-toggle:hover {
|
||||
--caret-color: #{$purple-300};
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown.show > .dropdown-toggle:not(.btn-success) {
|
||||
|
|
@ -136,6 +140,8 @@
|
|||
|
||||
.dropdown-menu.show {
|
||||
min-width: 100% !important;
|
||||
overflow: scroll;
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ input, textarea, input.form-control, textarea.form-control {
|
|||
color: $gray-50;
|
||||
border: 1px solid $gray-400;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
&:hover:not(:disabled):not(:read-only) {
|
||||
border-color: $gray-300;
|
||||
}
|
||||
|
||||
&:active:not(:disabled), &:focus:not(:disabled) {
|
||||
&:active:not(:disabled):not(:read-only), &:focus:not(:disabled):not(:read-only) {
|
||||
border-color: $purple-400;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
|
|
@ -56,13 +56,13 @@ input, textarea, input.form-control, textarea.form-control {
|
|||
|
||||
&.input-valid, &.input-invalid {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center right 16px;
|
||||
background-position: center right 0.5rem;
|
||||
}
|
||||
|
||||
&.input-valid {
|
||||
padding-right: 37px;
|
||||
padding-right: 27px;
|
||||
background-image: url(~@/assets/svg/for-css/check.svg);
|
||||
background-size: 13px 10px;
|
||||
background-size: 1rem;
|
||||
}
|
||||
|
||||
&.input-invalid {
|
||||
|
|
@ -91,8 +91,10 @@ input, textarea, input.form-control, textarea.form-control {
|
|||
border-color: $gray-300;
|
||||
}
|
||||
|
||||
&:focus, &:active, &:focus-within {
|
||||
border: solid 1px $purple-400;
|
||||
&:not(:read-only) {
|
||||
&:focus, &:active, &:focus-within {
|
||||
border: solid 1px $purple-400;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-prepend , .input-group-append {
|
||||
|
|
@ -163,8 +165,22 @@ input, textarea, input.form-control, textarea.form-control {
|
|||
input {
|
||||
height: 30px;
|
||||
border: 0;
|
||||
background: $white !important;
|
||||
}
|
||||
|
||||
&.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
&.is-invalid {
|
||||
border-color: $red-100 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.input-error {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
|
||||
color: $maroon-10;
|
||||
}
|
||||
|
||||
.input-group-spaced {
|
||||
|
|
@ -231,20 +247,20 @@ $bg-disabled-control: $gray-10;
|
|||
background-color: inherit;
|
||||
}
|
||||
|
||||
&:focus:not(:checked):not(:disabled)~.custom-control-label::before,
|
||||
&:focus:not(:checked):not(:disabled)~.custom-control-label::before,
|
||||
&:active:not(:checked):not(:disabled)~.custom-control-label::before {
|
||||
border: 2px solid $gray-300;
|
||||
box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5);
|
||||
}
|
||||
|
||||
&:focus:checked:not(:disabled)~.custom-control-label::before,
|
||||
&:focus:checked:not(:disabled)~.custom-control-label::before,
|
||||
&:active:checked:not(:disabled)~.custom-control-label::before {
|
||||
box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5);
|
||||
border-color: 2 px solid $purple-400;
|
||||
background-color: $purple-400;
|
||||
}
|
||||
|
||||
&:focus:disabled~.custom-control-label::before,
|
||||
&:focus:disabled~.custom-control-label::before,
|
||||
&:active:disabled~.custom-control-label::before {
|
||||
box-shadow: 0 0 0 6px rgba($bg-disabled-control, 0.1);
|
||||
}
|
||||
|
|
@ -398,8 +414,6 @@ $bg-color: $purple-400;
|
|||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Disable default style Firefox for invalid elements.
|
||||
// Selectors taken from view-source:resource://gre-resources/forms.css on Firefox
|
||||
:not(output):-moz-ui-invalid {
|
||||
|
|
|
|||
|
|
@ -1,55 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1000"
|
||||
viewBox="0 0 1000 1187.198"
|
||||
version="1.1"
|
||||
height="1187.198"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Apple_1998.svg">
|
||||
<metadata
|
||||
id="metadata10">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.1767767"
|
||||
inkscape:cx="-1066.5045"
|
||||
inkscape:cy="964.94669"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="m 979.04184,925.18785 c -17.95397,41.47737 -39.20563,79.65705 -63.82824,114.75895 -33.56298,47.8528 -61.04356,80.9761 -82.22194,99.3698 -32.83013,30.192 -68.00529,45.6544 -105.67203,46.5338 -27.04089,0 -59.6512,-7.6946 -97.61105,-23.3035 -38.08442,-15.5358 -73.08371,-23.2303 -105.08578,-23.2303 -33.56296,0 -69.55888,7.6945 -108.06101,23.2303 -38.5608,15.6089 -69.62484,23.7432 -93.37541,24.5493 -36.12049,1.5389 -72.1237,-14.3632 -108.06101,-47.7796 -22.93711,-20.0059 -51.62684,-54.3017 -85.99592,-102.8874 C 92.254176,984.54592 61.937588,924.38175 38.187028,855.7902 12.750995,781.70252 0,709.95986 0,640.50361 0,560.94181 17.191859,492.32094 51.626869,434.81688 78.689754,388.62753 114.69299,352.19192 159.75381,325.44413 c 45.06086,-26.74775 93.74914,-40.37812 146.18212,-41.25019 28.68971,0 66.3125,8.8744 113.06613,26.31542 46.62174,17.49964 76.55727,26.37404 89.68198,26.37404 9.8124,0 43.06758,-10.37669 99.4431,-31.06405 53.31237,-19.18512 98.30724,-27.12887 135.16787,-23.99975 99.8828,8.06098 174.92313,47.43518 224.82789,118.37174 -89.33023,54.12578 -133.51903,129.93556 -132.63966,227.18753 0.8061,75.75115 28.28668,138.78795 82.2952,188.8393 24.47603,23.23022 51.81008,41.18421 82.22186,53.93522 -6.59525,19.12648 -13.557,37.44688 -20.95846,55.03446 z M 749.96366,23.751237 c 0,59.37343 -21.69138,114.810233 -64.92748,166.121963 -52.17652,60.99961 -115.28658,96.24803 -183.72426,90.68597 -0.87204,-7.12298 -1.37769,-14.61967 -1.37769,-22.49743 0,-56.99843 24.81315,-117.99801 68.87738,-167.873453 21.99909,-25.25281 49.978,-46.25018 83.90738,-63.00018 C 686.57507,10.688027 718.59913,1.5631274 748.71783,5.2734376e-4 749.59727,7.9378274 749.96366,15.875627 749.96366,23.750467 Z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0" />
|
||||
<svg width="13" height="16" viewBox="0 0 13 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.841 2.564c-.567.672-1.474 1.202-2.382 1.126-.113-.908.331-1.873.851-2.47C7.877.53 8.87.039 9.673 0c.095.946-.274 1.873-.832 2.564zm.823 1.306c-1.314-.076-2.439.747-3.063.747-.633 0-1.588-.71-2.627-.69-1.352.018-2.609.785-3.299 2.005-1.418 2.441-.369 6.055 1.002 8.042.67.984 1.474 2.063 2.533 2.025 1.002-.038 1.399-.653 2.609-.653 1.219 0 1.569.653 2.627.634 1.097-.019 1.787-.984 2.458-1.968.765-1.116 1.077-2.204 1.096-2.261-.019-.019-2.117-.823-2.136-3.245-.019-2.025 1.654-2.99 1.73-3.047-.946-1.4-2.42-1.551-2.93-1.59z" fill="#1A181D" fill-rule="nonzero"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 670 B |
|
|
@ -1,3 +1,18 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="10" viewBox="0 0 13 10">
|
||||
<path fill="#24CC8F" fill-rule="evenodd" d="M4.662 9.832c-.312 0-.61-.123-.831-.344L0 5.657l1.662-1.662 2.934 2.934L10.534 0l1.785 1.529-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<path id="vm46q29nca" d="M6.662 12.832c-.312 0-.61-.123-.831-.344L2 8.657l1.662-1.662 2.934 2.934L12.534 3l1.785 1.529-6.764 7.893c-.214.248-.521.396-.848.409l-.045.001"/>
|
||||
</defs>
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<g transform="translate(-306 -8) translate(306 8)">
|
||||
<mask id="c8uzbxs4ob" fill="#fff">
|
||||
<use xlink:href="#vm46q29nca"/>
|
||||
</mask>
|
||||
<use fill="#878190" xlink:href="#vm46q29nca"/>
|
||||
<g fill="#20B780" mask="url(#c8uzbxs4ob)">
|
||||
<path d="M0 0H16V16H0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 808 B |
|
|
@ -1 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="17"><defs><path id="a" d="M10 13v1H6v-1h4zm0-2v1H6v-1h4zM8 2l5 6h-3v2H6V8H3l5-6z"/></defs><g transform="rotate(-90 8 8)" fill="none" fill-rule="evenodd"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><use fill="#BDA8FF" xlink:href="#a"/><g fill="#878190" mask="url(#b)"><path d="M0 0h16v16H0z"/></g></g></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" >
|
||||
<path d="M10 13v1H6v-1h4zm0-2v1H6v-1h4zM8 2l5 6h-3v2H6V8H3l5-6z" id="myc95n2o6a"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 419 B After Width: | Height: | Size: 183 B |
5
website/client/src/assets/svg/lock-small.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd" opacity=".75" transform="translate(3 2)">
|
||||
<path fill="#878190" d="M4 9h2V7H4v2zm4 1H2V6h6v4zM5 2c1.103 0 2 .897 2 2H3c0-1.103.897-2 2-2zm4 2.277V4c0-2.209-1.791-4-4-4S1 1.791 1 4v.277C.405 4.624 0 5.262 0 6v4c0 1.105.895 2 2 2h6c1.105 0 2-.895 2-2V6c0-.738-.405-1.376-1-1.723z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 470 B |
|
|
@ -731,6 +731,8 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted () {
|
||||
this.forgotPassword = this.$route.path.startsWith('/forgot-password');
|
||||
|
||||
hello.init({
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
});
|
||||
|
|
|
|||
|
|
@ -245,12 +245,13 @@ import notifications from '@/mixins/notifications';
|
|||
import closeX from '../ui/closeX';
|
||||
|
||||
import copyIcon from '@/assets/svg/copy.svg';
|
||||
import copyToClipboard from '@/mixins/copyToClipboard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
closeX,
|
||||
},
|
||||
mixins: [notifications],
|
||||
mixins: [notifications, copyToClipboard],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
|
|
@ -287,17 +288,10 @@ export default {
|
|||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||
},
|
||||
copyUsername () {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(this.user.auth.local.username);
|
||||
} else {
|
||||
const copyText = document.createElement('textarea');
|
||||
copyText.value = this.user.auth.local.username;
|
||||
document.body.appendChild(copyText);
|
||||
copyText.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(copyText);
|
||||
}
|
||||
this.text(this.$t('usernameCopied'));
|
||||
this.mixinCopyToClipboard(
|
||||
this.user.auth.local.username,
|
||||
this.$t('usernameCopied'),
|
||||
);
|
||||
},
|
||||
seekParty () {
|
||||
this.$store.dispatch('user:set', {
|
||||
|
|
|
|||
|
|
@ -86,11 +86,6 @@
|
|||
color: $gray-50;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
color: $red-50;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
border-radius: 2px;
|
||||
border: solid 1px $gray-400;
|
||||
|
|
|
|||
|
|
@ -117,14 +117,14 @@ import * as quests from '@/../../common/script/content/quests';
|
|||
import { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
|
||||
import notificationsIcon from '@/assets/svg/notifications.svg';
|
||||
import MenuDropdown from '../ui/customMenuDropdown';
|
||||
import MessageCount from './messageCount';
|
||||
import MessageCount from './messageCount.functional.vue';
|
||||
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
|
||||
import successImage from '@/assets/svg/success.svg';
|
||||
import starBadge from '@/assets/svg/star-badge.svg';
|
||||
|
||||
// Notifications
|
||||
import CARD_RECEIVED from './notifications/cardReceived';
|
||||
import CHALLENGE_INVITATION from './notifications/challengeInvitation';
|
||||
import CHALLENGE_INVITATION from './notifications/challengeInvitation.functional.vue';
|
||||
import GIFT_ONE_GET_ONE from './notifications/g1g1';
|
||||
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||
import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
>{{ $t('achievements') }}</a>
|
||||
<router-link
|
||||
class="topbar-dropdown-item dropdown-item"
|
||||
:to="{name: 'site'}"
|
||||
:to="{name: 'general'}"
|
||||
>
|
||||
{{ $t('settings') }}
|
||||
</router-link>
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
import { mapState } from '@/libs/store';
|
||||
import userIcon from '@/assets/svg/user.svg';
|
||||
import MenuDropdown from '../ui/customMenuDropdown';
|
||||
import MessageCount from './messageCount';
|
||||
import MessageCount from './messageCount.functional.vue';
|
||||
import { EVENTS } from '@/libs/events';
|
||||
import { PAGES } from '@/libs/consts';
|
||||
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@
|
|||
<div v-if="currentDraggingEgg != null">
|
||||
<div
|
||||
class="potion-icon"
|
||||
:class="'Pet_Egg_'+currentDraggingEgg.key"
|
||||
:class="`Pet_Egg_${currentDraggingEgg.key}`"
|
||||
></div>
|
||||
<div class="popover">
|
||||
<div class="popover-content">
|
||||
|
|
@ -248,7 +248,7 @@
|
|||
<div v-if="currentDraggingEgg != null">
|
||||
<div
|
||||
class="potion-icon"
|
||||
:class="'Pet_Egg_'+currentDraggingEgg.key"
|
||||
:class="`Pet_Egg_${currentDraggingEgg.key}`"
|
||||
></div>
|
||||
<div class="popover">
|
||||
<div
|
||||
|
|
@ -266,7 +266,7 @@
|
|||
<div v-if="currentDraggingPotion != null">
|
||||
<div
|
||||
class="potion-icon"
|
||||
:class="'Pet_HatchingPotion_'+currentDraggingPotion.key"
|
||||
:class="`Pet_HatchingPotion_${currentDraggingPotion.key}`"
|
||||
></div>
|
||||
<div class="popover">
|
||||
<div
|
||||
|
|
@ -285,7 +285,7 @@
|
|||
<div v-if="currentDraggingPotion != null">
|
||||
<div
|
||||
class="potion-icon"
|
||||
:class="'Pet_HatchingPotion_'+currentDraggingPotion.key"
|
||||
:class="`Pet_HatchingPotion_${currentDraggingPotion.key}`"
|
||||
></div>
|
||||
<div class="popover">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<span
|
||||
v-drag.food="item.key"
|
||||
class="item-content"
|
||||
:class="'Pet_Food_'+item.key"
|
||||
:class="`Pet_Food_${item.key}`"
|
||||
@itemDragEnd="dragend($event)"
|
||||
@itemDragStart="dragstart($event)"
|
||||
></span>
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
>
|
||||
<div class="potionEggGroup">
|
||||
<div class="potionEggBackground">
|
||||
<div :class="'Pet_HatchingPotion_'+hatchablePet.potionKey"></div>
|
||||
<div :class="`Pet_HatchingPotion_${hatchablePet.potionKey}`"></div>
|
||||
</div>
|
||||
<div class="potionEggBackground">
|
||||
<div :class="'Pet_Egg_'+hatchablePet.eggKey"></div>
|
||||
<div :class="`Pet_Egg_${hatchablePet.eggKey}`"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="title">
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@
|
|||
<div v-if="currentDraggingFood != null">
|
||||
<div
|
||||
class="food-icon"
|
||||
:class="'Pet_Food_'+currentDraggingFood.key"
|
||||
:class="`Pet_Food_${currentDraggingFood.key}`"
|
||||
></div>
|
||||
<div class="popover">
|
||||
<div
|
||||
|
|
@ -287,7 +287,7 @@
|
|||
<div v-if="currentDraggingFood != null">
|
||||
<div
|
||||
class="food-icon"
|
||||
:class="'Pet_Food_'+currentDraggingFood.key"
|
||||
:class="`Pet_Food_${currentDraggingFood.key}`"
|
||||
></div>
|
||||
<div class="popover">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -22,18 +22,19 @@
|
|||
v-if="currentEvent && currentEvent.promo === 'g1g1'"
|
||||
class="g1g1-margin d-flex flex-column align-items-center"
|
||||
>
|
||||
<div
|
||||
class="svg-big-gift"
|
||||
v-once
|
||||
v-html="icons.bigGift"
|
||||
></div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-big-gift"
|
||||
v-html="icons.bigGift"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="d-flex flex-column align-items-center">
|
||||
v-else
|
||||
class="d-flex flex-column align-items-center"
|
||||
>
|
||||
<div
|
||||
class="svg-big-gift"
|
||||
v-once
|
||||
class="svg-big-gift"
|
||||
v-html="icons.bigGift"
|
||||
></div>
|
||||
</div>
|
||||
|
|
@ -49,9 +50,10 @@
|
|||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="modal-close"
|
||||
@click="close()">
|
||||
v-else
|
||||
class="modal-close"
|
||||
@click="close()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon"
|
||||
v-html="icons.close"
|
||||
|
|
@ -65,26 +67,15 @@
|
|||
name="selectUser"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="input-group">
|
||||
<input
|
||||
id="selectUser"
|
||||
v-model="userSearchTerm"
|
||||
class="form-control"
|
||||
type="text"
|
||||
ref="textBox"
|
||||
:placeholder="$t('usernameOrUserId')"
|
||||
:class="{
|
||||
'input-valid': foundUser._id,
|
||||
'is-invalid input-invalid': userNotFound,
|
||||
}"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="userSearchTerm.length > 0 && userNotFound"
|
||||
class="input-error text-center mt-2"
|
||||
>
|
||||
{{ $t('userWithUsernameOrUserIdNotFound') }}
|
||||
</div>
|
||||
<validated-text-input
|
||||
id="selectUser"
|
||||
v-model="userSearchTerm"
|
||||
:is-valid="foundUser._id"
|
||||
|
||||
:placeholder="$t('usernameOrUserId')"
|
||||
:invalid-issues="userInputInvalidIssues"
|
||||
/>
|
||||
|
||||
<div class="d-flex flex-column justify-content-center align-items-middle mt-3">
|
||||
<button
|
||||
class="btn btn-primary mx-auto mt-2"
|
||||
|
|
@ -104,16 +95,12 @@
|
|||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-if="currentEvent && currentEvent.promo ==='g1g1'"
|
||||
class="g1g1-cancel d-flex justify-content-center"
|
||||
v-html="$t('cancel')"
|
||||
@click="close()"
|
||||
v-if="currentEvent && currentEvent.promo ==='g1g1'"
|
||||
class="g1g1-cancel d-flex justify-content-center"
|
||||
@click="close()"
|
||||
v-html="$t('cancel')"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</div>
|
||||
<div
|
||||
v-else>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -121,182 +108,179 @@
|
|||
slot="modal-footer"
|
||||
class="g1g1-fine-print text-center pt-3"
|
||||
>
|
||||
<strong>
|
||||
{{ $t ('howItWorks') }}
|
||||
<strong v-once>
|
||||
{{ $t('howItWorks') }}
|
||||
</strong>
|
||||
<p
|
||||
v-once
|
||||
class="mx-5 mt-1"
|
||||
>
|
||||
{{ $t ('g1g1HowItWorks') }}
|
||||
{{ $t('g1g1HowItWorks') }}
|
||||
</p>
|
||||
<strong>
|
||||
{{ $t ('limitations') }}
|
||||
<strong v-once>
|
||||
{{ $t('limitations') }}
|
||||
</strong>
|
||||
<p
|
||||
v-once
|
||||
class="mx-5 mt-1"
|
||||
>
|
||||
{{ $t ('g1g1Limitations') }}
|
||||
{{ $t('g1g1Limitations') }}
|
||||
</p>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
@import '~@/assets/scss/mixins.scss';
|
||||
|
||||
#select-user-modal {
|
||||
.modal-content {
|
||||
width:448px;
|
||||
#select-user-modal {
|
||||
.modal-content {
|
||||
width: 448px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-top: 0rem;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 448px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 0rem;
|
||||
|
||||
> * {
|
||||
margin: 0rem 0.25rem 0.25rem 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-top: 0rem;
|
||||
}
|
||||
body.modal-open .modal {
|
||||
display: flex !important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 448px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 0rem;
|
||||
|
||||
> * {
|
||||
margin: 0rem 0.25rem 0.25rem 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
body.modal-open .modal {
|
||||
display: flex !important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body.modal-open .modal .modal-dialog {
|
||||
margin: auto;
|
||||
}
|
||||
body.modal-open .modal .modal-dialog {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
a:not([href]) {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
}
|
||||
a:not([href]) {
|
||||
|
||||
#selectUser {
|
||||
width: 22rem;
|
||||
border: 0px;
|
||||
color: $gray-50;
|
||||
}
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.71;
|
||||
}
|
||||
|
||||
.g1g1 {
|
||||
background-image: url('~@/assets/images/g1g1-send.png');
|
||||
background-size: 446px 152px;
|
||||
width: 446px;
|
||||
height: 152px;
|
||||
margin: -16px 0px 0px -16px;
|
||||
border-radius: 4.8px 4.8px 0px 0px;
|
||||
padding: 24px;
|
||||
#selectUser {
|
||||
width: 22rem;
|
||||
border: 0px;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.g1g1 {
|
||||
background-image: url('~@/assets/images/g1g1-send.png');
|
||||
background-size: 446px 152px;
|
||||
width: 446px;
|
||||
height: 152px;
|
||||
margin: -16px 0px 0px -16px;
|
||||
border-radius: 4.8px 4.8px 0px 0px;
|
||||
padding: 24px;
|
||||
color: $white;
|
||||
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.4;
|
||||
color: $white;
|
||||
|
||||
h1 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.4;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
margin-left: 4rem;
|
||||
margin-right: 4rem;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
.g1g1-margin {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.g1g1-cancel {
|
||||
margin-top: 16px;
|
||||
color: $blue-10;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.g1g1-fine-print {
|
||||
color: $gray-100;
|
||||
background-color: $gray-700;
|
||||
p {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
margin-left: 4rem;
|
||||
margin-right: 4rem;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
.g1g1-modal-close {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
.g1g1-margin {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.g1g1-svg-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
.g1g1-cancel {
|
||||
margin-top: 16px;
|
||||
color: $blue-10;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
& ::v-deep svg path {
|
||||
fill: #FFFFFF;
|
||||
}
|
||||
.g1g1-fine-print {
|
||||
color: $gray-100;
|
||||
background-color: $gray-700;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.g1g1-modal-close {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
.g1g1-svg-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
& ::v-deep svg path {
|
||||
fill: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.g1g1-modal-dialog {
|
||||
margin-top: 10vh;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
color: $red-50;
|
||||
font-size: 90%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
border-radius: 2px;
|
||||
border: solid 1px $gray-400;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group:focus-within {
|
||||
border-color: $purple-500;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
color: $purple-300;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.svg-big-gift {
|
||||
width: 176px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
.svg-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.g1g1-modal-dialog {
|
||||
margin-top: 10vh;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
border-radius: 2px;
|
||||
border: solid 1px $gray-400;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group:focus-within {
|
||||
border-color: $purple-500;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
color: $purple-300;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.svg-big-gift {
|
||||
width: 176px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 4px;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
.svg-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
|
@ -308,8 +292,10 @@ import isUUID from 'validator/lib/isUUID';
|
|||
import { mapState } from '@/libs/store';
|
||||
import closeIcon from '@/assets/svg/close.svg';
|
||||
import bigGiftIcon from '@/assets/svg/big-gift.svg';
|
||||
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
|
||||
|
||||
export default {
|
||||
components: { ValidatedTextInput },
|
||||
data () {
|
||||
return {
|
||||
userNotFound: false,
|
||||
|
|
@ -332,6 +318,12 @@ export default {
|
|||
if (this.userSearchTerm.length < 1) return true;
|
||||
return typeof this.foundUser._id === 'undefined';
|
||||
},
|
||||
|
||||
userInputInvalidIssues () {
|
||||
return this.userSearchTerm.length > 0 && this.userNotFound
|
||||
? [this.$t('userWithUsernameOrUserIdNotFound')]
|
||||
: [''];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
userSearchTerm: {
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<div class="col-6">
|
||||
<h2>{{ $t('API') }}</h2>
|
||||
<p>{{ $t('APIText') }}</p>
|
||||
<div class="section">
|
||||
<h6>{{ $t('userId') }}</h6>
|
||||
<pre class="prettyprint">{{ user.id }}</pre>
|
||||
<h6>{{ $t('APIToken') }}</h6>
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
@click="showApiToken = !showApiToken"
|
||||
>
|
||||
{{ $t(`${showApiToken ? 'hide' : 'show'}APIToken`) }}
|
||||
</button>
|
||||
<pre
|
||||
v-if="showApiToken"
|
||||
class="prettyprint ml-4 mb-0"
|
||||
>{{ apiToken }}</pre>
|
||||
</div>
|
||||
<p v-html="$t('APITokenWarning', { hrefTechAssistanceEmail })"></p>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h3>{{ $t('thirdPartyApps') }}</h3>
|
||||
<p v-html="$t('thirdPartyTools')"></p>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h2>{{ $t('webhooks') }}</h2>
|
||||
<p v-html="$t('webhooksInfo')"></p>
|
||||
<table class="table table-striped">
|
||||
<thead v-if="user.webhooks.length">
|
||||
<tr>
|
||||
<th>{{ $t('enabled') }}</th>
|
||||
<th>{{ $t('webhookURL') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(webhook, index) in user.webhooks"
|
||||
:key="webhook.id"
|
||||
>
|
||||
<td>
|
||||
<input
|
||||
v-model="webhook.enabled"
|
||||
type="checkbox"
|
||||
@change="saveWebhook(webhook, index)"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
v-model="webhook.url"
|
||||
class="form-control"
|
||||
type="url"
|
||||
>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="btn btn-danger checklist-icons mr-2"
|
||||
@click="deleteWebhook(webhook, index)"
|
||||
>
|
||||
<span
|
||||
class="glyphicon glyphicon-trash"
|
||||
:tooltip="$t('delete')"
|
||||
> {{ $t('delete') }} </span>
|
||||
</div>
|
||||
<div
|
||||
class="btn btn-primary checklist-icons"
|
||||
@click="saveWebhook(webhook, index)"
|
||||
>
|
||||
{{ $t('subUpdateTitle') }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group col-sm-10">
|
||||
<input
|
||||
v-model="newWebhook.url"
|
||||
class="form-control"
|
||||
type="url"
|
||||
:placeholder="$t('webhookURL')"
|
||||
>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
type="submit"
|
||||
@click="addWebhook(newWebhook.url)"
|
||||
>
|
||||
{{ $t('add') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.section {
|
||||
margin-top: 2em;
|
||||
}
|
||||
li span
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import uuid from '@/../../common/script/libs/uuid';
|
||||
// @TODO: env.EMAILS.TECH_ASSISTANCE_EMAIL
|
||||
const TECH_ASSISTANCE_EMAIL = 'admin@habitica.com';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
newWebhook: {
|
||||
url: '',
|
||||
},
|
||||
hrefTechAssistanceEmail: `<a href="mailto:${TECH_ASSISTANCE_EMAIL}">${TECH_ASSISTANCE_EMAIL}</a>`,
|
||||
showApiToken: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data', credentials: 'credentials' }),
|
||||
apiToken () {
|
||||
return this.credentials.API_TOKEN;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('API'),
|
||||
});
|
||||
window.addEventListener('message', this.receiveMessage, false);
|
||||
},
|
||||
destroy () {
|
||||
window.removeEventListener('message', this.receiveMessage);
|
||||
},
|
||||
methods: {
|
||||
receiveMessage (eventFrom) {
|
||||
if (eventFrom.origin !== 'https://www.spritely.app') return;
|
||||
|
||||
const creds = {
|
||||
userId: this.user._id,
|
||||
apiToken: this.credentials.API_TOKEN,
|
||||
};
|
||||
eventFrom.source.postMessage(creds, eventFrom.origin);
|
||||
},
|
||||
async addWebhook (url) {
|
||||
const webhookInfo = {
|
||||
id: uuid(),
|
||||
type: 'taskActivity',
|
||||
options: {
|
||||
created: false,
|
||||
updated: false,
|
||||
deleted: false,
|
||||
scored: true,
|
||||
},
|
||||
url,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
const webhook = await this.$store.dispatch('user:addWebhook', { webhookInfo });
|
||||
this.user.webhooks.push(webhook);
|
||||
|
||||
this.newWebhook.url = '';
|
||||
},
|
||||
async saveWebhook (webhook, index) {
|
||||
delete webhook._editing;
|
||||
const updatedWebhook = await this.$store.dispatch('user:updateWebhook', { webhook });
|
||||
this.user.webhooks[index] = updatedWebhook;
|
||||
},
|
||||
async deleteWebhook (webhook, index) {
|
||||
delete webhook._editing;
|
||||
await this.$store.dispatch('user:deleteWebhook', { webhook });
|
||||
this.user.webhooks.splice(index, 1);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2>{{ $t('dataExport') }}</h2>
|
||||
<small>{{ $t('saveData') }}</small>
|
||||
<h4>{{ $t('habitHistory') }}</h4>
|
||||
{{ $t('exportHistory') }}
|
||||
<a href="/export/history.csv">{{ $t('csv') }}</a>
|
||||
<h4>{{ $t('userData') }}</h4>
|
||||
{{ $t('exportUserData') }}
|
||||
<a href="/export/userdata.xml">{{ $t('xml') }}</a>
|
||||
<a href="/export/userdata.json">{{ $t('json') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('dataExport'),
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<h5>{{ $t('dayStartAdjustment') }}</h5>
|
||||
<div class="mb-4">
|
||||
{{ $t('customDayStartInfo1') }}
|
||||
</div>
|
||||
<h3 v-once>{{ $t('adjustment') }}</h3>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="">
|
||||
<select
|
||||
v-model="newDayStart"
|
||||
class="form-control"
|
||||
>
|
||||
<option
|
||||
v-for="option in dayStartOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary full-width mt-3"
|
||||
:disabled="newDayStart === user.preferences.dayStart"
|
||||
@click="openDayStartModal()"
|
||||
>
|
||||
{{ $t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<small>
|
||||
<p v-html="$t('timezoneUTC', {utc: timezoneOffsetToUtc})"></p>
|
||||
<p v-html="$t('timezoneInfo')"></p>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import getUtcOffset from '../../../../common/script/fns/getUtcOffset';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
name: 'dayStartAdjustment',
|
||||
data () {
|
||||
const dayStartOptions = [];
|
||||
for (let number = 0; number <= 12; number += 1) {
|
||||
const meridian = number < 12 ? 'AM' : 'PM';
|
||||
const hour = number % 12;
|
||||
const timeWithMeridian = `(${hour || 12}:00 ${meridian})`;
|
||||
const option = {
|
||||
value: number,
|
||||
name: `+${number} hours ${timeWithMeridian}`,
|
||||
};
|
||||
|
||||
if (number === 0) {
|
||||
option.name = `Default ${timeWithMeridian}`;
|
||||
}
|
||||
|
||||
dayStartOptions.push(option);
|
||||
}
|
||||
|
||||
return {
|
||||
newDayStart: 0,
|
||||
dayStartOptions,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.newDayStart = this.user.preferences.dayStart;
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
timezoneOffsetToUtc () {
|
||||
const offsetString = moment().utcOffset(getUtcOffset(this.user)).format('Z');
|
||||
return `UTC${offsetString}`;
|
||||
},
|
||||
dayStart () {
|
||||
return this.user.preferences.dayStart;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async saveDayStart () {
|
||||
this.user.preferences.dayStart = this.newDayStart;
|
||||
await axios.post('/api/v4/user/custom-day-start', {
|
||||
dayStart: this.newDayStart,
|
||||
});
|
||||
// @TODO
|
||||
// Notification.text(response.data.data.message);
|
||||
},
|
||||
openDayStartModal () {
|
||||
const nextCron = this.calculateNextCron();
|
||||
// @TODO: Add generic modal
|
||||
if (!window.confirm(this.$t('sureChangeCustomDayStartTime', { time: nextCron }))) return; // eslint-disable-line no-alert
|
||||
this.saveDayStart();
|
||||
// $rootScope.openModal('change-day-start', { scope: $scope });
|
||||
},
|
||||
calculateNextCron () {
|
||||
let nextCron = moment()
|
||||
.hours(this.newDayStart)
|
||||
.minutes(0)
|
||||
.seconds(0)
|
||||
.milliseconds(0);
|
||||
|
||||
const currentHour = moment().format('H');
|
||||
if (currentHour >= this.newDayStart) {
|
||||
nextCron = nextCron.add(1, 'day');
|
||||
}
|
||||
|
||||
return nextCron.format(`${this.user.preferences.dateFormat.toUpperCase()} @ h:mm a`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
<template>
|
||||
<b-modal
|
||||
id="delete"
|
||||
:title="$t('deleteAccount')"
|
||||
:hide-footer="true"
|
||||
size="md"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<br>
|
||||
<strong v-if="user.auth.local.has_password">{{ $t('deleteLocalAccountText') }}</strong>
|
||||
<strong
|
||||
v-if="!user.auth.local.has_password"
|
||||
>{{ $t('deleteSocialAccountText', {magicWord: 'DELETE'}) }}</strong>
|
||||
<div class="row mt-3">
|
||||
<div class="col-6">
|
||||
<input
|
||||
v-model="password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div
|
||||
id="feedback"
|
||||
class="col-12 form-group"
|
||||
>
|
||||
<label for="feedbackTextArea">{{ $t('feedback') }}</label>
|
||||
<textarea
|
||||
id="feedbackTextArea"
|
||||
v-model="feedback"
|
||||
class="form-control"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('neverMind') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
:disabled="!password"
|
||||
@click="deleteAccount()"
|
||||
>
|
||||
{{ $t('deleteDo') }}
|
||||
</button>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
password: '',
|
||||
feedback: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'delete');
|
||||
},
|
||||
async deleteAccount () {
|
||||
await axios.delete('/api/v4/user', {
|
||||
data: {
|
||||
password: this.password,
|
||||
feedback: this.feedback,
|
||||
},
|
||||
});
|
||||
localStorage.clear();
|
||||
window.location.href = '/static/home';
|
||||
this.$root.$emit('bv::hide::modal', 'delete');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<secondary-menu class="col-12">
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'site'}"
|
||||
exact="exact"
|
||||
:class="{'active': $route.name === 'site'}"
|
||||
>
|
||||
{{ $t('site') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'api'}"
|
||||
:class="{'active': $route.name === 'api'}"
|
||||
>
|
||||
{{ $t('API') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'dataExport'}"
|
||||
:class="{'active': $route.name === 'dataExport'}"
|
||||
>
|
||||
{{ $t('dataExport') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'promoCode'}"
|
||||
:class="{'active': $route.name === 'promoCode'}"
|
||||
>
|
||||
{{ $t('promoCode') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'subscription'}"
|
||||
:class="{'active': $route.name === 'subscription'}"
|
||||
>
|
||||
{{ $t('subscription') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="hasPermission(user, 'userSupport')"
|
||||
class="nav-link"
|
||||
:to="{name: 'transactions'}"
|
||||
:class="{'active': $route.name === 'transactions'}"
|
||||
>
|
||||
{{ $t('transactions') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="nav-link"
|
||||
:to="{name: 'notifications'}"
|
||||
:class="{'active': $route.name === 'notifications'}"
|
||||
>
|
||||
{{ $t('notifications') }}
|
||||
</router-link>
|
||||
</secondary-menu>
|
||||
<div
|
||||
v-if="$route.name === 'subscription' && promo === 'g1g1'"
|
||||
class="g1g1-banner d-flex justify-content-center"
|
||||
@click="showSelectUser"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts left-gift"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-center text-center">
|
||||
<strong
|
||||
class="mt-auto mb-1"
|
||||
> {{ $t('g1g1Event') }} </strong>
|
||||
<p
|
||||
class="mb-auto"
|
||||
>
|
||||
{{ $t('g1g1Details') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts right-gift"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
strong {
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.g1g1-banner {
|
||||
color: $white;
|
||||
width: 100%;
|
||||
height: 5.75rem;
|
||||
background-image: linear-gradient(90deg, $teal-50 0%, $purple-400 100%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.left-gift {
|
||||
margin: auto 3rem auto auto;
|
||||
}
|
||||
|
||||
.right-gift {
|
||||
margin: auto auto auto 3rem;
|
||||
filter: flipH;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.svg-gifts {
|
||||
width: 3.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
import SecondaryMenu from '@/components/secondaryMenu';
|
||||
import gifts from '@/assets/svg/gifts-vertical.svg';
|
||||
import { userStateMixin } from '../../mixins/userState';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SecondaryMenu,
|
||||
},
|
||||
mixins: [userStateMixin],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
gifts,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
currentEvent () {
|
||||
return find(this.currentEventList, event => Boolean(event.promo));
|
||||
},
|
||||
promo () {
|
||||
if (!this.currentEvent || !this.currentEvent.promo) return 'none';
|
||||
return this.currentEvent.promo;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showSelectUser () {
|
||||
this.$root.$emit('bv::show::modal', 'select-user-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<div class="col-12">
|
||||
<h1>{{ $t('notifications') }}</h1>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.pushNotifications.unsubscribeFromAll"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('pushNotifications', 'unsubscribeFromAll')"
|
||||
>
|
||||
<span>{{ $t('unsubscribeAllPush') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.emailNotifications.unsubscribeFromAll"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('emailNotifications', 'unsubscribeFromAll')"
|
||||
>
|
||||
<span>{{ $t('unsubscribeAllEmails') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<small>{{ $t('unsubscribeAllEmailsText') }}</small>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td></td>
|
||||
<th>
|
||||
<span>{{ $t('email') }}</span>
|
||||
</th>
|
||||
<th>
|
||||
<span>{{ $t('push') }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="notification in notificationsIds"
|
||||
:key="notification"
|
||||
>
|
||||
<td>
|
||||
<span>{{ $t(notification) }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
v-model="user.preferences.emailNotifications[notification]"
|
||||
type="checkbox"
|
||||
@change="set('emailNotifications', notification)"
|
||||
>
|
||||
</td>
|
||||
<td v-if="onlyEmailsIds.indexOf(notification) === -1">
|
||||
<input
|
||||
v-model="user.preferences.pushNotifications[notification]"
|
||||
type="checkbox"
|
||||
@change="set('pushNotifications', notification)"
|
||||
>
|
||||
</td><td v-else>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import notificationsMixin from '@/mixins/notifications';
|
||||
|
||||
export default {
|
||||
mixins: [notificationsMixin],
|
||||
data () {
|
||||
return {
|
||||
notificationsIds: Object.freeze([
|
||||
'majorUpdates',
|
||||
'onboarding',
|
||||
'newPM',
|
||||
'wonChallenge',
|
||||
'giftedGems',
|
||||
'giftedSubscription',
|
||||
'invitedParty',
|
||||
'invitedGuild',
|
||||
'kickedGroup',
|
||||
'questStarted',
|
||||
'invitedQuest',
|
||||
'importantAnnouncements',
|
||||
'weeklyRecaps',
|
||||
'subscriptionReminders',
|
||||
]),
|
||||
// list of email-only notifications
|
||||
onlyEmailsIds: Object.freeze([
|
||||
'kickedGroup',
|
||||
'importantAnnouncements',
|
||||
'weeklyRecaps',
|
||||
'onboarding',
|
||||
'subscriptionReminders',
|
||||
]),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
async mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('notifications'),
|
||||
});
|
||||
// If ?unsubFrom param is passed with valid email type,
|
||||
// automatically unsubscribe users from that email and
|
||||
// show an alert
|
||||
|
||||
// A simple object to map the key stored in the db (user.preferences.emailNotification[key])
|
||||
// to its string id but ONLY when the preferences' key and the string key don't match
|
||||
const MAP_PREF_TO_EMAIL_STRING = {
|
||||
importantAnnouncements: 'inactivityEmails',
|
||||
};
|
||||
|
||||
const { unsubFrom } = this.$route.query;
|
||||
|
||||
if (unsubFrom) {
|
||||
await this.$store.dispatch('user:set', {
|
||||
[`preferences.emailNotifications.${unsubFrom}`]: false,
|
||||
});
|
||||
|
||||
const emailTypeString = this.$t(MAP_PREF_TO_EMAIL_STRING[unsubFrom] || unsubFrom);
|
||||
this.text(this.$t('correctlyUnsubscribedEmailType', { emailType: emailTypeString }));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
set (preferenceType, notification) {
|
||||
const settings = {};
|
||||
settings[`preferences.${preferenceType}.${notification}`] = this.user.preferences[preferenceType][notification];
|
||||
this.$store.dispatch('user:set', settings);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<div class="col-md-6">
|
||||
<h2>{{ $t('promoCode') }}</h2>
|
||||
<div
|
||||
class="form-inline"
|
||||
role="form"
|
||||
>
|
||||
<input
|
||||
v-model="couponCode"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('promoPlaceholder')"
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="enterCoupon()"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<small>{{ $t('couponText') }}</small>
|
||||
</div>
|
||||
<div v-if="user.permissions.coupons">
|
||||
<hr>
|
||||
<h4>{{ $t('generateCodes') }}</h4>
|
||||
<div
|
||||
class="form"
|
||||
role="form"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="codes.event"
|
||||
class="form-control"
|
||||
type="text"
|
||||
placeholder="Event code (eg, 'wondercon')"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="codes.count"
|
||||
class="form-control"
|
||||
type="number"
|
||||
placeholder="Number of codes to generate (eg, 250)"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="generateCodes(codes)"
|
||||
>
|
||||
{{ $t('generate') }}
|
||||
</button>
|
||||
<a
|
||||
class="btn btn-secondary"
|
||||
:href="getCodesUrl"
|
||||
>{{ $t('getCodes') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
data () {
|
||||
return {
|
||||
codes: {
|
||||
event: '',
|
||||
count: '',
|
||||
},
|
||||
couponCode: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data', credentials: 'credentials' }),
|
||||
getCodesUrl () {
|
||||
if (!this.user) return '';
|
||||
return '/api/v4/coupons';
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('promoCode'),
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
generateCodes () {
|
||||
// $http.post(ApiUrl.get() + '/api/v2/coupons/generate/
|
||||
// '+codes.event+'?count='+(codes.count || 1))
|
||||
// .success(function(res,code){
|
||||
// $scope._codes = {};
|
||||
// if (code!==200) return;
|
||||
// window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+
|
||||
// '&apiToken='+User.settings.auth.apiToken;
|
||||
// })
|
||||
},
|
||||
async enterCoupon () {
|
||||
const code = await axios.post(`/api/v4/coupons/enter/${this.couponCode}`);
|
||||
if (!code) return;
|
||||
|
||||
this.$store.state.user.data = code.data.data;
|
||||
|
||||
this.text(this.$t('promoCodeApplied'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<template>
|
||||
<b-modal
|
||||
id="reset"
|
||||
:title="$t('resetAccount')"
|
||||
:hide-footer="true"
|
||||
size="md"
|
||||
>
|
||||
<p>{{ $t('resetText1') }}</p>
|
||||
<p>{{ $t('resetText2') }}</p>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('neverMind') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="reset()"
|
||||
>
|
||||
{{ $t('resetDo') }}
|
||||
</button>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'reset');
|
||||
},
|
||||
async reset () {
|
||||
await axios.post('/api/v4/user/reset');
|
||||
this.$router.push('/');
|
||||
setTimeout(() => window.location.reload(true), 100);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
<template>
|
||||
<b-modal
|
||||
id="restore"
|
||||
:title="$t('fixValues')"
|
||||
:hide-footer="true"
|
||||
size="lg"
|
||||
>
|
||||
<p>{{ $t('fixValuesText1') }}</p>
|
||||
<p>{{ $t('fixValuesText2') }}</p>
|
||||
<div class="form-horizontal">
|
||||
<h3>{{ $t('stats') }}</h3>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-3">
|
||||
<label class="control-label">{{ $t('health') }}</label>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="restoreValues.stats.hp"
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="any"
|
||||
data-for="stats.hp"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-3">
|
||||
<label class="control-label">{{ $t('experience') }}</label>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="restoreValues.stats.exp"
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="any"
|
||||
data-for="stats.exp"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-3">
|
||||
<label class="control-label">{{ $t('gold') }}</label>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="restoreValues.stats.gp"
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="any"
|
||||
data-for="stats.gp"
|
||||
>
|
||||
</div>
|
||||
<!--input.form-control(type='number',
|
||||
step="any", data-for='stats.gp', v-model='restoreValues.stats.gp',disabled)-->
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-3">
|
||||
<label class="control-label">{{ $t('mana') }}</label>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="restoreValues.stats.mp"
|
||||
class="form-control"
|
||||
type="number"
|
||||
step="any"
|
||||
data-for="stats.mp"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-3">
|
||||
<label class="control-label">{{ $t('level') }}</label>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="restoreValues.stats.lvl"
|
||||
class="form-control"
|
||||
type="number"
|
||||
data-for="stats.lvl"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<h3>{{ $t('achievements') }}</h3>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-3">
|
||||
<label class="control-label">{{ $t('fix21Streaks') }}</label>
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
v-model="restoreValues.achievements.streak"
|
||||
class="form-control"
|
||||
type="number"
|
||||
data-for="achievements.streak"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('discardChanges') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="restore()"
|
||||
>
|
||||
{{ $t('saveAndClose') }}
|
||||
</button>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import clone from 'lodash/clone';
|
||||
import { MAX_LEVEL_HARD_CAP } from '@/../../common/script/constants';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
restoreValues: {
|
||||
stats: {
|
||||
hp: 0,
|
||||
mp: 0,
|
||||
gp: 0,
|
||||
exp: 0,
|
||||
lvl: 0,
|
||||
},
|
||||
achievements: {
|
||||
streak: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
mounted () {
|
||||
this.restoreValues.stats = clone(this.user.stats);
|
||||
this.restoreValues.achievements.streak = clone(this.user.achievements.streak);
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.validateInputs();
|
||||
this.$root.$emit('bv::hide::modal', 'restore');
|
||||
},
|
||||
restore () {
|
||||
if (!this.validateInputs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.restoreValues.stats.lvl > MAX_LEVEL_HARD_CAP) {
|
||||
this.restoreValues.stats.lvl = MAX_LEVEL_HARD_CAP;
|
||||
}
|
||||
|
||||
const userChangedLevel = this.restoreValues.stats.lvl !== this.user.stats.lvl;
|
||||
const userDidNotChangeExp = this.restoreValues.stats.exp === this.user.stats.exp;
|
||||
if (userChangedLevel && userDidNotChangeExp) this.restoreValues.stats.exp = 0;
|
||||
|
||||
this.user.stats = clone(this.restoreValues.stats);
|
||||
this.user.achievements.streak = clone(this.restoreValues.achievements.streak);
|
||||
|
||||
const settings = {
|
||||
'stats.hp': Number(this.restoreValues.stats.hp),
|
||||
'stats.exp': Number(this.restoreValues.stats.exp),
|
||||
'stats.gp': Number(this.restoreValues.stats.gp),
|
||||
'stats.lvl': Number(this.restoreValues.stats.lvl),
|
||||
'stats.mp': Number(this.restoreValues.stats.mp),
|
||||
'achievements.streak': Number(this.restoreValues.achievements.streak),
|
||||
};
|
||||
|
||||
this.$store.dispatch('user:set', settings);
|
||||
this.$root.$emit('bv::hide::modal', 'restore');
|
||||
},
|
||||
validateInputs () {
|
||||
const canRestore = ['hp', 'exp', 'gp', 'mp'];
|
||||
let valid = true;
|
||||
|
||||
for (const stat of canRestore) {
|
||||
if (this.restoreValues.stats[stat] === ''
|
||||
|| this.restoreValues.stats[stat] < 0
|
||||
) {
|
||||
this.restoreValues.stats[stat] = this.user.stats[stat];
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
const inputLevel = Number(this.restoreValues.stats.lvl);
|
||||
if (this.restoreValues.stats.lvl === ''
|
||||
|| !Number.isInteger(inputLevel)
|
||||
|| inputLevel < 1) {
|
||||
this.restoreValues.stats.lvl = this.user.stats.lvl;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
const inputStreak = Number(this.restoreValues.achievements.streak);
|
||||
if (this.restoreValues.achievements.streak === ''
|
||||
|| !Number.isInteger(inputStreak)
|
||||
|| inputStreak < 0) {
|
||||
this.restoreValues.achievements.streak = this.user.achievements.streak;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,859 +0,0 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<restore-modal />
|
||||
<reset-modal />
|
||||
<delete-modal />
|
||||
<h1 class="col-12">
|
||||
{{ $t('settings') }}
|
||||
</h1>
|
||||
<div class="col-sm-6">
|
||||
<div class="sleep">
|
||||
<h5>{{ $t('pauseDailies') }}</h5>
|
||||
<h4>{{ $t('sleepDescription') }}</h4>
|
||||
<ul>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet1') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet2') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet3') }}
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
v-if="!user.preferences.sleep"
|
||||
v-once
|
||||
class="sleep btn btn-primary btn-block pause-button"
|
||||
@click="toggleSleep()"
|
||||
>
|
||||
{{ $t('pauseDailies') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="user.preferences.sleep"
|
||||
v-once
|
||||
class="btn btn-secondary btn-block pause-button"
|
||||
@click="toggleSleep()"
|
||||
>
|
||||
{{ $t('unpauseDailies') }}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-horizontal">
|
||||
<h5>{{ $t('language') }}</h5>
|
||||
<select
|
||||
class="form-control"
|
||||
:value="user.preferences.language"
|
||||
@change="changeLanguage($event)"
|
||||
>
|
||||
<option
|
||||
v-for="lang in availableLanguages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small>
|
||||
{{ $t('americanEnglishGovern') }}
|
||||
<br>
|
||||
<strong v-html="$t('helpWithTranslation')"></strong>
|
||||
</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-horizontal">
|
||||
<h5>{{ $t('dateFormat') }}</h5>
|
||||
<select
|
||||
v-model="user.preferences.dateFormat"
|
||||
class="form-control"
|
||||
@change="set('dateFormat')"
|
||||
>
|
||||
<option
|
||||
v-for="dateFormat in availableFormats"
|
||||
:key="dateFormat"
|
||||
:value="dateFormat"
|
||||
>
|
||||
{{ dateFormat }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<h5>{{ $t('audioTheme') }}</h5>
|
||||
<select
|
||||
v-model="user.preferences.sound"
|
||||
class="form-control"
|
||||
@change="changeAudioTheme"
|
||||
>
|
||||
<option
|
||||
v-for="sound in availableAudioThemes"
|
||||
:key="sound"
|
||||
:value="sound"
|
||||
>
|
||||
{{ $t(`audioTheme_${sound}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary btn-xs"
|
||||
@click="playAudio"
|
||||
>
|
||||
{{ $t('demo') }}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div
|
||||
v-if="hasClass"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<h5>{{ $t('characterBuild') }}</h5>
|
||||
<h6 v-once>
|
||||
{{ $t('class') + ': ' }}
|
||||
<!-- @TODO: what is classText-->
|
||||
<!-- span(v-if='classText') {{ classText }} -->
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-danger btn-xs"
|
||||
@click="changeClassForUser(true)"
|
||||
>
|
||||
{{ $t('changeClass') }}
|
||||
</button>
|
||||
<small class="cost">
|
||||
3 {{ $t('gems') }}
|
||||
<!-- @TODO add icon span.Pet_Currency_Gem1x.inline-gems-->
|
||||
</small>
|
||||
</h6>
|
||||
<hr>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="checkbox"
|
||||
id="preferenceAdvancedCollapsed"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.advancedCollapsed"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('advancedCollapsed')"
|
||||
>
|
||||
<span class="hint">
|
||||
{{ $t('startAdvCollapsed') }}
|
||||
</span>
|
||||
<b-popover
|
||||
target="preferenceAdvancedCollapsed"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('startAdvCollapsedPop')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="party.memberCount === 1"
|
||||
class="checkbox"
|
||||
id="preferenceDisplayInviteAtOneMember"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.displayInviteToPartyWhenPartyIs1"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('displayInviteToPartyWhenPartyIs1')"
|
||||
>
|
||||
<span class="hint">
|
||||
{{ $t('displayInviteToPartyWhenPartyIs1') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<input
|
||||
v-model="user.preferences.suppressModals.levelUp"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('suppressModals', 'levelUp')"
|
||||
>
|
||||
<label>{{ $t('suppressLevelUpModal') }}</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<input
|
||||
v-model="user.preferences.suppressModals.hatchPet"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('suppressModals', 'hatchPet')"
|
||||
>
|
||||
<label>{{ $t('suppressHatchPetModal') }}</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<input
|
||||
v-model="user.preferences.suppressModals.raisePet"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('suppressModals', 'raisePet')"
|
||||
>
|
||||
<label>{{ $t('suppressRaisePetModal') }}</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<input
|
||||
v-model="user.preferences.suppressModals.streak"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('suppressModals', 'streak')"
|
||||
>
|
||||
<label>{{ $t('suppressStreakModal') }}</label>
|
||||
</div>
|
||||
<hr>
|
||||
<button
|
||||
id="buttonShowBailey"
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
@click="showBailey()"
|
||||
>
|
||||
{{ $t('showBailey') }}
|
||||
<b-popover
|
||||
target="buttonShowBailey"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('showBaileyPop')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
id="buttonFCV"
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
@click="openRestoreModal()"
|
||||
>
|
||||
{{ $t('fixVal') }}
|
||||
<b-popover
|
||||
target="buttonFCV"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('fixValPop')"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="user.preferences.disableClasses == true"
|
||||
id="buttonEnableClasses"
|
||||
class="btn btn-primary mb-2"
|
||||
@click="changeClassForUser(false)"
|
||||
>
|
||||
{{ $t('enableClass') }}
|
||||
<b-popover
|
||||
target="buttonEnableClasses"
|
||||
triggers="hover focus"
|
||||
placement="right"
|
||||
:prevent-overflow="false"
|
||||
:content="$t('enableClassPop')"
|
||||
/>
|
||||
</button>
|
||||
<hr>
|
||||
<day-start-adjustment />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h2>{{ $t('registration') }}</h2>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<ul class="list-inline">
|
||||
<li
|
||||
v-for="network in SOCIAL_AUTH_NETWORKS"
|
||||
:key="network.key"
|
||||
>
|
||||
<button
|
||||
v-if="!user.auth[network.key].id && network.key !== 'facebook'"
|
||||
class="btn btn-primary mb-2"
|
||||
@click="socialAuth(network.key, user)"
|
||||
>
|
||||
{{ $t('registerWithSocial', {network: network.name}) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!hasBackupAuthOption(network.key) && user.auth[network.key].id"
|
||||
class="btn btn-primary mb-2"
|
||||
disabled="disabled"
|
||||
>
|
||||
{{ $t('registeredWithSocial', {network: network.name}) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="hasBackupAuthOption(network.key) && user.auth[network.key].id"
|
||||
class="btn btn-danger"
|
||||
@click="deleteSocialAuth(network)"
|
||||
>
|
||||
{{ $t('detachSocial', {network: network.name}) }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<div v-if="!user.auth.local.has_password">
|
||||
<h5 v-if="!user.auth.local.email">
|
||||
{{ $t('addLocalAuth') }}
|
||||
</h5>
|
||||
<h5 v-if="user.auth.local.email">
|
||||
{{ $t('addPasswordAuth') }}
|
||||
</h5>
|
||||
<div
|
||||
class="form"
|
||||
name="localAuth"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div
|
||||
v-if="!user.auth.local.email"
|
||||
class="form-group"
|
||||
>
|
||||
<input
|
||||
v-model="localAuth.email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('email')"
|
||||
required="required"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="localAuth.password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('password')"
|
||||
required="required"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="localAuth.confirmPassword"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('confirmPass')"
|
||||
required="required"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="addLocalAuth()"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="usersettings">
|
||||
<h5>{{ $t('changeDisplayName') }}</h5>
|
||||
<div
|
||||
class="form"
|
||||
name="changeDisplayName"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changeDisplayname"
|
||||
v-model="temporaryDisplayName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newDisplayName')"
|
||||
:class="{'is-invalid input-invalid': displayNameInvalid}"
|
||||
>
|
||||
<div
|
||||
v-if="displayNameIssues.length > 0"
|
||||
class="mb-3"
|
||||
>
|
||||
<div
|
||||
v-for="issue in displayNameIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
:disabled="displayNameCannotSubmit"
|
||||
@click="changeDisplayName(temporaryDisplayName)"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<h5>{{ $t('changeUsername') }}</h5>
|
||||
<div
|
||||
class="form"
|
||||
name="changeUsername"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div
|
||||
v-if="verifiedUsername"
|
||||
class="iconalert iconalert-success"
|
||||
>
|
||||
{{ $t('usernameVerifiedConfirmation', {'username': user.auth.local.username}) }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="iconalert iconalert-warning"
|
||||
>
|
||||
<div class="align-middle">
|
||||
<span>{{ $t('usernameNotVerified') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changeUsername"
|
||||
v-model="usernameUpdates.username"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newUsername')"
|
||||
:class="{'is-invalid input-invalid': usernameInvalid}"
|
||||
@blur="restoreEmptyUsername()"
|
||||
>
|
||||
<div
|
||||
v-for="issue in usernameIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
<small class="form-text text-muted">{{ $t('changeUsernameDisclaimer') }}</small>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
:disabled="usernameCannotSubmit"
|
||||
@click="changeUser('username', usernameUpdates)"
|
||||
>
|
||||
{{ $t('saveAndConfirm') }}
|
||||
</button>
|
||||
</div>
|
||||
<h5 v-if="user.auth.local.has_password">
|
||||
{{ $t('changeEmail') }}
|
||||
</h5>
|
||||
<div
|
||||
v-if="user.auth.local.email"
|
||||
class="form"
|
||||
name="changeEmail"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changeEmail"
|
||||
v-model="emailUpdates.newEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newEmail')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="user.auth.local.has_password"
|
||||
class="form-group"
|
||||
>
|
||||
<input
|
||||
v-model="emailUpdates.password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('password')"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="changeUser('email', emailUpdates)"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<h5 v-if="user.auth.local.has_password">
|
||||
{{ $t('changePass') }}
|
||||
</h5>
|
||||
<div
|
||||
v-if="user.auth.local.has_password"
|
||||
class="form"
|
||||
name="changePassword"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changePassword"
|
||||
v-model="passwordUpdates.password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('oldPass')"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="passwordUpdates.newPassword"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('newPass')"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="passwordUpdates.confirmPassword"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('confirmPass')"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="changeUser('password', passwordUpdates)"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div>
|
||||
<h5>{{ $t('dangerZone') }}</h5>
|
||||
<div>
|
||||
<button
|
||||
v-b-popover.hover.auto="$t('resetAccPop')"
|
||||
class="btn btn-danger mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
@click="openResetModal()"
|
||||
>
|
||||
{{ $t('resetAccount') }}
|
||||
</button>
|
||||
<button
|
||||
v-b-popover.hover.auto="$t('deleteAccPop')"
|
||||
class="btn btn-danger mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
@click="openDeleteModal()"
|
||||
>
|
||||
{{ $t('deleteAccount') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
input {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
width: fit-content;
|
||||
}
|
||||
.usersettings h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.iconalert > div > span {
|
||||
line-height: 25px;
|
||||
}
|
||||
.iconalert > div:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
.input-error {
|
||||
color: $red-50;
|
||||
font-size: 90%;
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.sleep {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import hello from 'hellojs';
|
||||
import axios from 'axios';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
import restoreModal from './restoreModal';
|
||||
import resetModal from './resetModal';
|
||||
import deleteModal from './deleteModal';
|
||||
import dayStartAdjustment from './dayStartAdjustment';
|
||||
import { SUPPORTED_SOCIAL_NETWORKS } from '@/../../common/script/constants';
|
||||
import changeClass from '@/../../common/script/ops/changeClass';
|
||||
import notificationsMixin from '../../mixins/notifications';
|
||||
import sounds from '../../libs/sounds';
|
||||
import { buildAppleAuthUrl } from '../../libs/auth';
|
||||
|
||||
// @TODO: this needs our window.env fix
|
||||
// import { availableLanguages } from '../../../server/libs/i18n';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
restoreModal,
|
||||
resetModal,
|
||||
deleteModal,
|
||||
dayStartAdjustment,
|
||||
},
|
||||
mixins: [notificationsMixin],
|
||||
data () {
|
||||
return {
|
||||
SOCIAL_AUTH_NETWORKS: [],
|
||||
party: {},
|
||||
// Made available by the server as a script
|
||||
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
||||
temporaryDisplayName: '',
|
||||
usernameUpdates: { username: '' },
|
||||
emailUpdates: {},
|
||||
passwordUpdates: {},
|
||||
localAuth: {
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
displayNameIssues: [],
|
||||
usernameIssues: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
availableLanguages: 'i18n.availableLanguages',
|
||||
content: 'content',
|
||||
}),
|
||||
availableAudioThemes () {
|
||||
return ['off', ...this.content.audioThemes];
|
||||
},
|
||||
hasClass () {
|
||||
return this.$store.getters['members:hasClass'](this.user);
|
||||
},
|
||||
verifiedUsername () {
|
||||
return this.user.flags.verifiedUsername;
|
||||
},
|
||||
displayNameInvalid () {
|
||||
if (this.temporaryDisplayName.length <= 1) return false;
|
||||
return !this.displayNameValid;
|
||||
},
|
||||
displayNameValid () {
|
||||
if (this.temporaryDisplayName.length <= 1) return false;
|
||||
return this.displayNameIssues.length === 0;
|
||||
},
|
||||
displayNameCannotSubmit () {
|
||||
if (this.temporaryDisplayName.length <= 1) return true;
|
||||
return !this.displayNameValid;
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.usernameUpdates.username.length <= 1) return false;
|
||||
return this.usernameIssues.length === 0;
|
||||
},
|
||||
usernameInvalid () {
|
||||
if (this.usernameUpdates.username.length <= 1) return false;
|
||||
return !this.usernameValid;
|
||||
},
|
||||
usernameCannotSubmit () {
|
||||
if (this.usernameUpdates.username.length <= 1) return true;
|
||||
return !this.usernameValid;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
usernameUpdates: {
|
||||
handler () {
|
||||
this.validateUsername(this.usernameUpdates.username);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
temporaryDisplayName: {
|
||||
handler () {
|
||||
this.validateDisplayName(this.temporaryDisplayName);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.SOCIAL_AUTH_NETWORKS = SUPPORTED_SOCIAL_NETWORKS;
|
||||
// @TODO: We may need to request the party here
|
||||
this.party = this.$store.state.party;
|
||||
this.usernameUpdates.username = this.user.auth.local.username || null;
|
||||
this.temporaryDisplayName = this.user.profile.name;
|
||||
this.emailUpdates.newEmail = this.user.auth.local.email || null;
|
||||
this.localAuth.username = this.user.auth.local.username || null;
|
||||
this.soundIndex = 0;
|
||||
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
});
|
||||
|
||||
hello.init({
|
||||
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line no-process-env
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line no-process-env
|
||||
}, {
|
||||
redirect_uri: '', // eslint-disable-line
|
||||
});
|
||||
|
||||
const focusID = this.$route.query.focus;
|
||||
if (focusID !== undefined && focusID !== null) {
|
||||
this.$nextTick(() => {
|
||||
const element = document.getElementById(focusID);
|
||||
if (element !== undefined && element !== null) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSleep () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
validateDisplayName: debounce(function checkName (displayName) {
|
||||
if (displayName.length <= 1 || displayName === this.user.profile.name) {
|
||||
this.displayNameIssues = [];
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyDisplayName', {
|
||||
displayName,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.displayNameIssues = res.issues;
|
||||
} else {
|
||||
this.displayNameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
validateUsername: debounce(function checkName (username) {
|
||||
if (username.length <= 1 || username === this.user.auth.local.username) {
|
||||
this.usernameIssues = [];
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
username,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
set (preferenceType, subtype) {
|
||||
const settings = {};
|
||||
if (!subtype) {
|
||||
settings[`preferences.${preferenceType}`] = this.user.preferences[preferenceType];
|
||||
} else {
|
||||
settings[`preferences.${preferenceType}.${subtype}`] = this.user.preferences[preferenceType][subtype];
|
||||
}
|
||||
return this.$store.dispatch('user:set', settings);
|
||||
},
|
||||
hideHeader () {
|
||||
this.set('hideHeader');
|
||||
if (!this.user.preferences.hideHeader || !this.user.preferences.stickyHeader) return;
|
||||
this.user.preferences.hideHeader = false;
|
||||
this.set('stickyHeader');
|
||||
},
|
||||
toggleStickyHeader () {
|
||||
this.set('stickyHeader');
|
||||
},
|
||||
showTour () {
|
||||
// @TODO: Do we still use this?
|
||||
// User.set({'flags.showTour':true});
|
||||
// Guide.goto('intro', 0, true);
|
||||
},
|
||||
showBailey () {
|
||||
this.$root.$emit('bv::show::modal', 'new-stuff');
|
||||
},
|
||||
hasBackupAuthOption (networkKeyToCheck) {
|
||||
if (this.user.auth.local.username) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.SOCIAL_AUTH_NETWORKS.find(network => {
|
||||
if (network.key !== networkKeyToCheck) {
|
||||
if (this.user.auth[network.key]) {
|
||||
return !!this.user.auth[network.key].id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
async changeLanguage (e) {
|
||||
const newLang = e.target.value;
|
||||
this.user.preferences.language = newLang;
|
||||
await this.set('language');
|
||||
setTimeout(() => window.location.reload(true));
|
||||
},
|
||||
async changeUser (attribute, updates) {
|
||||
await axios.put(`/api/v4/user/auth/update-${attribute}`, updates);
|
||||
if (attribute === 'username') {
|
||||
this.user.auth.local.username = updates[attribute];
|
||||
this.localAuth.username = this.user.auth.local.username;
|
||||
this.user.flags.verifiedUsername = true;
|
||||
} else if (attribute === 'email') {
|
||||
this.user.auth.local.email = updates.newEmail;
|
||||
window.alert(this.$t('emailSuccess')); // eslint-disable-line no-alert
|
||||
} else if (attribute === 'password') {
|
||||
this.passwordUpdates = {};
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: this.$t('passwordSuccess'),
|
||||
type: 'success',
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
async changeDisplayName (newName) {
|
||||
await axios.put('/api/v4/user/', { 'profile.name': newName });
|
||||
window.alert(this.$t('displayNameSuccess')); // eslint-disable-line no-alert
|
||||
this.user.profile.name = newName;
|
||||
this.temporaryDisplayName = newName;
|
||||
},
|
||||
openRestoreModal () {
|
||||
this.$root.$emit('bv::show::modal', 'restore');
|
||||
},
|
||||
openResetModal () {
|
||||
this.$root.$emit('bv::show::modal', 'reset');
|
||||
},
|
||||
openDeleteModal () {
|
||||
this.$root.$emit('bv::show::modal', 'delete');
|
||||
},
|
||||
async deleteSocialAuth (network) {
|
||||
await axios.delete(`/api/v4/user/auth/social/${network.key}`);
|
||||
this.user.auth[network.key] = {};
|
||||
this.text(this.$t('detachedSocial', { network: network.name }));
|
||||
},
|
||||
async socialAuth (network) {
|
||||
if (network === 'apple') {
|
||||
window.location.href = buildAppleAuthUrl();
|
||||
} else {
|
||||
const auth = await hello(network).login({ scope: 'email' });
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
auth,
|
||||
});
|
||||
window.location.href = '/';
|
||||
}
|
||||
},
|
||||
async changeClassForUser (confirmationNeeded) {
|
||||
if (confirmationNeeded && !window.confirm(this.$t('changeClassConfirmCost'))) return; // eslint-disable-line no-alert
|
||||
try {
|
||||
changeClass(this.user);
|
||||
await axios.post('/api/v4/user/change-class');
|
||||
} catch (e) {
|
||||
window.alert(e.message); // eslint-disable-line no-alert
|
||||
}
|
||||
},
|
||||
async addLocalAuth () {
|
||||
if (this.localAuth.email === '') {
|
||||
this.localAuth.email = this.user.auth.local.email;
|
||||
}
|
||||
await axios.post('/api/v4/user/auth/local/register', this.localAuth);
|
||||
window.location.href = '/user/settings/site';
|
||||
},
|
||||
restoreEmptyUsername () {
|
||||
if (this.usernameUpdates.username.length < 1) {
|
||||
this.usernameUpdates.username = this.user.auth.local.username;
|
||||
}
|
||||
},
|
||||
changeAudioTheme () {
|
||||
this.soundIndex = 0;
|
||||
this.set('sound');
|
||||
},
|
||||
playAudio () {
|
||||
this.$root.$emit('playSound', sounds[this.soundIndex]);
|
||||
this.soundIndex = (this.soundIndex + 1) % sounds.length;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -115,8 +115,6 @@
|
|||
}
|
||||
|
||||
.input-error {
|
||||
color: $red-50;
|
||||
font-size: 90%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
:key="item.key"
|
||||
:item="item"
|
||||
:price="item.value"
|
||||
:item-content-class="'shop_'+item.key"
|
||||
:item-content-class="`shop_${item.key}`"
|
||||
:empty-item="false"
|
||||
:popover-position="'top'"
|
||||
@click="featuredItemSelected(item)"
|
||||
|
|
|
|||
54
website/client/src/components/shops/gemPrice.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<div
|
||||
v-once
|
||||
class="gem-price-div"
|
||||
:class="{'background': withBackground}"
|
||||
>
|
||||
<div
|
||||
:class="`mr-2 svg-icon gem icon-${iconSize}`"
|
||||
v-html="icons.gem"
|
||||
></div>
|
||||
<span class="gem-price">{{ gemPrice }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gemIcon from '@/assets/svg/gem.svg';
|
||||
|
||||
export default {
|
||||
name: 'GemPrice',
|
||||
props: ['gemPrice', 'iconSize', 'withBackground'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
gem: gemIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.gem-price {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
.gem-price-div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.background {
|
||||
align-self: center;
|
||||
border-radius: 20px;
|
||||
padding: 6px 20px;
|
||||
|
||||
background-color: rgba($green-100, 0.15);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
:item="item"
|
||||
:price="item.goldValue ? item.goldValue : item.value"
|
||||
:price-type="item.goldValue ? 'gold' : 'gem'"
|
||||
:item-content-class="'inventory_quest_scroll_'+item.key"
|
||||
:item-content-class="`inventory_quest_scroll_${item.key}`"
|
||||
:empty-item="false"
|
||||
:popover-position="'top'"
|
||||
@click="selectItem(item)"
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<item-with-label
|
||||
v-for="drop in getDropsList(quest.drop.items, false)"
|
||||
:key="drop.type+'_'+drop.key"
|
||||
:key="`${drop.type}_${drop.key}`"
|
||||
:item="{}"
|
||||
class="item-with-label"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -880,8 +880,6 @@ export default {
|
|||
},
|
||||
mounted () {
|
||||
hello.init({
|
||||
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
|
||||
// windows: WINDOWS_CLIENT_ID,
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||
});
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
|
|
|
|||
|
|
@ -682,7 +682,9 @@ export default {
|
|||
// as default filter for daily
|
||||
// and set the filter as 'due' only when the component first
|
||||
// loads and not on subsequent reloads.
|
||||
if (type === 'daily' && filter === '' && !this.challenge) {
|
||||
if (
|
||||
type === 'daily' && filter === '' && !this.challenge
|
||||
) {
|
||||
filter = 'due'; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
</span>
|
||||
<label
|
||||
v-once
|
||||
class="mb-1"
|
||||
v-html="text"
|
||||
></label>
|
||||
</div>
|
||||
|
|
@ -23,11 +22,11 @@
|
|||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
label {
|
||||
height: 1.5rem;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
letter-spacing: normal;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gray-200 {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
<div>
|
||||
<select-list
|
||||
:items="items"
|
||||
:key-prop="'icon'"
|
||||
class="difficulty-select"
|
||||
:class="{disabled: disabled}"
|
||||
:disabled="disabled"
|
||||
|
|
@ -10,7 +9,7 @@
|
|||
:hide-icon="true"
|
||||
@select="$emit('select', $event.value)"
|
||||
>
|
||||
<template v-slot:item="{ item, button }">
|
||||
<template #item="{ item, button }">
|
||||
<div
|
||||
v-if="item"
|
||||
class="difficulty-item"
|
||||
|
|
|
|||
|
|
@ -203,16 +203,15 @@
|
|||
<template
|
||||
v-if="task.type !== 'reward'"
|
||||
>
|
||||
<div class="d-flex mt-3">
|
||||
<div class="d-flex mt-3 align-items-center">
|
||||
<lockable-label
|
||||
:locked="challengeAccessRequired"
|
||||
:text="$t('difficulty')"
|
||||
/>
|
||||
<div
|
||||
v-b-tooltip.hover.righttop="$t('difficultyHelp')"
|
||||
class="svg-icon info-icon mb-auto ml-1"
|
||||
v-html="icons.information"
|
||||
></div>
|
||||
<information-icon
|
||||
tooltip-id="difficultyHelp"
|
||||
:tooltip="$t('difficultyHelp')"
|
||||
/>
|
||||
</div>
|
||||
<select-difficulty
|
||||
:value="task.priority"
|
||||
|
|
@ -452,7 +451,7 @@
|
|||
>
|
||||
<div>
|
||||
<div
|
||||
v-if="task.type === 'daily' && isUserTask && purpose === 'edit'"
|
||||
v-if="advancedSettingsShowRestoreStreak"
|
||||
class="option mt-3"
|
||||
>
|
||||
<div class="form-group">
|
||||
|
|
@ -479,8 +478,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="task.type === 'habit'
|
||||
&& isUserTask && purpose === 'edit' && (task.up || task.down)"
|
||||
v-if="advancedSettingsShowAdjustCounter"
|
||||
class="option mt-3"
|
||||
>
|
||||
<div class="form-group">
|
||||
|
|
@ -539,6 +537,31 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="advancedSettingsShowTaskAlias"
|
||||
class="option mt-3"
|
||||
>
|
||||
<div class="form-group">
|
||||
<label
|
||||
v-once
|
||||
class="mb-1"
|
||||
>{{ $t('taskAlias') }}
|
||||
|
||||
<information-icon
|
||||
tooltip-id="taskAlias"
|
||||
:tooltip="$t('taskAliasPopover')"
|
||||
/>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="task.alias"
|
||||
class="form-control"
|
||||
:placeholder="$t('taskAliasPlaceholder')"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
|
|
@ -882,6 +905,11 @@
|
|||
height: 1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.habit-option {
|
||||
&-container {
|
||||
min-width: 3rem;
|
||||
|
|
@ -997,7 +1025,6 @@ import lockableLabel from '@/components/tasks/modal-controls/lockableLabel';
|
|||
|
||||
import syncTask from '../../mixins/syncTask';
|
||||
|
||||
import informationIcon from '@/assets/svg/information.svg';
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
import negativeIcon from '@/assets/svg/negative.svg';
|
||||
import streakIcon from '@/assets/svg/streak.svg';
|
||||
|
|
@ -1006,10 +1033,12 @@ import goldIcon from '@/assets/svg/gold.svg';
|
|||
import chevronIcon from '@/assets/svg/chevron.svg';
|
||||
import calendarIcon from '@/assets/svg/calendar.svg';
|
||||
import gripIcon from '@/assets/svg/grip.svg';
|
||||
import InformationIcon from '@/components/ui/informationIcon.vue';
|
||||
|
||||
|
||||
export default {
|
||||
components: {
|
||||
InformationIcon,
|
||||
SelectMulti,
|
||||
Datepicker,
|
||||
checklist,
|
||||
|
|
@ -1029,7 +1058,6 @@ export default {
|
|||
showAssignedSelect: false,
|
||||
newChecklistItem: null,
|
||||
icons: Object.freeze({
|
||||
information: informationIcon,
|
||||
negative: negativeIcon,
|
||||
positive: positiveIcon,
|
||||
destroy: deleteIcon,
|
||||
|
|
@ -1064,25 +1092,25 @@ export default {
|
|||
dayMapping: 'constants.DAY_MAPPING',
|
||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||
}),
|
||||
advancedSettingsAvailable () {
|
||||
if (
|
||||
this.task.type === 'reward'
|
||||
|| this.task.type === 'todo'
|
||||
|| this.purpose === 'create'
|
||||
|| !this.isUserTask
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.task.type === 'habit'
|
||||
&& !this.task.up
|
||||
&& !this.task.down
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// region advanced settings
|
||||
advancedSettingsShowAdjustCounter () {
|
||||
return this.task.type === 'habit'
|
||||
&& this.isUserTask && this.purpose === 'edit'
|
||||
&& (this.task.up || this.task.down);
|
||||
},
|
||||
advancedSettingsShowRestoreStreak () {
|
||||
return this.task.type === 'daily' && this.isUserTask
|
||||
&& this.purpose === 'edit';
|
||||
},
|
||||
advancedSettingsShowTaskAlias () {
|
||||
return this.isUserTask && this.user.preferences.developerMode;
|
||||
},
|
||||
advancedSettingsAvailable () {
|
||||
return this.advancedSettingsShowRestoreStreak
|
||||
|| this.advancedSettingsShowAdjustCounter
|
||||
|| this.advancedSettingsShowTaskAlias;
|
||||
},
|
||||
// endregion advanced settings
|
||||
checklistEnabled () {
|
||||
return ['daily', 'todo'].indexOf(this.task.type) > -1 && !this.isOriginalChallengeTask;
|
||||
},
|
||||
|
|
@ -1157,7 +1185,6 @@ export default {
|
|||
},
|
||||
},
|
||||
async mounted () {
|
||||
this.showAdvancedOptions = !this.user.preferences.advancedCollapsed;
|
||||
if (this.groupId) {
|
||||
const groupResponse = await axios.get(`/api/v4/groups/${this.groupId}`);
|
||||
this.managers = Object.keys(groupResponse.data.data.managers);
|
||||
|
|
|
|||
37
website/client/src/components/ui/informationIcon.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<span class="ml-1">
|
||||
<div
|
||||
:id="`tooltip_${tooltipId}`"
|
||||
class="svg-icon icon-16"
|
||||
:title="tooltip"
|
||||
v-html="icons.information"
|
||||
></div>
|
||||
<b-tooltip
|
||||
:title="tooltip"
|
||||
:target="`tooltip_${tooltipId}`"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import informationIcon from '@/assets/svg/information.svg';
|
||||
|
||||
export default {
|
||||
name: 'InformationIcon',
|
||||
props: ['tooltipId', 'tooltip'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
information: informationIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
@show="isOpened = true"
|
||||
@hide="isOpened = false"
|
||||
>
|
||||
<template v-slot:button-content>
|
||||
<template #button-content>
|
||||
<slot
|
||||
name="item"
|
||||
:item="selected || placeholder"
|
||||
|
|
@ -21,13 +21,13 @@
|
|||
</template>
|
||||
<b-dropdown-item
|
||||
v-for="item in items"
|
||||
:key="keyProp ? item[keyProp] : item"
|
||||
:disabled="typeof item[disabledProp] === 'undefined' ? false : item[disabledProp]"
|
||||
:active="item === selected"
|
||||
:key="getKeyProp(item)"
|
||||
:disabled="isDisabled(item)"
|
||||
:active="isSelected(item)"
|
||||
:class="{
|
||||
active: item === selected,
|
||||
active: isSelected(item),
|
||||
selectListItem: true,
|
||||
showIcon: !hideIcon && item === selected
|
||||
showIcon: !hideIcon && isSelected(item)
|
||||
}"
|
||||
@click="selectItem(item)"
|
||||
>
|
||||
|
|
@ -51,39 +51,39 @@
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.select-list ::v-deep {
|
||||
.dropdown-toggle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 25px; /* To allow enough room for the down arrow to be displayed */
|
||||
.select-list ::v-deep {
|
||||
.dropdown-toggle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 25px; /* To allow enough room for the down arrow to be displayed */
|
||||
}
|
||||
|
||||
.selectListItem {
|
||||
position: relative;
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.selectListItem {
|
||||
position: relative;
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:not(.showIcon) {
|
||||
.svg-icon.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.showIcon) {
|
||||
.svg-icon.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon.check-icon.color {
|
||||
margin-left: 10px; /* So the flex item (checkmark) will have some spacing from the text */
|
||||
width: 0.77rem;
|
||||
height: 0.615rem;
|
||||
color: $purple-300;
|
||||
}
|
||||
.svg-icon.check-icon.color {
|
||||
margin-left: 10px; /* So the flex item (checkmark) will have some spacing from the text */
|
||||
width: 0.77rem;
|
||||
height: 0.615rem;
|
||||
color: $purple-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
|
@ -101,6 +101,9 @@ export default {
|
|||
keyProp: {
|
||||
type: String,
|
||||
},
|
||||
activeKeyProp: {
|
||||
type: String,
|
||||
},
|
||||
disabledProp: {
|
||||
type: String,
|
||||
},
|
||||
|
|
@ -128,10 +131,23 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
getKeyProp (item) {
|
||||
return this.keyProp ? item[this.keyProp] : item;
|
||||
},
|
||||
isDisabled (item) {
|
||||
return typeof item[this.disabledProp] === 'undefined' ? false : item[this.disabledProp];
|
||||
},
|
||||
selectItem (item) {
|
||||
this.selected = item;
|
||||
this.selected = this.getKeyProp(item);
|
||||
this.$emit('select', item);
|
||||
},
|
||||
isSelected (item) {
|
||||
if (this.activeKeyProp) {
|
||||
return item[this.activeKeyProp] === this.selected;
|
||||
}
|
||||
|
||||
return item === this.selected;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
131
website/client/src/components/ui/validatedTextInput.vue
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="label-line">
|
||||
<div
|
||||
v-if="settingsLabel"
|
||||
class="settings-label"
|
||||
>
|
||||
{{ $t(settingsLabel) }}
|
||||
</div>
|
||||
|
||||
<slot name="top-right"></slot>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
:class="{
|
||||
'is-valid': validStyle,
|
||||
'is-invalid': invalidStyle
|
||||
}"
|
||||
>
|
||||
<input
|
||||
:value="value"
|
||||
class="form-control"
|
||||
:type="inputType"
|
||||
:class="{
|
||||
'is-invalid input-invalid': invalidStyle,
|
||||
'is-valid input-valid': validStyle
|
||||
}"
|
||||
:readonly="readonly"
|
||||
:aria-readonly="readonly"
|
||||
|
||||
:placeholder="placeholder"
|
||||
@keyup="handleChange"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-for="issue in invalidIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ValidatedTextInput',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'update:value',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isValid: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onlyShowInvalidState: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
settingsLabel: {
|
||||
type: String,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
},
|
||||
invalidIssues: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
wasChanged: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canChangeClasses () {
|
||||
return !this.readonly && this.wasChanged;
|
||||
},
|
||||
validStyle () {
|
||||
return this.canChangeClasses && this.isValid && !this.onlyShowInvalidState;
|
||||
},
|
||||
invalidStyle () {
|
||||
return this.canChangeClasses && !this.isValid;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange ({ target: { value } }) {
|
||||
this.wasChanged = true;
|
||||
this.$emit('update:value', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.label-line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import BootstrapVue from 'bootstrap-vue';
|
||||
import Fragment from 'vue-fragment';
|
||||
import AppComponent from './app';
|
||||
import {
|
||||
setup as setupAnalytics,
|
||||
|
|
@ -28,6 +29,7 @@ Vue.config.productionTip = IS_PRODUCTION;
|
|||
Vue.use(i18n, { i18nData: window && window['habitica-i18n'] });
|
||||
Vue.use(StoreModule);
|
||||
Vue.use(BootstrapVue);
|
||||
Vue.use(Fragment.Plugin);
|
||||
|
||||
setUpLogging();
|
||||
setupAnalytics(); // just create queues for analytics, no scripts loaded at this time
|
||||
|
|
|
|||
23
website/client/src/mixins/copyToClipboard.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import notifications from './notifications';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
methods: {
|
||||
async mixinCopyToClipboard (valueToCopy, notificationToShow = null) {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(valueToCopy);
|
||||
} else {
|
||||
// fallback if clipboard API does not exist
|
||||
const copyText = document.createElement('textarea');
|
||||
copyText.value = valueToCopy;
|
||||
document.body.appendChild(copyText);
|
||||
copyText.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(copyText);
|
||||
}
|
||||
if (notificationToShow) {
|
||||
this.text(notificationToShow);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -10,7 +10,7 @@ function toFixedWithoutRounding (num, fixed) {
|
|||
return num.toString().match(re)[0];
|
||||
}
|
||||
|
||||
export default {
|
||||
export const NotificationMixins = {
|
||||
computed: {
|
||||
...mapState({ notifications: 'notificationStore' }),
|
||||
},
|
||||
|
|
@ -90,3 +90,5 @@ export default {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default NotificationMixins;
|
||||
|
|
|
|||
62
website/client/src/mixins/passwordInputChecks.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Component Example
|
||||
*
|
||||
* <current-password-input
|
||||
* :show-forget-password="true"
|
||||
* :is-valid="mixinData.currentPasswordIssues.length === 0"
|
||||
* :invalid-issues="mixinData.currentPasswordIssues"
|
||||
* @passwordValue="updates.password = $event"
|
||||
* />
|
||||
*/
|
||||
|
||||
export const PasswordInputChecksMixin = {
|
||||
data () {
|
||||
return {
|
||||
mixinData: {
|
||||
currentPasswordIssues: [],
|
||||
newPasswordIssues: [],
|
||||
confirmPasswordIssues: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clearPasswordIssues () {
|
||||
this.mixinData.currentPasswordIssues.length = 0;
|
||||
this.mixinData.newPasswordIssues.length = 0;
|
||||
this.mixinData.confirmPasswordIssues.length = 0;
|
||||
},
|
||||
/**
|
||||
* @param {() => Promise<void>} promiseCall
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async passwordInputCheckMixinTryCall (promiseCall) {
|
||||
try {
|
||||
// reset previous issues
|
||||
this.clearPasswordIssues();
|
||||
|
||||
await promiseCall();
|
||||
} catch (axiosError) {
|
||||
const message = axiosError.response?.data?.message;
|
||||
|
||||
if ([this.$t('wrongPassword'), this.$t('missingPassword')].includes(message)) {
|
||||
this.mixinData.currentPasswordIssues.push(message);
|
||||
} else if ([this.$t('missingNewPassword'), this.$t('passwordIssueLength'), this.$t('passwordConfirmationMatch')].includes(message)) {
|
||||
this.mixinData.newPasswordIssues.push(message);
|
||||
this.mixinData.confirmPasswordIssues.push(message);
|
||||
} else if (this.$t('invalidReqParams') === message) {
|
||||
const errors = axiosError.response?.data?.errors ?? [];
|
||||
|
||||
for (const error of errors) {
|
||||
if (error.param === 'password') {
|
||||
this.mixinData.currentPasswordIssues.push(error.message);
|
||||
} else if (error.param === 'newPassword') {
|
||||
this.mixinData.newPasswordIssues.push(error.message);
|
||||
} else {
|
||||
this.mixinData.confirmPasswordIssues.push(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -3,7 +3,7 @@ import { mapState } from '@/libs/store';
|
|||
export const userCustomStateMixin = fieldname => {
|
||||
const map = { };
|
||||
map[fieldname] = 'user.data';
|
||||
return { // eslint-disable-line import/prefer-default-export
|
||||
return {
|
||||
computed: {
|
||||
...mapState(map),
|
||||
},
|
||||
|
|
|
|||
238
website/client/src/pages/settings-overview.vue
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<secondary-menu class="col-12">
|
||||
<template
|
||||
v-for="routePath in tabs"
|
||||
>
|
||||
<router-link
|
||||
v-if="allowedToShowTab(routePath)"
|
||||
:key="routePath"
|
||||
|
||||
class="nav-link"
|
||||
:to="{name: routePath}"
|
||||
exact="exact"
|
||||
:class="{'active': $route.name === routePath}"
|
||||
>
|
||||
{{ $t(pathTranslateKey(routePath)) }}
|
||||
</router-link>
|
||||
</template>
|
||||
</secondary-menu>
|
||||
<div
|
||||
v-if="$route.name === 'subscription' && promo === 'g1g1'"
|
||||
class="g1g1-banner d-flex justify-content-center"
|
||||
@click="showSelectUser"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts left-gift"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-items-center text-center">
|
||||
<strong
|
||||
class="mt-auto mb-1"
|
||||
> {{ $t('g1g1Event') }} </strong>
|
||||
<p
|
||||
class="mb-auto"
|
||||
>
|
||||
{{ $t('g1g1Details') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gifts right-gift"
|
||||
v-html="icons.gifts"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-12 d-flex "
|
||||
:class="{'justify-content-center': applyNarrowView}"
|
||||
>
|
||||
<div :class="{'settings-content': applyNarrowView, 'full-width-content': !applyNarrowView}">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
strong {
|
||||
font-size: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.g1g1-banner {
|
||||
color: $white;
|
||||
width: 100%;
|
||||
height: 5.75rem;
|
||||
background-image: linear-gradient(90deg, $teal-50 0%, $purple-400 100%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.left-gift {
|
||||
margin: auto 3rem auto auto;
|
||||
}
|
||||
|
||||
.right-gift {
|
||||
margin: auto auto auto 3rem;
|
||||
filter: flipH;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.svg-gifts {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
.full-width-content {
|
||||
width: 100%;
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
flex: 0 0 732px;
|
||||
max-width: unset;
|
||||
|
||||
::v-deep {
|
||||
line-height: 1.71;
|
||||
|
||||
.small {
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
table tr.expanded td {
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
font-weight: bold;
|
||||
color: $gray-50;
|
||||
|
||||
width: 23%;
|
||||
}
|
||||
|
||||
.input-area .settings-label {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.settings-value {
|
||||
color: $gray-50;
|
||||
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.settings-button {
|
||||
width: 30%;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: $purple-300;
|
||||
|
||||
&.danger {
|
||||
color: $maroon-50;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-disclaimer {
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
width: 320px;
|
||||
margin: 1rem auto 0;
|
||||
}
|
||||
|
||||
.edit-link {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.remove-link {
|
||||
color: $maroon-50 !important;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import find from 'lodash/find';
|
||||
import { mapState } from '@/libs/store';
|
||||
import SecondaryMenu from '@/components/secondaryMenu';
|
||||
import gifts from '@/assets/svg/gifts-vertical.svg';
|
||||
import { userStateMixin } from '@/mixins/userState';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SecondaryMenu,
|
||||
},
|
||||
mixins: [userStateMixin],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
gifts,
|
||||
}),
|
||||
tabs: [
|
||||
'general',
|
||||
'subscription',
|
||||
'siteData',
|
||||
'promoCode',
|
||||
'transactions',
|
||||
'notifications',
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
currentEventList: 'worldState.data.currentEventList',
|
||||
}),
|
||||
currentEvent () {
|
||||
return find(this.currentEventList, event => Boolean(event.promo));
|
||||
},
|
||||
promo () {
|
||||
if (!this.currentEvent || !this.currentEvent.promo) return 'none';
|
||||
return this.currentEvent.promo;
|
||||
},
|
||||
applyNarrowView () {
|
||||
return !['subscription', 'transactions'].includes(this.$route.name);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param {String} tabName
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
allowedToShowTab (tabName) {
|
||||
const transactionsTab = tabName === 'transactions';
|
||||
|
||||
return transactionsTab
|
||||
? this.hasPermission(this.user, 'userSupport')
|
||||
: true;
|
||||
},
|
||||
|
||||
showSelectUser () {
|
||||
this.$root.$emit('bv::show::modal', 'select-user-modal');
|
||||
},
|
||||
pathTranslateKey (path) {
|
||||
if (path === 'api') {
|
||||
return 'API';
|
||||
}
|
||||
return path;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<div
|
||||
class="class-value"
|
||||
:class="{[selectedClass]: !classDisabled, disabled: classDisabled}"
|
||||
>
|
||||
<span
|
||||
v-if="!classDisabled"
|
||||
class="svg-icon icon-16 mr-2"
|
||||
v-html="classIcons[selectedClass]"
|
||||
></span>
|
||||
|
||||
<span
|
||||
v-if="classDisabled"
|
||||
class="label"
|
||||
>
|
||||
{{ $t('noClassSelected') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="label"
|
||||
>
|
||||
{{ $t(selectedClass) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import warriorIcon from '@/assets/svg/warrior.svg';
|
||||
import rogueIcon from '@/assets/svg/rogue.svg';
|
||||
import healerIcon from '@/assets/svg/healer.svg';
|
||||
import wizardIcon from '@/assets/svg/wizard.svg';
|
||||
|
||||
export default {
|
||||
name: 'ClassIconLabel',
|
||||
props: ['selectedClass', 'classDisabled'],
|
||||
data () {
|
||||
return {
|
||||
classIcons: Object.freeze({
|
||||
warrior: warriorIcon,
|
||||
rogue: rogueIcon,
|
||||
healer: healerIcon,
|
||||
wizard: wizardIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.class-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(.disabled) {
|
||||
.label {
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.healer {
|
||||
color: $healer-color;
|
||||
}
|
||||
|
||||
.rogue {
|
||||
color: $rogue-color;
|
||||
}
|
||||
|
||||
.warrior {
|
||||
color: $warrior-color;
|
||||
}
|
||||
|
||||
.wizard {
|
||||
color: $wizard-color;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: $maroon-50;
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<div class="input-area">
|
||||
<validated-text-input
|
||||
v-model="currentPassword"
|
||||
:settings-label="customLabel ?? 'password'"
|
||||
|
||||
:placeholder="$t(customLabel ?? 'password')"
|
||||
:is-valid="isValid"
|
||||
:invalid-issues="invalidIssues"
|
||||
:only-show-invalid-state="true"
|
||||
input-type="password"
|
||||
@update:value="$emit('passwordValue', currentPassword)"
|
||||
>
|
||||
<div
|
||||
v-if="showForgetPassword"
|
||||
slot="top-right"
|
||||
class="forgot-password"
|
||||
>
|
||||
<router-link
|
||||
to="/forgot-password"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $t('forgotPassword') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</validated-text-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
|
||||
|
||||
export default {
|
||||
name: 'CurrentPasswordInput',
|
||||
components: { ValidatedTextInput },
|
||||
props: ['customLabel', 'showForgetPassword', 'isValid', 'invalidIssues'],
|
||||
data () {
|
||||
return {
|
||||
currentPassword: '',
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.forgot-password {
|
||||
a {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
export const GenericUserPreferencesMixin = {
|
||||
methods: {
|
||||
setUserPreference (preferenceType, subtype) {
|
||||
const settings = {};
|
||||
if (!subtype) {
|
||||
settings[`preferences.${preferenceType}`] = this.user.preferences[preferenceType];
|
||||
} else {
|
||||
settings[`preferences.${preferenceType}.${subtype}`] = this.user.preferences[preferenceType][subtype];
|
||||
}
|
||||
return this.$store.dispatch('user:set', settings);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { reactive } from 'vue';
|
||||
|
||||
export const sharedInlineSettingStore = reactive({
|
||||
inlineSettingAlreadyOpen: false,
|
||||
inlineSettingUnsavedValues: false,
|
||||
/**
|
||||
* @type InlineSettingMixin
|
||||
*/
|
||||
instanceOfCurrentlyOpened: null,
|
||||
markAsOpened (currentInstance) {
|
||||
this.inlineSettingAlreadyOpen = true;
|
||||
this.instanceOfCurrentlyOpened = currentInstance;
|
||||
},
|
||||
markAsClosed () {
|
||||
this.inlineSettingUnsavedValues = false;
|
||||
this.inlineSettingAlreadyOpen = false;
|
||||
},
|
||||
});
|
||||
|
||||
export const InlineSettingMixin = {
|
||||
data () {
|
||||
return {
|
||||
mixinData: {
|
||||
inlineSettingMixin: {
|
||||
modalVisible: false,
|
||||
sharedState: sharedInlineSettingStore,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
if (this.mixinData.inlineSettingMixin.sharedState.inlineSettingAlreadyOpen) {
|
||||
if (this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues) {
|
||||
if (window.confirm(this.$t('confirmCancelChanges'))) {
|
||||
this._hidePrevious();
|
||||
this._openIt();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this._hidePrevious();
|
||||
}
|
||||
}
|
||||
|
||||
this._openIt();
|
||||
},
|
||||
_openIt () {
|
||||
this.mixinData.inlineSettingMixin.sharedState.markAsOpened(this);
|
||||
this.mixinData.inlineSettingMixin.modalVisible = true;
|
||||
|
||||
this.$el.scrollTo({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
},
|
||||
_hidePrevious () {
|
||||
this.mixinData.inlineSettingMixin.sharedState.instanceOfCurrentlyOpened.resetControls();
|
||||
this.mixinData.inlineSettingMixin.sharedState.instanceOfCurrentlyOpened.closeModal();
|
||||
},
|
||||
/**
|
||||
* This is just for the cancel buttons - so that they also ask if there are unchanged values
|
||||
*/
|
||||
requestCloseModal () {
|
||||
if (this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues && !window.confirm(this.$t('confirmCancelChanges'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetControls();
|
||||
this.closeModal();
|
||||
},
|
||||
/**
|
||||
* This is for the save methods to call it after they are done
|
||||
*/
|
||||
closeModal () {
|
||||
this.mixinData.inlineSettingMixin.modalVisible = false;
|
||||
this.mixinData.inlineSettingMixin.sharedState.markAsClosed();
|
||||
},
|
||||
modalValuesChanged (value = true) {
|
||||
this.mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues = value;
|
||||
},
|
||||
resetControls () {},
|
||||
},
|
||||
};
|
||||
100
website/client/src/pages/settings/components/lockedInput.vue
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div class="input-area">
|
||||
<div class="label-line">
|
||||
<div class="settings-label">
|
||||
{{ label }}
|
||||
</div>
|
||||
<div
|
||||
class="link-style"
|
||||
@click="mixinCopyToClipboard(value, notificationText)"
|
||||
>
|
||||
{{ $t('copy') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
>
|
||||
<div class="input-group-prepend input-group-icon">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon icon-16"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
</div>
|
||||
<input
|
||||
:value="value"
|
||||
class="form-control"
|
||||
readonly
|
||||
aria-readonly="true"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import CopyToClipboard from '@/mixins/copyToClipboard';
|
||||
import svgLockSmall from '@/assets/svg/lock-small.svg';
|
||||
|
||||
export default {
|
||||
name: 'LockedInput',
|
||||
mixins: [CopyToClipboard],
|
||||
props: ['label', 'value', 'notificationText'],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
lock: svgLockSmall,
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.label-line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.link-style {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $purple-300;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover, &:active, &:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group {
|
||||
border-radius: 2px;
|
||||
|
||||
input {
|
||||
border: solid 1px $gray-500;
|
||||
background-color: $gray-700;
|
||||
|
||||
&:hover {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-icon {
|
||||
padding: 8px;
|
||||
|
||||
border-radius: 2px;
|
||||
|
||||
background-color: $gray-600;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div
|
||||
class="buttons"
|
||||
:class="{'no-padding': noPadding}"
|
||||
>
|
||||
<button
|
||||
v-if="!hideSave"
|
||||
class="btn btn-save"
|
||||
:class="primaryButtonColor ?? 'btn-primary'"
|
||||
type="submit"
|
||||
:disabled="disableSave"
|
||||
@click="$emit('saveClicked')"
|
||||
>
|
||||
{{ $t(primaryButtonLabel ?? 'save') }}
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-if="!hideCancel"
|
||||
class="edit-link"
|
||||
@click.prevent="$emit('cancelClicked')"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SaveCancelButtons',
|
||||
props: {
|
||||
hideSave: {
|
||||
type: Boolean,
|
||||
},
|
||||
hideCancel: {
|
||||
type: Boolean,
|
||||
},
|
||||
disableSave: {
|
||||
type: Boolean,
|
||||
},
|
||||
noPadding: {
|
||||
type: Boolean,
|
||||
},
|
||||
primaryButtonLabel: {
|
||||
type: String,
|
||||
},
|
||||
primaryButtonColor: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.buttons {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:not(.no-padding) {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
56
website/client/src/pages/settings/components/yourBalance.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="your-balance">
|
||||
<span
|
||||
v-once
|
||||
class="label"
|
||||
>
|
||||
{{ $t('yourBalance') }}
|
||||
</span>
|
||||
|
||||
<balance-info
|
||||
class="balance-info"
|
||||
:currency-needed="currencyNeeded"
|
||||
:amount-needed="amountNeeded"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BalanceInfo from '@/components/shops/balanceInfo.vue';
|
||||
|
||||
export default {
|
||||
name: 'YourBalance',
|
||||
components: { BalanceInfo },
|
||||
props: {
|
||||
currencyNeeded: {
|
||||
type: String,
|
||||
},
|
||||
amountNeeded: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.your-balance {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
background-color: $gray-600;
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
}
|
||||
|
||||
.balance-info {
|
||||
display: inline-block !important;
|
||||
}
|
||||
</style>
|
||||
140
website/client/src/pages/settings/generalSettings.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<div class="col-12">
|
||||
<h1
|
||||
v-once
|
||||
class="page-header"
|
||||
>
|
||||
{{ $t('generalSettings') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<h2 v-once>
|
||||
{{ $t('account') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<user-name-setting />
|
||||
<user-email-setting />
|
||||
<display-name-setting />
|
||||
<password-setting />
|
||||
<reset-account />
|
||||
<delete-account />
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 v-once>
|
||||
{{ $t('loginMethods') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<LoginMethods />
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2 v-once>
|
||||
{{ $t('site') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<language-setting />
|
||||
<date-format-setting />
|
||||
<day-start-adjustment-setting />
|
||||
<audio-theme-setting />
|
||||
<sleep-mode />
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<h2 v-once>
|
||||
{{ $t('character') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<fix-values-setting />
|
||||
<class-setting />
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.standard-page {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: $gray-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import notificationsMixin from '../../mixins/notifications';
|
||||
import UserNameSetting from './settingRows/userNameSetting';
|
||||
import UserEmailSetting from './settingRows/userEmailSetting';
|
||||
import DisplayNameSetting from './settingRows/displayNameSetting';
|
||||
import PasswordSetting from './settingRows/passwordSetting';
|
||||
import ResetAccount from './settingRows/resetAccount';
|
||||
import DeleteAccount from './settingRows/deleteAccount';
|
||||
import { sharedInlineSettingStore } from './components/inlineSettingMixin';
|
||||
import LanguageSetting from './settingRows/languageSetting';
|
||||
import DateFormatSetting from './settingRows/dateFormatSetting';
|
||||
import DayStartAdjustmentSetting from './settingRows/dayStartAdjustmentSetting.vue';
|
||||
import AudioThemeSetting from '@/pages/settings/settingRows/audioThemeSetting.vue';
|
||||
import ClassSetting from '@/pages/settings/settingRows/classSetting.vue';
|
||||
import FixValuesSetting from '@/pages/settings/settingRows/fixValuesSetting.vue';
|
||||
import LoginMethods from '@/pages/settings/settingRows/loginMethods.vue';
|
||||
import { GenericUserPreferencesMixin } from '@/pages/settings/components/genericUserPreferencesMixin';
|
||||
import { mapState } from '@/libs/store';
|
||||
import SleepMode from '@/pages/settings/settingRows/sleepMode.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SleepMode,
|
||||
LoginMethods,
|
||||
FixValuesSetting,
|
||||
ClassSetting,
|
||||
AudioThemeSetting,
|
||||
DayStartAdjustmentSetting,
|
||||
DateFormatSetting,
|
||||
LanguageSetting,
|
||||
DeleteAccount,
|
||||
ResetAccount,
|
||||
PasswordSetting,
|
||||
DisplayNameSetting,
|
||||
UserEmailSetting,
|
||||
UserNameSetting,
|
||||
},
|
||||
mixins: [notificationsMixin, GenericUserPreferencesMixin],
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
beforeRouteLeave (_, __, next) {
|
||||
sharedInlineSettingStore.markAsClosed();
|
||||
next();
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('generalSettings'),
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
325
website/client/src/pages/settings/notificationSettings.vue
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
<template>
|
||||
<div class="row standard-page px-0">
|
||||
<div class="col-12">
|
||||
<h1
|
||||
v-once
|
||||
class="page-header"
|
||||
>
|
||||
{{ $t('notifications') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<h2 v-once>
|
||||
{{ $t('allNotifications') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="bold">
|
||||
{{ $t('unsubscribeAllPush') }}
|
||||
</td>
|
||||
<td>
|
||||
<toggle-switch
|
||||
:checked="user.preferences.pushNotifications.unsubscribeFromAll"
|
||||
@change="set('pushNotifications', 'unsubscribeFromAll', $event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="bold">{{ $t('unsubscribeAllEmails') }}</span> <br>
|
||||
<small>{{ $t('unsubscribeAllEmailsText') }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<toggle-switch
|
||||
:checked="user.preferences.emailNotifications.unsubscribeFromAll"
|
||||
@change="set('emailNotifications', 'unsubscribeFromAll', $event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<h2>Website</h2>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td
|
||||
v-once
|
||||
class="bold"
|
||||
>
|
||||
{{ $t('showLevelUpModal') }}
|
||||
</td>
|
||||
<td class="email_push_col">
|
||||
<toggle-switch
|
||||
:checked="!user.preferences.suppressModals.levelUp"
|
||||
class="toggle-switch-width"
|
||||
@change="set('suppressModals', 'levelUp', !$event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
v-once
|
||||
class="bold"
|
||||
>
|
||||
{{ $t('showHatchPetModal') }}
|
||||
</td>
|
||||
<td class="email_push_col">
|
||||
<toggle-switch
|
||||
:checked="!user.preferences.suppressModals.hatchPet"
|
||||
class="toggle-switch-width"
|
||||
@change="set('suppressModals', 'hatchPet', !$event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
v-once
|
||||
class="bold"
|
||||
>
|
||||
{{ $t('showRaisePetModal') }}
|
||||
</td>
|
||||
<td class="email_push_col">
|
||||
<toggle-switch
|
||||
:checked="!user.preferences.suppressModals.raisePet"
|
||||
class="toggle-switch-width"
|
||||
@change="set('suppressModals', 'raisePet', !$event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
v-once
|
||||
class="bold"
|
||||
>
|
||||
{{ $t('showStreakModal') }}
|
||||
</td>
|
||||
<td class="email_push_col">
|
||||
<toggle-switch
|
||||
:checked="!user.preferences.suppressModals.streak"
|
||||
class="toggle-switch-width"
|
||||
@change="set('suppressModals', 'streak', !$event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
v-once
|
||||
class="bold"
|
||||
>
|
||||
{{ $t('baileyAnnouncement') }}
|
||||
</td>
|
||||
<td class="email_push_col show_bailey_col">
|
||||
<b-popover
|
||||
target="viewBaileyLink"
|
||||
triggers="hover"
|
||||
placement="right"
|
||||
:content="$t('showBaileyPop')"
|
||||
/>
|
||||
<a
|
||||
id="viewBaileyLink"
|
||||
class="show_bailey_link"
|
||||
@click="showBailey()"
|
||||
>
|
||||
{{ $t('view') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<h2>Email & Push</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td></td>
|
||||
<th class="email_push_col email_col_padding">
|
||||
<span v-once>{{ $t('email') }}</span>
|
||||
</th>
|
||||
<th class="email_push_col">
|
||||
<span v-once>{{ $t('push') }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="notification in notificationsIds"
|
||||
:key="notification"
|
||||
>
|
||||
<td
|
||||
v-once
|
||||
class="bold"
|
||||
>
|
||||
{{ $t(notification) }}
|
||||
</td>
|
||||
<td class="email_push_col">
|
||||
<toggle-switch
|
||||
:checked="user.preferences.emailNotifications[notification]"
|
||||
class="toggle-switch-width"
|
||||
@change="set('emailNotifications', notification, $event)"
|
||||
/>
|
||||
</td>
|
||||
<td class="email_push_col">
|
||||
<toggle-switch
|
||||
v-if="!onlyEmailsIds.includes(notification)"
|
||||
:checked="user.preferences.pushNotifications[notification]"
|
||||
class="toggle-switch-width"
|
||||
@change="set('pushNotifications', notification, $event)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.toggle-switch-width {
|
||||
::v-deep {
|
||||
.toggle-switch {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.email_push_col {
|
||||
width: 50px;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
/** Table Styles, maybe can be copied / extracted once more Pages need it */
|
||||
|
||||
.table {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.table th, .table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
}
|
||||
|
||||
.email_col_padding {
|
||||
padding-right: 70px !important;
|
||||
}
|
||||
|
||||
toggle-switch {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.show_bailey_col {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.show_bailey_link {
|
||||
padding-right: 8px;
|
||||
line-height: 1.71;
|
||||
// color: $blue-10 !important;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import notificationsMixin from '@/mixins/notifications';
|
||||
import ToggleSwitch from '@/components/ui/toggleSwitch';
|
||||
|
||||
export default {
|
||||
components: { ToggleSwitch },
|
||||
mixins: [notificationsMixin],
|
||||
data () {
|
||||
return {
|
||||
notificationsIds: Object.freeze([
|
||||
'majorUpdates',
|
||||
'newPM',
|
||||
'giftedGems',
|
||||
'giftedSubscription',
|
||||
'invitedParty',
|
||||
'invitedGuild',
|
||||
'invitedQuest',
|
||||
'questStarted',
|
||||
'wonChallenge',
|
||||
// 'weeklyRecaps',
|
||||
'kickedGroup',
|
||||
'onboarding',
|
||||
'importantAnnouncements',
|
||||
'subscriptionReminders',
|
||||
]),
|
||||
// list of email-only notifications
|
||||
onlyEmailsIds: Object.freeze([
|
||||
'kickedGroup',
|
||||
'importantAnnouncements',
|
||||
'weeklyRecaps',
|
||||
'onboarding',
|
||||
'subscriptionReminders',
|
||||
]),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
async mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('notifications'),
|
||||
});
|
||||
// If ?unsubFrom param is passed with valid email type,
|
||||
// automatically unsubscribe users from that email and
|
||||
// show an alert
|
||||
|
||||
// A simple object to map the key stored in the db (user.preferences.emailNotification[key])
|
||||
// to its string id but ONLY when the preferences' key and the string key don't match
|
||||
const MAP_PREF_TO_EMAIL_STRING = {
|
||||
importantAnnouncements: 'inactivityEmails',
|
||||
};
|
||||
|
||||
const { unsubFrom } = this.$route.query;
|
||||
|
||||
if (unsubFrom) {
|
||||
await this.$store.dispatch('user:set', {
|
||||
[`preferences.emailNotifications.${unsubFrom}`]: false,
|
||||
});
|
||||
|
||||
const emailTypeString = this.$t(MAP_PREF_TO_EMAIL_STRING[unsubFrom] || unsubFrom);
|
||||
this.text(this.$t('correctlyUnsubscribedEmailType', { emailType: emailTypeString }));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
set (preferenceType, notification, $event) {
|
||||
const settings = {};
|
||||
settings[`preferences.${preferenceType}.${notification}`] = $event ?? this.user.preferences[preferenceType][notification];
|
||||
this.$store.dispatch('user:set', settings);
|
||||
},
|
||||
showBailey () {
|
||||
this.$root.$emit('bv::show::modal', 'new-stuff');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
77
website/client/src/pages/settings/promoCode.vue
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<div class="col-12">
|
||||
<h1
|
||||
v-once
|
||||
class="page-header"
|
||||
>
|
||||
{{ $t('promoCode') }}
|
||||
</h1>
|
||||
|
||||
<div class="input-area">
|
||||
<div
|
||||
class="form-inline"
|
||||
role="form"
|
||||
>
|
||||
<input
|
||||
v-model="couponCode"
|
||||
class="form-control w-100"
|
||||
type="text"
|
||||
:placeholder="$t('promoPlaceholder')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="small mt-2"
|
||||
>
|
||||
{{ $t('couponText') }}
|
||||
</div>
|
||||
<save-cancel-buttons
|
||||
:hide-cancel="true"
|
||||
primary-button-label="submit"
|
||||
@saveClicked="enterCoupon()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import SaveCancelButtons from '@/pages/settings/components/saveCancelButtons.vue';
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
mixins: [notifications],
|
||||
data () {
|
||||
return {
|
||||
codes: {
|
||||
event: '',
|
||||
count: '',
|
||||
},
|
||||
couponCode: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data', credentials: 'credentials' }),
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('promoCode'),
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
async enterCoupon () {
|
||||
const code = await axios.post(`/api/v4/coupons/enter/${this.couponCode}`);
|
||||
if (!code) return;
|
||||
|
||||
this.$store.state.user.data = code.data.data;
|
||||
|
||||
this.text(this.$t('promoCodeApplied'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import PurchaseHistoryTable from '../ui/purchaseHistoryTable.vue';
|
||||
import PurchaseHistoryTable from '../../components/ui/purchaseHistoryTable.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td
|
||||
v-once
|
||||
class="settings-label"
|
||||
>
|
||||
{{ $t("audioTheme") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ $t(`audioTheme_${currentAudioTheme}`) }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("audioTheme") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
<span>{{ $t("audioThemeDisclaimer") }}</span>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<div class="label-columns">
|
||||
<div class="settings-label">
|
||||
{{ $t("enableAudio") }}
|
||||
</div>
|
||||
<div>
|
||||
<toggle-switch
|
||||
:checked="!isDisabled"
|
||||
@change="toggleAudioThemeOff($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label-columns mb-2">
|
||||
<div class="settings-label">
|
||||
{{ $t("audioTheme") }}
|
||||
</div>
|
||||
<div v-if="!isDisabled">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="playAudio()"
|
||||
>
|
||||
{{ $t('playDemoAudio') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<select-list
|
||||
:disabled="isDisabled"
|
||||
:items="availableAudioThemes"
|
||||
:value="themeSelected"
|
||||
@select="changeAudioThemeTemporary($event)"
|
||||
>
|
||||
<template #item="{ item, button }">
|
||||
<span v-if="button">
|
||||
{{ $t(`audioTheme_${themeSelected}`) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t(`audioTheme_${item}`) }}
|
||||
</span>
|
||||
</template>
|
||||
</select-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="previousValue === currentAudioTheme"
|
||||
@saveClicked="changeAudioThemeAndClose()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.label-columns {
|
||||
display: flex;
|
||||
|
||||
&:first-of-type {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
div:first-of-type {
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SelectList from '@/components/ui/selectList';
|
||||
import { GenericUserPreferencesMixin } from '../components/genericUserPreferencesMixin';
|
||||
import sounds from '@/libs/sounds';
|
||||
import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
|
||||
|
||||
export default {
|
||||
components: { ToggleSwitch, SelectList, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, GenericUserPreferencesMixin],
|
||||
data () {
|
||||
return {
|
||||
soundIndex: 0,
|
||||
previousValue: '',
|
||||
// using the user.preferences didn't update the select-list values from off state
|
||||
themeSelected: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
availableLanguages: 'i18n.availableLanguages',
|
||||
content: 'content',
|
||||
}),
|
||||
availableAudioThemes () {
|
||||
return this.content.audioThemes;
|
||||
},
|
||||
currentAudioTheme () {
|
||||
return this.user.preferences.sound;
|
||||
},
|
||||
isDisabled () {
|
||||
return this.currentAudioTheme === 'off';
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.previousValue = this.currentAudioTheme;
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* is a callback from the {InlineSettingMixin}
|
||||
* do not remove
|
||||
*/
|
||||
resetControls () {
|
||||
this.changeAudioThemeTemporary(this.previousValue);
|
||||
},
|
||||
changeAudioThemeTemporary ($event) {
|
||||
this.user.preferences.sound = $event;
|
||||
this.themeSelected = $event;
|
||||
this.soundIndex = 0;
|
||||
},
|
||||
changeAudioThemeAndClose () {
|
||||
this.setUserPreference('sound');
|
||||
this.previousValue = this.user.preferences.sound;
|
||||
this.closeModal();
|
||||
},
|
||||
playAudio () {
|
||||
this.$root.$emit('playSound', sounds[this.soundIndex]);
|
||||
this.soundIndex = (this.soundIndex + 1) % sounds.length;
|
||||
},
|
||||
toggleAudioThemeOff (enabled) {
|
||||
if (enabled) {
|
||||
const [audioTheme] = this.availableAudioThemes;
|
||||
|
||||
this.changeAudioThemeTemporary(audioTheme);
|
||||
} else {
|
||||
this.changeAudioThemeTemporary('off');
|
||||
}
|
||||
|
||||
this.modalValuesChanged();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
252
website/client/src/pages/settings/settingRows/classSetting.vue
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
<template>
|
||||
<fragment v-if="allowedToChangeClass">
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("changeClassSetting") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
<class-icon-label
|
||||
:selected-class="selectedClass"
|
||||
:class-disabled="classDisabled"
|
||||
/>
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="showRealModalOrInline()"
|
||||
>
|
||||
{{ $t(classDisabled ? 'chooseClassSetting' : 'edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("changeClassSetting") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
<span>{{ $t("changeClassDisclaimer") }}</span>
|
||||
</div>
|
||||
<div class="content-centered">
|
||||
<div class="current-class mt-3">
|
||||
<span class="label">{{ $t('currentClass') }}:</span>
|
||||
<class-icon-label
|
||||
:selected-class="selectedClass"
|
||||
:class-disabled="classDisabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<gem-price
|
||||
gem-price="3"
|
||||
icon-size="24"
|
||||
class="gem-price-spacing"
|
||||
:with-background="true"
|
||||
/>
|
||||
|
||||
<save-cancel-buttons
|
||||
primary-button-label="changeClassSetting"
|
||||
class="mb-2"
|
||||
:no-padding="true"
|
||||
:disable-save="!enoughGemsAvailable"
|
||||
@saveClicked="changeClassAndClose()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
|
||||
<your-balance
|
||||
:amount-needed="amountNeeded"
|
||||
currency-needed="gems"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.label-columns {
|
||||
display: flex;
|
||||
|
||||
&:first-of-type {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
div:first-of-type {
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
|
||||
.content-centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gem-price-spacing {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.class-selection {
|
||||
display: flex;
|
||||
gap: 22px;
|
||||
justify-content: center;
|
||||
|
||||
margin-bottom: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
line-height: 1.71;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selected-badge {
|
||||
position: absolute;
|
||||
bottom: -1rem;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
padding: 4px;
|
||||
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
|
||||
background-color: $green-50;
|
||||
border-radius: 1rem;
|
||||
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.current-class {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.label {
|
||||
margin-right: 0.5rem;
|
||||
|
||||
font-weight: bold;
|
||||
color: $gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapGetters, mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import { GenericUserPreferencesMixin } from '../components/genericUserPreferencesMixin';
|
||||
import YourBalance from '@/pages/settings/components/yourBalance.vue';
|
||||
import GemPrice from '@/components/shops/gemPrice.vue';
|
||||
import checkIcon from '@/assets/svg/check.svg';
|
||||
import changeClass from '@/../../common/script/ops/changeClass';
|
||||
import ClassIconLabel from '@/pages/settings/components/classIconLabel.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ClassIconLabel,
|
||||
GemPrice,
|
||||
YourBalance,
|
||||
SaveCancelButtons,
|
||||
},
|
||||
mixins: [InlineSettingMixin, GenericUserPreferencesMixin],
|
||||
data () {
|
||||
return {
|
||||
amountNeeded: 3 / 4,
|
||||
selectedClass: '',
|
||||
icons: Object.freeze({
|
||||
check: checkIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
userGems: 'user:gems',
|
||||
}),
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
availableLanguages: 'i18n.availableLanguages',
|
||||
content: 'content',
|
||||
}),
|
||||
classList () {
|
||||
return this.content.classes;
|
||||
},
|
||||
allowedToChangeClass () {
|
||||
return this.user.stats.lvl >= 10;
|
||||
},
|
||||
enoughGemsAvailable () {
|
||||
return this.amountNeeded <= this.userGems;
|
||||
},
|
||||
classDisabled () {
|
||||
return this.user.preferences.disableClasses;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.selectedClass = this.user.stats.class;
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
showRealModalOrInline () {
|
||||
if (!this.classDisabled) {
|
||||
this.openModal();
|
||||
} else {
|
||||
this.changeClassAndClose();
|
||||
}
|
||||
},
|
||||
async changeClassAndClose () {
|
||||
if (!this.classDisabled && !window.confirm(this.$t('changeClassConfirmCost'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$once('bv::hide::modal', () => {
|
||||
// update the label in the settings list
|
||||
this.selectedClass = this.user.stats.class;
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
// resets the class settings and triggers indirectly the modal of
|
||||
// src/components/achievemnts/chooseClass - I don't know if we should keep this weird way
|
||||
changeClass(this.user),
|
||||
axios.post('/api/v4/user/change-class'),
|
||||
]);
|
||||
} catch (e) {
|
||||
window.alert(e.message); // eslint-disable-line no-alert
|
||||
}
|
||||
|
||||
this.closeModal();
|
||||
},
|
||||
/**
|
||||
* is a callback from the {InlineSettingMixin}
|
||||
* do not remove
|
||||
*/
|
||||
resetControls () {
|
||||
this.selectedClass = this.user.stats.class;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("dateFormat") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ currentActiveFormat }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("dateFormat") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
<span>{{ $t("dateFormatDisclaimer") }}</span>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("dateFormat") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select-list
|
||||
:items="availableFormats"
|
||||
:value="selectedFormat"
|
||||
@select="changeFormat($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="selectedFormat === currentActiveFormat"
|
||||
@saveClicked="changeFormatAndClose()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SelectList from '@/components/ui/selectList';
|
||||
import { GenericUserPreferencesMixin } from '../components/genericUserPreferencesMixin';
|
||||
|
||||
export default {
|
||||
components: { SelectList, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, GenericUserPreferencesMixin],
|
||||
data () {
|
||||
return {
|
||||
selectedFormat: '',
|
||||
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
currentActiveFormat () {
|
||||
return this.user.preferences.dateFormat;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
changeFormat (e) {
|
||||
this.selectedFormat = e;
|
||||
this.modalValuesChanged();
|
||||
},
|
||||
async changeFormatAndClose () {
|
||||
this.user.preferences.dateFormat = this.selectedFormat;
|
||||
await this.setUserPreference('dateFormat');
|
||||
this.closeModal();
|
||||
},
|
||||
/**
|
||||
* is a callback from the {InlineSettingMixin}
|
||||
* do not remove
|
||||
*/
|
||||
resetControls () {
|
||||
this.selectedFormat = this.currentActiveFormat;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("dayStartAdjustment") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ selectedDayStartLabel(user.preferences.dayStart) }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("dayStartAdjustment") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
v-html="$t('customDayStartInfo1')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("adjustment") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select-list
|
||||
:items="dayStartOptions"
|
||||
:value="newDayStart"
|
||||
key-prop="value"
|
||||
active-key-prop="value"
|
||||
:hide-icon="false"
|
||||
@select="changeDayStart($event)"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<span v-if="item === newDayStart || (!item && newDayStart === 0)">
|
||||
{{ selectedDayStartLabel(newDayStart) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ item?.name }}
|
||||
</span>
|
||||
</template>
|
||||
</select-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small
|
||||
class="timezone-explain"
|
||||
>
|
||||
<p v-html="$t('timezoneUTC', {utc: timezoneOffsetToUtc})"></p>
|
||||
<p v-html="$t('timezoneInfo')"></p>
|
||||
</small>
|
||||
|
||||
<div class="input-area">
|
||||
<save-cancel-buttons
|
||||
:disable-save="newDayStart === user.preferences.dayStart"
|
||||
@saveClicked="saveDayStart()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.timezone-explain {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
|
||||
color: $gray-100;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment/moment';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import SelectList from '@/components/ui/selectList.vue';
|
||||
import getUtcOffset from '../../../../../common/script/fns/getUtcOffset';
|
||||
|
||||
export default {
|
||||
components: { SelectList, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
const dayStartOptions = [];
|
||||
for (let number = 0; number <= 12; number += 1) {
|
||||
const meridian = number < 12 ? 'AM' : 'PM';
|
||||
const hour = number % 12;
|
||||
const timeWithMeridian = `(${hour || 12}:00 ${meridian})`;
|
||||
const option = {
|
||||
value: number,
|
||||
name: `+${number} hours ${timeWithMeridian}`,
|
||||
};
|
||||
|
||||
if (number === 0) {
|
||||
option.name = `Default ${timeWithMeridian}`;
|
||||
}
|
||||
|
||||
dayStartOptions.push(option);
|
||||
}
|
||||
|
||||
return {
|
||||
newDayStart: 0,
|
||||
dayStartOptions,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.newDayStart = this.user.preferences.dayStart;
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
timezoneOffsetToUtc () {
|
||||
const offsetString = moment().utcOffset(getUtcOffset(this.user)).format('Z');
|
||||
return `UTC${offsetString}`;
|
||||
},
|
||||
dayStart () {
|
||||
return this.user.preferences.dayStart;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeDayStart ($event) {
|
||||
this.newDayStart = $event.value;
|
||||
},
|
||||
async saveDayStart () {
|
||||
this.user.preferences.dayStart = this.newDayStart;
|
||||
await axios.post('/api/v4/user/custom-day-start', {
|
||||
dayStart: this.newDayStart,
|
||||
});
|
||||
|
||||
this.closeModal();
|
||||
},
|
||||
selectedDayStartLabel (dayStartValue) {
|
||||
if (!this.dayStartOptions) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.dayStartOptions.find(l => l.value === dayStartValue)?.name ?? '';
|
||||
},
|
||||
calculateNextCron () {
|
||||
let nextCron = moment()
|
||||
.hours(this.newDayStart)
|
||||
.minutes(0)
|
||||
.seconds(0)
|
||||
.milliseconds(0);
|
||||
|
||||
const currentHour = moment().format('H');
|
||||
if (currentHour >= this.newDayStart) {
|
||||
nextCron = nextCron.add(1, 'day');
|
||||
}
|
||||
|
||||
return nextCron.format(`${this.user.preferences.dateFormat.toUpperCase()} @ h:mm a`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
153
website/client/src/pages/settings/settingRows/deleteAccount.vue
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("deleteAccount") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('learnMore') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title danger"
|
||||
>
|
||||
{{ $t("deleteAccount") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
v-html="hasPassword
|
||||
? $t('deleteLocalAccountText')
|
||||
: $t('deleteSocialAccountText', {magicWord: 'DELETE'})"
|
||||
>
|
||||
</div>
|
||||
|
||||
<current-password-input
|
||||
v-if="hasPassword"
|
||||
:show-forget-password="true"
|
||||
:is-valid="mixinData.currentPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.currentPasswordIssues"
|
||||
@passwordValue="passwordValue = $event"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="input-area"
|
||||
>
|
||||
<div
|
||||
class="form"
|
||||
>
|
||||
<div class="settings-label">
|
||||
{{ $t("confirm") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="passwordValue"
|
||||
class="form-control"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-once
|
||||
class="feedback"
|
||||
v-html="$t('feedback')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="input-area"
|
||||
>
|
||||
<textarea
|
||||
id="feedbackTextArea"
|
||||
v-model="feedback"
|
||||
:placeholder="$t('feedbackPlaceholder')"
|
||||
class="form-control"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<save-cancel-buttons
|
||||
:disable-save="!enableDelete"
|
||||
primary-button-color="btn-danger"
|
||||
primary-button-label="deleteAccount"
|
||||
@saveClicked="deleteAccount()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.feedback {
|
||||
color: $gray-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
import { PasswordInputChecksMixin } from '@/mixins/passwordInputChecks';
|
||||
|
||||
|
||||
export default {
|
||||
components: { CurrentPasswordInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, PasswordInputChecksMixin],
|
||||
data () {
|
||||
return {
|
||||
passwordValue: '',
|
||||
feedback: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
hasPassword () {
|
||||
return this.user.auth.local.has_password;
|
||||
},
|
||||
enableDelete () {
|
||||
return this.hasPassword ? Boolean(this.passwordValue) : this.passwordValue === 'DELETE';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async deleteAccount () {
|
||||
await this.passwordInputCheckMixinTryCall(async () => {
|
||||
await axios.delete('/api/v4/user', {
|
||||
data: {
|
||||
password: this.passwordValue,
|
||||
feedback: this.feedback,
|
||||
},
|
||||
});
|
||||
localStorage.clear();
|
||||
window.location.href = '/static/home';
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("displayName") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ user.profile.name }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("displayName") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
{{ $t("changeDisplayNameDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("displayName") }}
|
||||
</div>
|
||||
<div
|
||||
class="form"
|
||||
name="changeDisplayName"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changeDisplayname"
|
||||
v-model="temporaryDisplayName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newDisplayName')"
|
||||
:class="{'is-invalid input-invalid': displayNameInvalid}"
|
||||
@keyup="valuesChanged()"
|
||||
>
|
||||
<div
|
||||
v-if="displayNameIssues.length > 0"
|
||||
class="mb-3"
|
||||
>
|
||||
<div
|
||||
v-for="issue in displayNameIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="displayNameCannotSubmit"
|
||||
@saveClicked="changeDisplayName(temporaryDisplayName)"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.input-floating-checkmark {
|
||||
position: absolute;
|
||||
background: none !important;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-group.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
.input-group:not(.is-valid) {
|
||||
.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 12px;
|
||||
height: 10px;
|
||||
color: $green-50;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import * as validator from 'validator';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import checkIcon from '@/assets/svg/check.svg';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import NotificationMixins from '@/mixins/notifications';
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, NotificationMixins],
|
||||
data () {
|
||||
return {
|
||||
temporaryDisplayName: '',
|
||||
inputChanged: false,
|
||||
displayNameIssues: [],
|
||||
updates: {
|
||||
newEmail: '',
|
||||
password: '',
|
||||
},
|
||||
icons: Object.freeze({
|
||||
checkIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
validEmail () {
|
||||
return validator.isEmail(this.updates.newEmail);
|
||||
},
|
||||
allowedToSave () {
|
||||
return !this.validEmail || this.updates.password.length === 0;
|
||||
},
|
||||
displayNameInvalid () {
|
||||
if (this.temporaryDisplayName.length <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.displayNameIssues.length !== 0;
|
||||
},
|
||||
displayNameCannotSubmit () {
|
||||
return this.displayNameInvalid || !this.inputChanged;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
temporaryDisplayName: {
|
||||
handler () {
|
||||
this.validateDisplayName(this.temporaryDisplayName);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* is a callback from the {InlineSettingMixin}
|
||||
* do not remove
|
||||
*/
|
||||
resetControls () {
|
||||
this.temporaryDisplayName = this.user.profile.name;
|
||||
},
|
||||
async changeDisplayName (newName) {
|
||||
await axios.put('/api/v4/user/', { 'profile.name': newName });
|
||||
this.text(this.$t('displayNameSuccess'));
|
||||
this.user.profile.name = newName;
|
||||
this.temporaryDisplayName = newName;
|
||||
|
||||
this.closeModal();
|
||||
},
|
||||
validateDisplayName: debounce(async function checkName (displayName) {
|
||||
if (displayName.length <= 1 || displayName === this.user.profile.name) {
|
||||
this.displayNameIssues = [];
|
||||
return;
|
||||
}
|
||||
const res = await this.$store.dispatch('auth:verifyDisplayName', {
|
||||
displayName,
|
||||
});
|
||||
|
||||
if (res.issues !== undefined) {
|
||||
this.displayNameIssues = res.issues;
|
||||
} else {
|
||||
this.displayNameIssues = [];
|
||||
}
|
||||
}, 500),
|
||||
valuesChanged () {
|
||||
this.inputChanged = true;
|
||||
this.modalValuesChanged();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("fixValues") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td
|
||||
colspan="3"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("fixValues") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
<span v-html="$t('fixValuesText1')"></span>
|
||||
<br>
|
||||
<br>
|
||||
<span v-html="$t('fixValuesText2')"></span>
|
||||
</div>
|
||||
|
||||
<div class="content-centered">
|
||||
<div class="input-rows row">
|
||||
<div
|
||||
v-for="input in inputList"
|
||||
:key="input.property"
|
||||
class="col-4"
|
||||
>
|
||||
<div class="fix-value-group mt-3">
|
||||
<span class="fix-label">
|
||||
{{ $t(input.translationKey) }}
|
||||
</span>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend positive-addon input-group-icon">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon icon-16"
|
||||
:class="{[input.translationKey]: true}"
|
||||
v-html="input.icon"
|
||||
></div>
|
||||
</div>
|
||||
<input
|
||||
v-model="restoreValues[input.property]"
|
||||
class="form-control"
|
||||
type="number"
|
||||
min="0"
|
||||
required="required"
|
||||
@keydown="markAsChanged(input, $event)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="!mixinData.inlineSettingMixin.sharedState.inlineSettingUnsavedValues"
|
||||
class="mt-4"
|
||||
@saveClicked="save()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.input-rows {
|
||||
width: calc(600px + 1.5rem);
|
||||
}
|
||||
|
||||
.content-centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fix-label {
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.svg-icon.icon-16 {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield !important;
|
||||
|
||||
&::-webkit-inner-spin-button,
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon.level {
|
||||
color: $gray-200;
|
||||
|
||||
:global svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// import clone from 'lodash/clone';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import healthIcon from '@/assets/svg/health.svg';
|
||||
import experienceIcon from '@/assets/svg/experience.svg';
|
||||
import manaIcon from '@/assets/svg/mana.svg';
|
||||
import svgGold from '@/assets/svg/gold.svg';
|
||||
import level from '@/assets/svg/level.svg';
|
||||
import streakIcon from '@/assets/svg/streak.svg';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { MAX_LEVEL_HARD_CAP } from '../../../../../common/script/constants';
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {
|
||||
restoreValues: {
|
||||
hp: 0,
|
||||
mp: 0,
|
||||
gp: 0,
|
||||
exp: 0,
|
||||
lvl: 0,
|
||||
streak: 0,
|
||||
},
|
||||
icons: Object.freeze({
|
||||
health: healthIcon,
|
||||
experience: experienceIcon,
|
||||
mana: manaIcon,
|
||||
gold: svgGold,
|
||||
level,
|
||||
streak: streakIcon,
|
||||
}),
|
||||
inputList: Object.freeze([
|
||||
{
|
||||
translationKey: 'health',
|
||||
icon: healthIcon,
|
||||
property: 'hp',
|
||||
},
|
||||
{
|
||||
translationKey: 'experience',
|
||||
icon: experienceIcon,
|
||||
property: 'exp',
|
||||
}, {
|
||||
translationKey: 'mana',
|
||||
icon: manaIcon,
|
||||
property: 'mp',
|
||||
}, {
|
||||
translationKey: 'gold',
|
||||
icon: svgGold,
|
||||
property: 'gp',
|
||||
},
|
||||
{
|
||||
translationKey: 'level',
|
||||
icon: level,
|
||||
property: 'lvl',
|
||||
},
|
||||
{
|
||||
translationKey: 'fix21Streaks',
|
||||
icon: streakIcon,
|
||||
property: 'streak',
|
||||
},
|
||||
]),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
mounted () {
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
resetControls () {
|
||||
const {
|
||||
hp, mp, gp, exp, lvl,
|
||||
} = this.user.stats;
|
||||
|
||||
this.restoreValues = {
|
||||
hp, mp, gp, exp, lvl, streak: this.user.achievements.streak,
|
||||
};
|
||||
},
|
||||
close () {
|
||||
this.validateInputs();
|
||||
},
|
||||
markAsChanged (inputType, keyupEvent) {
|
||||
this.restoreValues[inputType.property] = keyupEvent.target.value;
|
||||
this.modalValuesChanged();
|
||||
},
|
||||
save () {
|
||||
if (!this.validateInputs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.restoreValues.lvl > MAX_LEVEL_HARD_CAP) {
|
||||
this.restoreValues.lvl = MAX_LEVEL_HARD_CAP;
|
||||
}
|
||||
|
||||
const userChangedLevel = this.restoreValues.lvl !== this.user.stats.lvl;
|
||||
const userDidNotChangeExp = this.restoreValues.exp === this.user.stats.exp;
|
||||
if (userChangedLevel && userDidNotChangeExp) {
|
||||
this.restoreValues.exp = 0;
|
||||
}
|
||||
|
||||
const settings = {
|
||||
'stats.hp': Number(this.restoreValues.hp),
|
||||
'stats.exp': Number(this.restoreValues.exp),
|
||||
'stats.gp': Number(this.restoreValues.gp),
|
||||
'stats.lvl': Number(this.restoreValues.lvl),
|
||||
'stats.mp': Number(this.restoreValues.mp),
|
||||
'achievements.streak': Number(this.restoreValues.streak),
|
||||
};
|
||||
|
||||
this.$store.dispatch('user:set', settings);
|
||||
|
||||
this.wasChanged = false;
|
||||
this.closeModal();
|
||||
},
|
||||
validateInputs () {
|
||||
const canRestore = ['hp', 'exp', 'gp', 'mp'];
|
||||
let valid = true;
|
||||
|
||||
for (const stat of canRestore) {
|
||||
if (this.restoreValues[stat] === ''
|
||||
|| this.restoreValues[stat] < 0
|
||||
) {
|
||||
this.restoreValues[stat] = this.user.stats[stat];
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
const inputLevel = Number(this.restoreValues.lvl);
|
||||
if (this.restoreValues.lvl === ''
|
||||
|| !Number.isInteger(inputLevel)
|
||||
|| inputLevel < 1) {
|
||||
this.restoreValues.lvl = this.user.stats.lvl;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
const inputStreak = Number(this.restoreValues.streak);
|
||||
if (this.restoreValues.streak === ''
|
||||
|| !Number.isInteger(inputStreak)
|
||||
|| inputStreak < 0) {
|
||||
this.restoreValues.streak = this.user.achievements.streak;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr>
|
||||
<td class="settings-label">
|
||||
{{ $t("showHeader") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<toggle-switch
|
||||
v-model="user.preferences.showHeader"
|
||||
@change="setUserPreference('showHeader')"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="settings-label">
|
||||
{{ $t("stickyHeader") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<toggle-switch
|
||||
v-model="user.preferences.stickyHeader"
|
||||
@change="setUserPreference('stickyHeader')"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep {
|
||||
.toggle-switch-outer {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
|
||||
import { GenericUserPreferencesMixin } from '../components/genericUserPreferencesMixin';
|
||||
|
||||
|
||||
export default {
|
||||
components: { ToggleSwitch },
|
||||
mixins: [InlineSettingMixin, GenericUserPreferencesMixin],
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("language") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ currentLanguageLabel }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("language") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
<span>{{ $t("americanEnglishGovern") }} </span>
|
||||
<span v-html="$t('helpWithTranslation')"></span>
|
||||
</div>
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("siteLanguage") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select-list
|
||||
:items="availableLanguages"
|
||||
:value="selectedLanguage"
|
||||
key-prop="code"
|
||||
active-key-prop="code"
|
||||
@select="changeLanguage($event)"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<span v-if="item === selectedLanguage">
|
||||
{{ selectedLanguageLabel(selectedLanguage) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</template>
|
||||
</select-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="selectedLanguage === currentActiveLanguage"
|
||||
@saveClicked="changeLanguageAndClose()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SelectList from '@/components/ui/selectList';
|
||||
import { GenericUserPreferencesMixin } from '../components/genericUserPreferencesMixin';
|
||||
|
||||
export default {
|
||||
components: { SelectList, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, GenericUserPreferencesMixin],
|
||||
data () {
|
||||
return {
|
||||
selectedLanguage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
availableLanguages: 'i18n.availableLanguages',
|
||||
}),
|
||||
currentActiveLanguage () {
|
||||
return this.user.preferences.language;
|
||||
},
|
||||
currentLanguageLabel () {
|
||||
return this.selectedLanguageLabel(this.selectedLanguage);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* is a callback from the {InlineSettingMixin}
|
||||
* do not remove
|
||||
*/
|
||||
resetControls () {
|
||||
this.selectedLanguage = this.currentActiveLanguage;
|
||||
},
|
||||
changeLanguage (e) {
|
||||
const newLang = e.code;
|
||||
this.selectedLanguage = newLang;
|
||||
|
||||
this.modalValuesChanged();
|
||||
},
|
||||
selectedLanguageLabel (languageKey) {
|
||||
if (!this.availableLanguages) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.availableLanguages.find(l => l.code === languageKey)?.name ?? '';
|
||||
},
|
||||
async changeLanguageAndClose () {
|
||||
this.user.preferences.language = this.selectedLanguage;
|
||||
await this.setUserPreference('language');
|
||||
setTimeout(() => window.location.reload(true));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
185
website/client/src/pages/settings/settingRows/loginMethods.vue
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-for="network in SOCIAL_AUTH_NETWORKS"
|
||||
:key="network.key"
|
||||
>
|
||||
<td class="settings-label">
|
||||
<div class="network-icon-with-label">
|
||||
<span
|
||||
:class="'svg-icon icon-16 social-icon ' + network.key"
|
||||
v-html="icons[network.key]"
|
||||
></span>
|
||||
|
||||
<span class="ml-75"> {{ network.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
<div
|
||||
v-if="isConnected(network.key)"
|
||||
class="connected-pill"
|
||||
>
|
||||
{{ $t('connected') }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
v-if="allowedToConnect(network.key)"
|
||||
class="edit-link"
|
||||
@click.prevent="socialAuth(network.key, user)"
|
||||
>
|
||||
{{ $t('connect') }}
|
||||
</a>
|
||||
<a
|
||||
v-if="allowedToRemove(network.key)"
|
||||
class="remove-link"
|
||||
@click.prevent="deleteSocialAuth(network)"
|
||||
>
|
||||
{{ $t('remove') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import hello from 'hellojs';
|
||||
import { buildAppleAuthUrl } from '@/libs/auth';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { SUPPORTED_SOCIAL_NETWORKS } from '../../../../../common/script/constants';
|
||||
import googleIcon from '@/assets/svg/google.svg';
|
||||
import appleIcon from '@/assets/svg/apple_black.svg';
|
||||
|
||||
export default {
|
||||
name: 'LoginMethods',
|
||||
data () {
|
||||
return {
|
||||
SOCIAL_AUTH_NETWORKS: [],
|
||||
// Made available by the server as a script
|
||||
localAuth: {
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
icons: Object.freeze({
|
||||
google: googleIcon,
|
||||
apple: appleIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
content: 'content',
|
||||
}),
|
||||
},
|
||||
mounted () {
|
||||
this.SOCIAL_AUTH_NETWORKS = SUPPORTED_SOCIAL_NETWORKS;
|
||||
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
});
|
||||
|
||||
hello.init({
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line no-process-env
|
||||
}, {
|
||||
redirect_uri: '', // eslint-disable-line
|
||||
});
|
||||
|
||||
const focusID = this.$route.query.focus;
|
||||
if (focusID !== undefined && focusID !== null) {
|
||||
this.$nextTick(() => {
|
||||
const element = document.getElementById(focusID);
|
||||
if (element !== undefined && element !== null) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async deleteSocialAuth (network) {
|
||||
await axios.delete(`/api/v4/user/auth/social/${network.key}`);
|
||||
this.user.auth[network.key] = {};
|
||||
this.text(this.$t('detachedSocial', { network: network.name }));
|
||||
},
|
||||
async socialAuth (network) {
|
||||
if (network === 'apple') {
|
||||
window.location.href = buildAppleAuthUrl();
|
||||
} else {
|
||||
const auth = await hello(network).login({ scope: 'email' });
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
auth,
|
||||
});
|
||||
window.location.href = '/';
|
||||
}
|
||||
},
|
||||
hasBackupAuthOption (networkKeyToCheck) {
|
||||
if (this.user.auth.local.username && this.user.auth.local.has_password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.SOCIAL_AUTH_NETWORKS.find(network => {
|
||||
if (network.key !== networkKeyToCheck) {
|
||||
if (this.user.auth[network.key]) {
|
||||
return !!this.user.auth[network.key].id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
isConnected (networkKeyToCheck) {
|
||||
return !!this.user.auth[networkKeyToCheck].id;
|
||||
},
|
||||
allowedToConnect (networkKeyToCheck) {
|
||||
if (networkKeyToCheck === 'facebook') {
|
||||
return false; // is still needed? the list of networks doesn't have facebook
|
||||
}
|
||||
|
||||
const isConnected = this.isConnected(networkKeyToCheck);
|
||||
|
||||
return !isConnected;
|
||||
},
|
||||
allowedToRemove (networkKeyToCheck) {
|
||||
const isConnected = this.isConnected(networkKeyToCheck);
|
||||
|
||||
return isConnected && this.hasBackupAuthOption(networkKeyToCheck);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.icon-16 ::v-deep svg {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.network-icon-with-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
span:not(.svg-icon) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.connected-pill {
|
||||
display: inline-block;
|
||||
|
||||
padding: 4px 12px;
|
||||
border-radius: 100px;
|
||||
background-color: $green-50;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.social-icon.apple {
|
||||
margin-bottom: -2px !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("password") }}
|
||||
</td>
|
||||
<td class="settings-value"></td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t(hasPassword ? 'edit' : 'add') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td
|
||||
colspan="3"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("password") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
{{ $t("changePasswordDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<current-password-input
|
||||
v-if="hasPassword"
|
||||
:show-forget-password="true"
|
||||
custom-label="currentPass"
|
||||
:is-valid="mixinData.currentPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.currentPasswordIssues"
|
||||
|
||||
@passwordValue="passwordUpdates.password = $event"
|
||||
/>
|
||||
<current-password-input
|
||||
custom-label="newPass"
|
||||
:is-valid="mixinData.newPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.newPasswordIssues"
|
||||
@passwordValue="passwordUpdates.newPassword = $event"
|
||||
/>
|
||||
<current-password-input
|
||||
custom-label="confirmPass"
|
||||
:is-valid="mixinData.confirmPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.confirmPasswordIssues"
|
||||
@passwordValue="passwordUpdates.confirmPassword = $event"
|
||||
/>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="inputsInvalid"
|
||||
@saveClicked="hasPassword ? changePassword() : addLocalAuth()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.input-floating-checkmark {
|
||||
position: absolute;
|
||||
background: none !important;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-group.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
.input-group:not(.is-valid) {
|
||||
.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 12px;
|
||||
height: 10px;
|
||||
color: $green-50;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
import { mapState } from '@/libs/store';
|
||||
import { PasswordInputChecksMixin } from '@/mixins/passwordInputChecks';
|
||||
|
||||
export default {
|
||||
components: { CurrentPasswordInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, PasswordInputChecksMixin],
|
||||
data () {
|
||||
return {
|
||||
passwordUpdates: {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
hasPassword () {
|
||||
return this.user.auth.local.has_password;
|
||||
},
|
||||
inputsInvalid () {
|
||||
if (this.hasPassword && !this.passwordUpdates.password) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.passwordUpdates.newPassword !== this.passwordUpdates.confirmPassword;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async changePassword () {
|
||||
await this.passwordInputCheckMixinTryCall(async () => {
|
||||
const localAuthData = {
|
||||
password: this.passwordUpdates.password,
|
||||
newPassword: this.passwordUpdates.newPassword,
|
||||
confirmPassword: this.passwordUpdates.confirmPassword,
|
||||
};
|
||||
|
||||
await axios.put('/api/v4/user/auth/update-password', localAuthData);
|
||||
|
||||
this.passwordUpdates = {};
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: this.$t('passwordSuccess'),
|
||||
type: 'success',
|
||||
timeout: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async addLocalAuth () {
|
||||
await this.passwordInputCheckMixinTryCall(async () => {
|
||||
const localAuthData = {
|
||||
password: this.passwordUpdates.newPassword,
|
||||
confirmPassword: this.passwordUpdates.confirmPassword,
|
||||
email: this.user.auth.local.email,
|
||||
username: this.user.auth.local.username,
|
||||
};
|
||||
|
||||
await axios.post('/api/v4/user/auth/local/register', localAuthData);
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
135
website/client/src/pages/settings/settingRows/resetAccount.vue
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("resetAccount") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
v-if="!!user?.auth?.local?.username"
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('learnMore') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title danger"
|
||||
>
|
||||
{{ $t("resetAccount") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
v-html="$t('resetText1')"
|
||||
>
|
||||
</div>
|
||||
<div class="split-lists my-3 ">
|
||||
<ul>
|
||||
<li
|
||||
v-once
|
||||
>
|
||||
{{ $t('resetDetail1') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('resetDetail3') }}
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li v-once>
|
||||
{{ $t('resetDetail2') }}
|
||||
</li>
|
||||
|
||||
<li v-once>
|
||||
{{ $t('resetDetail4') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-once
|
||||
v-html="$t('resetText2')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<current-password-input
|
||||
:show-forget-password="true"
|
||||
:is-valid="mixinData.currentPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.currentPasswordIssues"
|
||||
@passwordValue="passwordValue = $event"
|
||||
/>
|
||||
<save-cancel-buttons
|
||||
:disable-save="passwordValue === ''"
|
||||
primary-button-color="btn-danger"
|
||||
primary-button-label="resetAccount"
|
||||
@saveClicked="reset()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.split-lists {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: $gray-50;
|
||||
|
||||
ul {
|
||||
flex: 0 0 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
import { PasswordInputChecksMixin } from '@/mixins/passwordInputChecks';
|
||||
|
||||
|
||||
export default {
|
||||
components: { CurrentPasswordInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, PasswordInputChecksMixin],
|
||||
data () {
|
||||
return {
|
||||
passwordValue: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
async reset () {
|
||||
await this.passwordInputCheckMixinTryCall(async () => {
|
||||
await axios.post('/api/v4/user/reset', {
|
||||
password: this.passwordValue,
|
||||
});
|
||||
this.$router.push('/');
|
||||
setTimeout(() => window.location.reload(true), 100);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
95
website/client/src/pages/settings/settingRows/sleepMode.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("pauseDailies") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('learnMore') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("pauseDailies") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
v-html="$t('sleepDescription')"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
||||
<ul>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet1') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet2') }}
|
||||
</li>
|
||||
<li v-once>
|
||||
{{ $t('sleepBullet3') }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="input-area">
|
||||
<save-cancel-buttons
|
||||
:primary-button-label="user.preferences.sleep ? 'unpauseDailies' : 'pauseDailies'"
|
||||
@saveClicked="toggleSleep(); requestCloseModal();"
|
||||
@cancelClicked="requestCloseModal();"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.feedback {
|
||||
color: $gray-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
toggleSleep () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("email") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ user?.auth?.local?.email }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("email") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
{{ $t("changeEmailDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<validated-text-input
|
||||
v-model="updates.newEmail"
|
||||
settings-label="email"
|
||||
:is-valid="validEmail"
|
||||
@update:value="modalValuesChanged"
|
||||
@blur="restoreEmptyEmail()"
|
||||
/>
|
||||
|
||||
<current-password-input
|
||||
:show-forget-password="true"
|
||||
:is-valid="mixinData.currentPasswordIssues.length === 0"
|
||||
:invalid-issues="mixinData.currentPasswordIssues"
|
||||
@passwordValue="updates.password = $event"
|
||||
/>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="disallowedToSave"
|
||||
@saveClicked="changeEmail()"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import * as validator from 'validator';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
|
||||
import NotificationMixins from '@/mixins/notifications';
|
||||
import { PasswordInputChecksMixin } from '@/mixins/passwordInputChecks';
|
||||
|
||||
export default {
|
||||
components: { ValidatedTextInput, CurrentPasswordInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, NotificationMixins, PasswordInputChecksMixin],
|
||||
data () {
|
||||
return {
|
||||
updates: {
|
||||
newEmail: '',
|
||||
password: '',
|
||||
},
|
||||
|
||||
previousEmail: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
emailChanged () {
|
||||
return this.previousEmail !== this.updates.newEmail;
|
||||
},
|
||||
validEmail () {
|
||||
return validator.isEmail(this.updates.newEmail);
|
||||
},
|
||||
disallowedToSave () {
|
||||
return !this.emailChanged
|
||||
|| !this.validEmail
|
||||
|| this.updates.password.length === 0;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.restoreEmptyEmail();
|
||||
},
|
||||
methods: {
|
||||
resetControls () {
|
||||
this.restoreEmail();
|
||||
},
|
||||
restoreEmptyEmail () {
|
||||
if (this.updates.newEmail.length < 1) {
|
||||
this.restoreEmail();
|
||||
}
|
||||
},
|
||||
restoreEmail () {
|
||||
this.updates.newEmail = this.user.auth.local.email;
|
||||
this.previousEmail = this.user.auth.local.email;
|
||||
},
|
||||
async changeEmail () {
|
||||
await this.passwordInputCheckMixinTryCall(async () => {
|
||||
await axios.put('/api/v4/user/auth/update-email', this.updates);
|
||||
this.user.auth.local.email = this.updates.newEmail;
|
||||
this.text(this.$t('emailSuccess'));
|
||||
this.closeModal();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("username") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ user?.auth?.local?.username }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t(user?.auth?.local?.username ? 'edit' : 'add') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("username") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
{{ $t("changeUsernameDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<validated-text-input
|
||||
v-model="inputValue"
|
||||
settings-label="username"
|
||||
:is-valid="usernameValid"
|
||||
:invalid-issues="usernameIssues"
|
||||
@update:value="valuesChanged()"
|
||||
@blur="restoreEmptyUsername()"
|
||||
/>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="usernameCannotSubmit"
|
||||
@saveClicked="changeUser('username', cleanedInputValue)"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
|
||||
import { NotificationMixins } from '@/mixins/notifications';
|
||||
|
||||
// TODO extract usernameIssues/checks to a mixin to share between this and the authForm
|
||||
|
||||
export default {
|
||||
components: { ValidatedTextInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin, NotificationMixins],
|
||||
data () {
|
||||
return {
|
||||
inputValue: '',
|
||||
inputChanged: false,
|
||||
usernameIssues: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
cleanedInputValue () {
|
||||
return this.inputValue.startsWith('@')
|
||||
// remove the @ from the value, only if its starting with
|
||||
? this.inputValue.replace('@', '')
|
||||
// not removing it creates an error that is displayed
|
||||
: this.inputValue;
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.cleanedInputValue.length <= 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.usernameIssues.length === 0;
|
||||
},
|
||||
|
||||
usernameCannotSubmit () {
|
||||
if (this.cleanedInputValue.length <= 1) {
|
||||
return true;
|
||||
}
|
||||
return !this.usernameValid || !this.inputChanged;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
inputValue () {
|
||||
this.validateUsername(this.cleanedInputValue);
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.resetControls();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* is a callback from the {InlineSettingMixin}
|
||||
* do not remove
|
||||
*/
|
||||
resetControls () {
|
||||
this.inputValue = `@${this.user.auth.local.username}`;
|
||||
},
|
||||
restoreEmptyUsername () {
|
||||
if (this.inputValue.length < 1) {
|
||||
this.resetControls();
|
||||
}
|
||||
},
|
||||
async changeUser (attribute, newUsername) {
|
||||
await axios.put(`/api/v4/user/auth/update-${attribute}`, {
|
||||
username: newUsername,
|
||||
});
|
||||
|
||||
this.user.auth.local.username = newUsername;
|
||||
this.user.flags.verifiedUsername = true;
|
||||
|
||||
this.text(this.$t('userNameSuccess'));
|
||||
|
||||
this.closeModal();
|
||||
},
|
||||
valuesChanged () {
|
||||
this.inputChanged = true;
|
||||
|
||||
this.modalValuesChanged();
|
||||
},
|
||||
validateUsername: debounce(async function checkName (username) {
|
||||
if (username.length <= 1 || username === this.user.auth.local.username) {
|
||||
this.usernameIssues = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await this.$store.dispatch('auth:verifyUsername', {
|
||||
username,
|
||||
});
|
||||
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
}, 500),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
71
website/client/src/pages/settings/siteData.vue
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div class="row standard-page">
|
||||
<div class="col-12">
|
||||
<h1
|
||||
v-once
|
||||
class="page-header"
|
||||
>
|
||||
{{ $t('siteData') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<h2 v-once>
|
||||
{{ $t('user') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<user-id-row />
|
||||
<user-data-row />
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<h2 v-once>
|
||||
{{ $t('api') }}
|
||||
</h2>
|
||||
|
||||
<table class="table">
|
||||
<api-row />
|
||||
<developer-mode-row />
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<webhooks-row />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserIdRow from '@/pages/settings/siteDataRows/userIdRow.vue';
|
||||
import UserDataRow from '@/pages/settings/siteDataRows/userDataRow.vue';
|
||||
import ApiRow from '@/pages/settings/siteDataRows/apiRow.vue';
|
||||
import WebhooksRow from '@/pages/settings/siteDataRows/webhooksRow.vue';
|
||||
import DeveloperModeRow from '@/pages/settings/siteDataRows/developerModeRow.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DeveloperModeRow,
|
||||
WebhooksRow,
|
||||
ApiRow,
|
||||
UserDataRow,
|
||||
UserIdRow,
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
subSection: this.$t('siteData'),
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
131
website/client/src/pages/settings/siteDataRows/apiRow.vue
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("APITokenTitle") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('learnMore') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("APITokenTitle") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
v-html="$t('APITokenDisclaimer')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center api-key-input">
|
||||
<locked-input
|
||||
:label="$t('APITokenTitle')"
|
||||
:value="apiToken"
|
||||
:notification-text="$t('APICopied')"
|
||||
/>
|
||||
</div>
|
||||
<save-cancel-buttons
|
||||
:hide-save="true"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.api-key-input {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 0;
|
||||
|
||||
td {
|
||||
border: 0;
|
||||
padding: 0 !important;
|
||||
|
||||
&:first-of-type {
|
||||
text-align: end;
|
||||
vertical-align: middle;
|
||||
padding-right: 1rem !important;
|
||||
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.dropdown-menu {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '@/pages/settings/components/saveCancelButtons.vue';
|
||||
import LockedInput from '@/pages/settings/components/lockedInput.vue';
|
||||
|
||||
export default {
|
||||
components: { LockedInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('message', this.receiveMessage, false);
|
||||
},
|
||||
destroy () {
|
||||
window.removeEventListener('message', this.receiveMessage);
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
credentials: 'credentials',
|
||||
}),
|
||||
apiToken () {
|
||||
return this.credentials.API_TOKEN;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
receiveMessage (eventFrom) {
|
||||
if (eventFrom.origin !== 'https://www.spritely.app') {
|
||||
return;
|
||||
}
|
||||
|
||||
const creds = {
|
||||
userId: this.user._id,
|
||||
apiToken: this.credentials.API_TOKEN,
|
||||
};
|
||||
eventFrom.source.postMessage(creds, eventFrom.origin);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<tr>
|
||||
<td class="settings-label">
|
||||
<div class="d-flex align-items-center">
|
||||
{{ $t("developerMode") }}
|
||||
<information-icon
|
||||
tooltip-id="developerMode"
|
||||
:tooltip="$t('developerModeTooltip')"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<toggle-switch
|
||||
v-model="user.preferences.developerMode"
|
||||
@change="setUserPreference('developerMode')"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep {
|
||||
.toggle-switch-outer {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
|
||||
import { GenericUserPreferencesMixin } from '@/pages/settings/components/genericUserPreferencesMixin';
|
||||
import informationIcon from '@/assets/svg/information.svg';
|
||||
import InformationIcon from '@/components/ui/informationIcon.vue';
|
||||
|
||||
export default {
|
||||
components: { InformationIcon, ToggleSwitch },
|
||||
mixins: [GenericUserPreferencesMixin],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
information: informationIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
139
website/client/src/pages/settings/siteDataRows/userDataRow.vue
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("yourUserData") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="openModal()"
|
||||
>
|
||||
{{ $t('learnMore') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("yourUserData") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
{{ $t("yourUserDataDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center data-download-selection">
|
||||
<table v-once>
|
||||
<tr>
|
||||
<td>{{ $t('taskHistory') }}</td>
|
||||
<td>
|
||||
<a
|
||||
href="/export/history.csv"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
{{ $t('downloadCSV') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('userData') }}</td>
|
||||
<td>
|
||||
<b-dropdown
|
||||
:text="$t('downloadAs')"
|
||||
right="right"
|
||||
>
|
||||
<b-dropdown-item
|
||||
href="/export/userdata.xml"
|
||||
>
|
||||
{{ $t('xml') }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
href="/export/userdata.json"
|
||||
>
|
||||
{{ $t('json') }}
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<save-cancel-buttons
|
||||
:hide-save="true"
|
||||
@cancelClicked="requestCloseModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.data-download-selection {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 0;
|
||||
|
||||
|
||||
td {
|
||||
border: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
|
||||
&:first-of-type {
|
||||
text-align: end;
|
||||
vertical-align: middle;
|
||||
padding-right: 0.5rem !important;
|
||||
|
||||
font-weight: bold;
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
tr:first-of-type {
|
||||
td {
|
||||
padding-bottom: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep {
|
||||
.dropdown-menu {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '@/pages/settings/components/saveCancelButtons.vue';
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
58
website/client/src/pages/settings/siteDataRows/userIdRow.vue
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<template>
|
||||
<tr>
|
||||
<td class="settings-label">
|
||||
<div
|
||||
v-once
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
{{ $t("userId") }} <information-icon
|
||||
tooltip-id="userId"
|
||||
:tooltip="$t('userIdTooltip')"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
v-once
|
||||
class="settings-value"
|
||||
>
|
||||
{{ user.id }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="copyUserId()"
|
||||
>
|
||||
{{ $t('copy') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
import copyToClipboard from '@/mixins/copyToClipboard';
|
||||
import InformationIcon from '@/components/ui/informationIcon.vue';
|
||||
|
||||
export default {
|
||||
components: { InformationIcon },
|
||||
mixins: [copyToClipboard],
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
copyUserId () {
|
||||
this.mixinCopyToClipboard(
|
||||
this.user.id,
|
||||
this.$t('useridCopied'),
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
323
website/client/src/pages/settings/siteDataRows/webhooksRow.vue
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
<template>
|
||||
<div>
|
||||
<h2
|
||||
v-once
|
||||
>
|
||||
{{ $t("webhooks") }}
|
||||
</h2>
|
||||
<div
|
||||
v-once
|
||||
class="webhooks-info mb-3"
|
||||
v-html="$t('webhooksInfo')"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="d-flex justify-content-center webhooks-list"
|
||||
:class="{'webhooks-exists': Boolean(webhooks.length)}"
|
||||
>
|
||||
<table class="table table-striped">
|
||||
<tr v-if="webhooks.length">
|
||||
<th>{{ $t('webhookURL') }}</th>
|
||||
<th>{{ $t('enabled') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
v-for="(webhook, index) in webhooks"
|
||||
:key="webhook.id"
|
||||
>
|
||||
<td style="width: 588px">
|
||||
<div class="d-flex align-items-center">
|
||||
<div style="width: 440px">
|
||||
<validated-text-input
|
||||
v-model="webhook.url"
|
||||
:placeholder="$t('webhookURL')"
|
||||
:is-valid="isValidUrl(webhook.url)"
|
||||
:readonly="!unsaved.includes(index)"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="unsaved.includes(index)">
|
||||
<button
|
||||
class="btn btn-primary ml-2"
|
||||
:disabled="!isValidUrl(webhook.url)"
|
||||
@click="saveWebhook(webhook, index)"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<a
|
||||
class="edit-link ml-3"
|
||||
@click.prevent="cancelWebhookChanges(webhook, index)"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
<td style="vertical-align: middle;">
|
||||
<toggle-switch
|
||||
v-if="!unsaved.includes(index)"
|
||||
v-model="webhook.enabled"
|
||||
@change="updateWebhookEnabled(webhook, index)"
|
||||
/>
|
||||
</td>
|
||||
<td class="menu-column">
|
||||
<b-dropdown
|
||||
v-if="!unsaved.includes(index)"
|
||||
right="right"
|
||||
toggle-class="with-icon"
|
||||
class="ml-2"
|
||||
:no-caret="true"
|
||||
>
|
||||
<template #button-content>
|
||||
<span
|
||||
v-once
|
||||
class="svg-icon inline menuIcon"
|
||||
v-html="icons.menuIcon"
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
<b-dropdown-item
|
||||
class="selectListItem"
|
||||
@click="editWebhook(webhook, index)"
|
||||
>
|
||||
<span class="with-icon">
|
||||
<span
|
||||
v-once
|
||||
class="svg-icon icon-16 color"
|
||||
v-html="icons.editIcon"
|
||||
></span>
|
||||
<span v-once>
|
||||
{{ $t('edit') }}
|
||||
</span>
|
||||
</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item
|
||||
class="selectListItem custom-hover--delete"
|
||||
@click="deleteWebhook(webhook, index)"
|
||||
>
|
||||
<span class="with-icon">
|
||||
<span
|
||||
v-once
|
||||
class="svg-icon icon-16 color"
|
||||
v-html="icons.deleteIcon"
|
||||
></span>
|
||||
<span v-once>
|
||||
{{ $t('delete') }}
|
||||
</span>
|
||||
</span>
|
||||
</b-dropdown-item>
|
||||
</b-dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
colspan="3"
|
||||
:class="{'webhooks-empty': !Boolean(webhooks.length)}"
|
||||
>
|
||||
<button
|
||||
class="btn btn-secondary d-flex align-items-center new-webhook-btn"
|
||||
:class="{'webhooks-exists': Boolean(webhooks.length)}"
|
||||
tabindex="0"
|
||||
@click="newUnsavedWebhook()"
|
||||
>
|
||||
<div
|
||||
class="svg-icon icon-10 color"
|
||||
v-html="icons.positive"
|
||||
></div>
|
||||
<div class="ml-75 mr-1">
|
||||
{{ $t('addWebhook') }}
|
||||
</div>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.webhooks-info {
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.svg-icon.icon-10 {
|
||||
color: $green-10;
|
||||
}
|
||||
|
||||
.menuIcon {
|
||||
width: 4px;
|
||||
height: 1rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.custom-hover--delete {
|
||||
--hover-color: #{$maroon-50};
|
||||
--hover-background: #ffb6b83F;
|
||||
}
|
||||
|
||||
.webhooks-list {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
tr:first-of-type {
|
||||
th {
|
||||
padding: 0.25rem;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.5rem !important;
|
||||
|
||||
&:first-of-type {
|
||||
text-align: end;
|
||||
vertical-align: middle;
|
||||
padding-right: 1rem !important;
|
||||
|
||||
line-height: 1.71;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
&:not(:first-of-type) {
|
||||
padding-right: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td.webhooks-empty {
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
td.menu-column {
|
||||
width: 2rem;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.new-webhook-btn:not(.webhooks-exists) {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import * as validator from 'validator';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import uuid from '../../../../../common/script/libs/uuid';
|
||||
import positiveIcon from '@/assets/svg/positive.svg';
|
||||
import ToggleSwitch from '@/components/ui/toggleSwitch.vue';
|
||||
import menuIcon from '@/assets/svg/menu.svg';
|
||||
import deleteIcon from '@/assets/svg/delete.svg';
|
||||
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
|
||||
import editIcon from '@/assets/svg/edit.svg';
|
||||
|
||||
export default {
|
||||
components: { ValidatedTextInput, ToggleSwitch },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
positive: positiveIcon,
|
||||
menuIcon,
|
||||
deleteIcon,
|
||||
editIcon,
|
||||
}),
|
||||
webhooks: [], // view copy of state
|
||||
unsaved: [],
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.setWebhooksViewCopy();
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
credentials: 'credentials',
|
||||
}),
|
||||
|
||||
},
|
||||
methods: {
|
||||
isValidUrl (url) {
|
||||
return validator.isURL(url, {
|
||||
require_tld: true,
|
||||
require_protocol: true,
|
||||
protocols: ['http', 'https'],
|
||||
});
|
||||
},
|
||||
async newUnsavedWebhook () {
|
||||
const webhookInfo = {
|
||||
id: uuid(),
|
||||
type: 'taskActivity',
|
||||
options: {
|
||||
created: false,
|
||||
updated: false,
|
||||
deleted: false,
|
||||
scored: true,
|
||||
},
|
||||
url: '',
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
this.unsaved.push(
|
||||
this.webhooks.push(webhookInfo) - 1,
|
||||
);
|
||||
},
|
||||
cancelWebhookChanges (webhook, index) {
|
||||
if (this.unsaved.includes(index)) {
|
||||
this.unsaved = this.unsaved.filter(i => i !== index);
|
||||
}
|
||||
|
||||
if (this.user.webhooks[index]) {
|
||||
this.webhooks[index] = this.user.webhooks[index];
|
||||
} else {
|
||||
this.webhooks.splice(index, 1);
|
||||
}
|
||||
},
|
||||
async saveWebhook (webhook, index) {
|
||||
if (!this.isValidUrl(webhook.url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const webhookId = webhook.id;
|
||||
|
||||
if (this.user.webhooks.every(w => w.id !== webhookId)) {
|
||||
const createdWebhook = await this.$store.dispatch('user:addWebhook', { webhook });
|
||||
|
||||
this.user.webhooks[index] = createdWebhook;
|
||||
} else {
|
||||
const updatedWebhook = await this.$store.dispatch('user:updateWebhook', { webhook });
|
||||
this.user.webhooks[index] = updatedWebhook;
|
||||
}
|
||||
this.cancelWebhookChanges(webhook, index);
|
||||
},
|
||||
async updateWebhookEnabled (webhook, index) {
|
||||
if (this.unsaved.includes(index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedWebhook = await this.$store.dispatch('user:updateWebhook', { webhook });
|
||||
this.user.webhooks[index] = updatedWebhook;
|
||||
},
|
||||
async editWebhook (webhook, index) {
|
||||
this.unsaved.push(index);
|
||||
},
|
||||
async deleteWebhook (webhook, index) {
|
||||
await this.$store.dispatch('user:deleteWebhook', { webhook });
|
||||
this.user.webhooks.splice(index, 1);
|
||||
this.setWebhooksViewCopy();
|
||||
},
|
||||
setWebhooksViewCopy () {
|
||||
this.webhooks = [...this.user.webhooks];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -88,6 +88,8 @@ const router = new VueRouter({
|
|||
{ name: 'logout', path: '/logout', component: Logout },
|
||||
{
|
||||
name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
}, {
|
||||
name: 'forgotPassword', path: '/forgot-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
||||
},
|
||||
{ name: 'tasks', path: '/', component: UserTasks },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import ParentPage from '@/components/parentPage.vue';
|
||||
|
||||
// Settings
|
||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
|
||||
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
|
||||
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
|
||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
|
||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
|
||||
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
|
||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/pages/settings-overview');
|
||||
const GeneralSettings = () => import(/* webpackChunkName: "settings" */'@/pages/settings/generalSettings');
|
||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/pages/settings/notificationSettings');
|
||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/pages/settings/purchaseHistory.vue');
|
||||
|
||||
const SiteData = () => import(/* webpackChunkName: "settings" */'@/pages/settings/siteData.vue');
|
||||
|
||||
// not converted yet
|
||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/pages/settings/promoCode.vue');
|
||||
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
|
||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
|
||||
|
||||
export const USER_ROUTES = {
|
||||
path: '/user',
|
||||
|
|
@ -20,20 +22,16 @@ export const USER_ROUTES = {
|
|||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
name: 'site',
|
||||
path: 'site',
|
||||
component: Site,
|
||||
name: 'general',
|
||||
path: 'general',
|
||||
component: GeneralSettings,
|
||||
},
|
||||
{
|
||||
name: 'api',
|
||||
path: 'api',
|
||||
component: API,
|
||||
},
|
||||
{
|
||||
name: 'dataExport',
|
||||
path: 'data-export',
|
||||
component: DataExport,
|
||||
name: 'siteData',
|
||||
path: 'siteData',
|
||||
component: SiteData,
|
||||
},
|
||||
{ path: 'api', redirect: { name: 'siteData' } },
|
||||
{
|
||||
name: 'promoCode',
|
||||
path: 'promo-code',
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export async function sleep (store) {
|
|||
}
|
||||
|
||||
export async function addWebhook (store, payload) {
|
||||
const response = await axios.post('/api/v4/user/webhook', payload.webhookInfo);
|
||||
const response = await axios.post('/api/v4/user/webhook', payload.webhook);
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const nconf = require('nconf');
|
||||
const vueTemplateCompiler = require('vue-template-babel-compiler');
|
||||
const { DuplicatesPlugin } = require('inspectpack/plugin');
|
||||
const setupNconf = require('../server/libs/setupNconf');
|
||||
const pkg = require('./package.json');
|
||||
|
|
@ -126,6 +127,15 @@ module.exports = {
|
|||
if (process.env.NODE_ENV === 'development') {
|
||||
config.plugins.delete('preload');
|
||||
}
|
||||
|
||||
// enable optional chaining in templates
|
||||
config.module
|
||||
.rule('vue')
|
||||
.use('vue-loader')
|
||||
.tap(options => {
|
||||
options.compiler = vueTemplateCompiler;
|
||||
return options;
|
||||
});
|
||||
},
|
||||
|
||||
devServer: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
{
|
||||
"frequentlyAskedQuestions": "Frequently Asked Questions",
|
||||
"general": "General",
|
||||
|
||||
"faqQuestion0": "I'm confused. Where do I get an overview?",
|
||||
"iosFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > Customize Avatar.\n\n Some basic ways to interact: click the (+) in the upper-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-left-hand corner, and expand and contract checklists by clicking on the checklist bubble.",
|
||||
"androidFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > [Inventory >] Avatar.\n\n Some basic ways to interact: click the (+) in the lower-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-right-hand corner, and expand and contract checklists by clicking on the checklist count box.",
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
"missingPassword": "Missing password.",
|
||||
"missingNewPassword": "Missing new password.",
|
||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
||||
"wrongPassword": "Wrong password.",
|
||||
"wrongPassword": "Password is incorrect. If you forgot your password, click \"Forgot Password.\"",
|
||||
"incorrectDeletePhrase": "Please type <%= magicWord %> in all capital letters to delete your account.",
|
||||
"notAnEmail": "Invalid email address.",
|
||||
"emailTaken": "Email address is already used in an account.",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"modalAchievement": "Achievement!",
|
||||
"special": "Special",
|
||||
"site": "Site",
|
||||
"general": "General",
|
||||
"help": "Help",
|
||||
"user": "User",
|
||||
"market": "Market",
|
||||
|
|
@ -71,6 +72,7 @@
|
|||
"error": "Error",
|
||||
"menu": "Menu",
|
||||
"notifications": "Notifications",
|
||||
"allNotifications": "All Notifications",
|
||||
"noNotifications": "You're all caught up!",
|
||||
"noNotificationsText": "The notification fairies give you a raucous round of applause! Well done!",
|
||||
"clear": "Clear",
|
||||
|
|
|
|||
|
|
@ -1,18 +1,26 @@
|
|||
{
|
||||
"settings": "Settings",
|
||||
"generalSettings": "General Settings",
|
||||
"siteData": "Site Data",
|
||||
"taskSettings": "Task Settings",
|
||||
"confirmCancelChanges": "Are you sure? You will lose your unsaved changes.",
|
||||
"account": "Account",
|
||||
"loginMethods": "Login Methods",
|
||||
"character": "Character",
|
||||
"language": "Language",
|
||||
"siteLanguage": "Site Language",
|
||||
"americanEnglishGovern": "In the event of a discrepancy in the translations, the American English version governs.",
|
||||
"helpWithTranslation": "Would you like to help with the translation of Habitica? Great! Then visit <a href=\"https://translate.habitica.com\">Habitica's Weblate site</a>!",
|
||||
"helpWithTranslation": "Are you interested in helping with the translation of Habitica? Great! Then visit <a href=\"https://translate.habitica.com\">Habitica's Weblate site</a>!",
|
||||
"stickyHeader": "Sticky header",
|
||||
"newTaskEdit": "Open new tasks in edit mode",
|
||||
"reverseChatOrder": "Show chat messages in reverse order",
|
||||
"startAdvCollapsed": "Advanced Settings in tasks start collapsed",
|
||||
"startAdvCollapsedPop": "With this option set, Advanced Settings will be hidden when you first open a task for editing.",
|
||||
"dontShowAgain": "Don't show this again",
|
||||
"suppressLevelUpModal": "Don't show popup when gaining a level",
|
||||
"suppressHatchPetModal": "Don't show popup when hatching a pet",
|
||||
"suppressRaisePetModal": "Don't show popup when raising a pet into a mount",
|
||||
"suppressStreakModal": "Don't show popup when attaining a Streak achievement",
|
||||
"showLevelUpModal": "When Gaining a Level",
|
||||
"showHatchPetModal": "When Hatching a Pet",
|
||||
"showRaisePetModal": "When Raising a Pet into a Mount",
|
||||
"showStreakModal": "When Attaining a Streak Achievement",
|
||||
"baileyAnnouncement": "Latest Bailey Announcement",
|
||||
"view": "View",
|
||||
"showTour": "Show Tour",
|
||||
"showBailey": "Show Bailey",
|
||||
"showBaileyPop": "Bring Bailey the Town Crier out of hiding so you can review past news.",
|
||||
|
|
@ -25,17 +33,28 @@
|
|||
"resetAccPop": "Start over, removing all levels, gold, gear, history, and tasks.",
|
||||
"deleteAccount": "Delete Account",
|
||||
"deleteAccPop": "Cancel and remove your Habitica account.",
|
||||
"feedback": "If you'd like to give us feedback, please enter it below - we'd love to know what you liked or didn't like about Habitica! Don't speak English well? No problem! Use the language you prefer.",
|
||||
"feedback": "If you'd like to give us feedback, please enter it below - we'd love to hear your feedback! It will be anonymous unless you choose to enter your contact details. Don't speak English well? No problem! Use the language you prefer.",
|
||||
"feedbackPlaceholder": "Add your feedback",
|
||||
"dataExport": "Data Export",
|
||||
"saveData": "Here are a few options for saving your data.",
|
||||
"habitHistory": "Habit History",
|
||||
"exportHistory": "Export History:",
|
||||
"csv": "(CSV)",
|
||||
"downloadCSV": "Download CSV",
|
||||
"downloadAs": "Download as",
|
||||
"userData": "User Data",
|
||||
"yourUserData": "Your User Data",
|
||||
"taskHistory": "Task History",
|
||||
"yourUserDataDisclaimer": "Here you can download a copy of your task history or your full user data.",
|
||||
"exportUserData": "Export User Data:",
|
||||
"useridCopied": "User ID copied to clipboard.",
|
||||
"userIdTooltip": "The User ID is a unique number that Habitica automatically generates when a player joins, similar to a Username. However, unlike the Username, a User ID can not be changed.",
|
||||
"developerMode": "Developer Mode",
|
||||
"developerModeTooltip": "Habitica provides a developer mode to enable additional features that interact with Habitica's API.",
|
||||
"export": "Export",
|
||||
"xml": "(XML)",
|
||||
"json": "(JSON)",
|
||||
"xml": "XML",
|
||||
"json": "JSON",
|
||||
"api": "API",
|
||||
"customDayStart": "Custom Day Start",
|
||||
"adjustment": "Adjustment",
|
||||
"dayStartAdjustment": "Day Start Adjustment",
|
||||
|
|
@ -45,6 +64,7 @@
|
|||
"customDayStartInfo1": "Habitica checks and resets your Dailies at midnight in your own time zone each day. You can adjust when that happens past the default time here.",
|
||||
"misc": "Misc",
|
||||
"showHeader": "Show Header",
|
||||
"currentPass": "Current Password",
|
||||
"changePass": "Change Password",
|
||||
"changeUsername": "Change Username",
|
||||
"changeEmail": "Change Email Address",
|
||||
|
|
@ -54,11 +74,18 @@
|
|||
"confirmPass": "Confirm New Password",
|
||||
"newUsername": "New Username",
|
||||
"dangerZone": "Danger Zone",
|
||||
"resetText1": "WARNING! This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
|
||||
"resetText2": "You will lose all your levels, Gold, and Experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment except Subscriber Mystery Items and free commemorative items. You will be able to buy the deleted items back, including all limited edition equipment (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class, achievements and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks and equipment.",
|
||||
"deleteLocalAccountText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type your password into the text box below.",
|
||||
"deleteSocialAccountText": "Are you sure? This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type \"<%= magicWord %>\" into the text box below.",
|
||||
"resetText1": "<b>Be careful!</b> This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
|
||||
"resetDetail1": "You will lose all your levels, Gold, and Experience points.",
|
||||
"resetDetail2": "You will keep your current class, achievements and your pets and mounts.",
|
||||
"resetDetail3": "All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data.",
|
||||
"resetDetail4": "You will lose all your equipment except Subscriber Mystery Items and free commemorative items. You will be able to buy the deleted items back, including all limited edition equipment (you will need to be in the correct class to re-buy class-specific gear).",
|
||||
"resetText2": "Another option is using an <b>Orb of Rebirth</b>, which will reset everything else while preserving your Tasks and Equipment.",
|
||||
"deleteLocalAccountText": "<b>Are you sure?</b> This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type your password into the text box below.",
|
||||
"deleteSocialAccountText": "<b>Are you sure?</b> This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type <b>\"<%= magicWord %>\"</b> into the text box below.",
|
||||
"API": "API",
|
||||
"APICopied": "API token copied to clipboard.",
|
||||
"APITokenTitle": "API Token",
|
||||
"APITokenDisclaimer": "<b>Your API Token is like a password; Do not share it publicly.</b> You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.<br><br><b>Note:</b> If you need a new API Token (e.g., if you accidentally shared it), email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with your User ID and current Token. Once it is reset you will need to re-authorize everything by logging out of the website and mobile app and by providing the new Token to any other Habitica tools that you use.",
|
||||
"APIv3": "API v3",
|
||||
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",
|
||||
"APIToken": "API Token (this is a password - see warning above!)",
|
||||
|
|
@ -70,13 +97,14 @@
|
|||
"resetDo": "Do it, reset my account!",
|
||||
"resetComplete": "Reset complete!",
|
||||
"fixValues": "Fix Values",
|
||||
"fixValuesText1": "If you've encountered a bug or made a mistake that unfairly changed your character (damage you shouldn't have taken, Gold you didn't really earn, etc.), you can manually correct your numbers here. Yes, this makes it possible to cheat: use this feature wisely, or you'll sabotage your own habit-building!",
|
||||
"fixValuesText2": "Note that you cannot restore Streaks on individual tasks here. To do that, edit the Daily and go to Advanced Settings, where you will find a Restore Streak field.",
|
||||
"fixValuesText1": "If you've encountered an issue that unfairly changed your character (damage you shouldn't have taken, Gold you didn't really earn, etc.), you can manually correct those values here. Yes, this makes it possible to cheat: use this feature wisely, or you'll sabotage your own habit-building!",
|
||||
"fixValuesText2": "<b>Note</b>: To restore Streaks on individual Tasks, edit the Task and use the Restore Streak field.",
|
||||
"fix21Streaks": "21-Day Streaks",
|
||||
"discardChanges": "Discard Changes",
|
||||
"deleteDo": "Do it, delete my account!",
|
||||
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
|
||||
"passwordChangeSuccess": "Your password was successfully changed to the one you just chose. You can now use it to access your account.",
|
||||
"userNameSuccess": "Username successfully changed",
|
||||
"displayNameSuccess": "Display name successfully changed",
|
||||
"emailSuccess": "Email successfully changed",
|
||||
"passwordSuccess": "Password successfully changed",
|
||||
|
|
@ -98,8 +126,8 @@
|
|||
"giftedSubscriptionInfo": "<%= name %> gifted you a <%= months %> month subscription",
|
||||
"giftedSubscriptionFull": "Hello <%= username %>, <%= sender %> has sent you <%= monthCount %> months of subscription!",
|
||||
"giftedSubscriptionWinterPromo": "Hello <%= username %>, you received <%= monthCount %> months of subscription as part of our holiday gift-giving promotion!",
|
||||
"invitedParty": "You were invited to a Party",
|
||||
"invitedGuild": "You were invited to a Guild",
|
||||
"invitedParty": "Invited to a Party",
|
||||
"invitedGuild": "Invited to a Guild",
|
||||
"importantAnnouncements": "Reminders to check in to complete tasks and receive prizes",
|
||||
"weeklyRecaps": "Summaries of your account activity in the past week (Note: this is currently disabled due to performance issues, but we hope to have this back up and sending e-mails again soon!)",
|
||||
"onboarding": "Guidance with setting up your Habitica account",
|
||||
|
|
@ -107,25 +135,24 @@
|
|||
"subscriptionReminders": "Subscriptions Reminders",
|
||||
"questStarted": "Your Quest has Begun",
|
||||
"invitedQuest": "Invited to Quest",
|
||||
"kickedGroup": "Kicked from group",
|
||||
"kickedGroup": "Removed from group",
|
||||
"remindersToLogin": "Reminders to check in to Habitica",
|
||||
"unsubscribedSuccessfully": "Unsubscribed successfully!",
|
||||
"unsubscribedTextUsers": "You have successfully unsubscribed from all Habitica emails. You can enable only the emails you want to receive from <a href=\"/user/settings/notifications\">Settings > > Notifications</a> (requires login).",
|
||||
"unsubscribedTextOthers": "You won't receive any other email from Habitica.",
|
||||
"unsubscribeAllEmails": "Check to Unsubscribe from Emails",
|
||||
"unsubscribeAllEmailsText": "By checking this box, I certify that I understand that by unsubscribing from all emails, Habitica will never be able to notify me via email about important changes to the site or my account.",
|
||||
"unsubscribeAllPush": "Check to Unsubscribe from all Push Notifications",
|
||||
"unsubscribeAllEmails": "Unsubscribe from Emails",
|
||||
"unsubscribeAllEmailsText": "Habitica will be unable to notify you via email about important changes to the site or your account.",
|
||||
"unsubscribeAllPush": "Unsubscribe from all Push Notifications",
|
||||
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from \"<%= emailType %>\" emails.",
|
||||
"subscriptionRateText": "Recurring <strong>$<%= price %> USD</strong> every <strong><%= months %> months</strong>",
|
||||
"giftSubscriptionRateText": "<strong>$<%= price %> USD</strong> for <strong><%= months %> months</strong>",
|
||||
"benefits": "Benefits",
|
||||
"coupon": "Coupon",
|
||||
"couponText": "We sometimes have events and give out promo codes for special gear. (eg, those who stop by our Wondercon booth)",
|
||||
"couponText": "We sometimes have events and give out promo codes for special gear.",
|
||||
"apply": "Apply",
|
||||
"promoCode": "Promo Code",
|
||||
"promoCodeApplied": "Promo Code Applied! Check your inventory",
|
||||
"promoPlaceholder": "Enter Promotion Code",
|
||||
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.",
|
||||
"saveCustomDayStart": "Save Custom Day Start",
|
||||
"registration": "Registration",
|
||||
"addLocalAuth": "Add Email and Password Login",
|
||||
|
|
@ -134,9 +161,10 @@
|
|||
"generate": "Generate",
|
||||
"getCodes": "Get Codes",
|
||||
"webhooks": "Webhooks",
|
||||
"webhooksInfo": "Habitica provides webhooks so that when certain actions occur in your account, information can be sent to a script on another website. You can specify those scripts here. Be careful with this feature because specifying an incorrect URL can cause errors or slowness in Habitica. For more information, see the wiki's <a target=\"_blank\" href=\"https://habitica.fandom.com/wiki/Webhooks\">Webhooks</a> page.",
|
||||
"webhooksInfo": "Webhooks provide a way for developers to receive notifications when a particular action is performed, such as scoring or updating a Task, or sending a message in a Group. By creating a webhook, you will be able to listen to changes in Habitica and build apps that respond to these changes.<br><br>For additional information and examples on webhooks, please visit our <a target=\"_blank\" href=\"https://habitica.fandom.com/wiki/Webhooks\">API Docs</a>",
|
||||
"enabled": "Enabled",
|
||||
"webhookURL": "Webhook URL",
|
||||
"addWebhook": "Add Webhook",
|
||||
"invalidUrl": "invalid url",
|
||||
"invalidWebhookId": "the \"id\" parameter should be a valid UUID.",
|
||||
"webhookBooleanOption": "\"<%= option %>\" must be a Boolean value.",
|
||||
|
|
@ -179,7 +207,14 @@
|
|||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "Your username is used for invitations, @mentions in chat, and messaging. It must be 1 to 20 characters, containing only letters a to z, numbers 0 to 9, hyphens, or underscores, and cannot include any inappropriate terms.",
|
||||
"changeEmailDisclaimer": "This is the email address that you use to log in to Habitica, as well as receive notifications.",
|
||||
"changeDisplayNameDisclaimer": "This is the name that will be displayed for your Avatar in Habitica.",
|
||||
"changePasswordDisclaimer": "Password must be 8 characters or more. We recommend a strong password that you're not using elsewhere.",
|
||||
"dateFormatDisclaimer": "Adjust the date formatting across Habitica.",
|
||||
"verifyUsernameVeteranPet": "One of these Veteran Pets will be waiting for you after you've finished confirming!",
|
||||
"enableAudio": "Enable Audio",
|
||||
"playDemoAudio": "Play Demo",
|
||||
"audioThemeDisclaimer": "Audio themes add optional sound effects to the Habitica website. Volume levels are controlled using your computer's volume settings.",
|
||||
"mentioning": "Mentioning",
|
||||
"suggestMyUsername": "Suggest my username",
|
||||
"everywhere": "Everywhere",
|
||||
|
|
@ -189,6 +224,11 @@
|
|||
"amount": "Amount",
|
||||
"action": "Action",
|
||||
"note": "Note",
|
||||
"noClassSelected": "No Class Selected",
|
||||
"currentClass": "Current Class",
|
||||
"changeClassSetting": "Change Class",
|
||||
"chooseClassSetting": "Choose Class",
|
||||
"changeClassDisclaimer": "Changing your class will refund all of your existing Stat Points. Once you have selected your new class, adjust your Stat Points from the Stats section of your profile.",
|
||||
"remainingBalance": "Remaining Balance",
|
||||
"transactions": "Transactions",
|
||||
"hourglassTransactions": "Hourglass Transactions",
|
||||
|
|
@ -203,7 +243,6 @@
|
|||
"transaction_gift_receive": "<b>Received</b> from",
|
||||
"transaction_create_challenge": "<b>Created</b> challenge",
|
||||
"transaction_create_bank_challenge": "<b>Created</b> bank challenge",
|
||||
"transaction_create_bank_challenge": "Created bank challenge",
|
||||
"transaction_create_guild": "<b>Created</b> guild",
|
||||
"transaction_change_class": "<b>Class</b> change",
|
||||
"transaction_rebirth": "Used Orb of Rebirth",
|
||||
|
|
@ -212,5 +251,8 @@
|
|||
"transaction_reroll": "Used Fortify Potion",
|
||||
"transaction_subscription_perks": "<b>Subscription</b> perk",
|
||||
"transaction_admin_update_balance": "<b>Admin</b> given",
|
||||
"transaction_admin_update_hourglasses": "<b>Admin</b> updated"
|
||||
"transaction_admin_update_hourglasses": "<b>Admin</b> updated",
|
||||
"connected": "Connected",
|
||||
"connect": "Connect",
|
||||
"remove": "Remove"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@
|
|||
"streakCoins": "Streak Bonus!",
|
||||
"taskToTop": "To top",
|
||||
"taskToBottom": "To bottom",
|
||||
"taskAlias": "Task Alias",
|
||||
"taskAliasPopover": "This task alias can be used when integrating with 3rd party integrations. Only dashes, underscores, and alphanumeric characters are supported. The task alias must be unique among all your tasks.",
|
||||
"taskAliasPlaceholder": "your-task-alias-here",
|
||||
"taskAliasAlreadyUsed": "Task alias already used on another task.",
|
||||
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
|
||||
"invalidTasksType": "Task type must be one of \"habits\", \"dailys\", \"todos\", \"rewards\".",
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export const CHAT_FLAG_FROM_SHADOW_MUTE = 10;
|
|||
// @TODO use those constants to replace hard-coded numbers
|
||||
|
||||
export const SUPPORTED_SOCIAL_NETWORKS = [
|
||||
{ key: 'google', name: 'Google' },
|
||||
{ key: 'apple', name: 'Apple' },
|
||||
{ key: 'google', name: 'Google' },
|
||||
];
|
||||
|
||||
export const GUILDS_PER_PAGE = 30; // number of guilds to return per page when using pagination
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import moment from 'moment';
|
||||
|
||||
export const CURRENT_SEASON = moment().isBefore('2020-08-02') ? 'summer' : '_NONE_';
|
||||
|
||||
// sorting this also changes the class selection
|
||||
export const CLASSES = [
|
||||
'warrior',
|
||||
'rogue',
|
||||
'healer',
|
||||
'wizard',
|
||||
'rogue',
|
||||
'warrior',
|
||||
];
|
||||
|
||||
export const GEAR_TYPES = [
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export default function addTask (user, req = { body: {} }) {
|
|||
if (task._editing) {
|
||||
task._edit = clone(task);
|
||||
}
|
||||
task._advanced = !user.preferences.advancedCollapsed;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
|
|
|||