Squashed commit of the following:
commit16d8b87e90Merge: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 commit07387faf48Author: 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> commitea8563cd17Merge:3e16584dcf6259955891Author: 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 commit3e16584dcfAuthor: negue <eugen.bolz@gmail.com> Date: Tue Aug 29 21:22:06 2023 +0200 fix PR comments commit84ba44fb19Author: negue <eugen.bolz@gmail.com> Date: Tue Aug 29 20:38:54 2023 +0200 fix PR comments commit6259955891Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 25 11:20:26 2023 -0400 update form.scss commitda82bd8e68Author: negue <eugen.bolz@gmail.com> Date: Thu Aug 24 21:40:02 2023 +0200 remove ending commit82e5fd2a83Author: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 22:25:41 2023 +0200 fix spacing commit9ad06ea88bAuthor: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 22:09:22 2023 +0200 clean up debug row for login methods commit41cde37675Merge: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 commit8c568060f9Author: negue <eugen.bolz@gmail.com> Date: Mon Aug 21 21:49:31 2023 +0200 fix PR comments commit36f7a4711dMerge: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 commitd279af7897Merge:ffbed3e044b20ea44d49Author: negue <eugen.bolz@gmail.com> Date: Wed Aug 9 21:13:37 2023 +0200 Merge branch 'negue/refactor/routes' into negue/ui/setting commitb20ea44d49Author: negue <eugen.bolz@gmail.com> Date: Wed Aug 9 21:04:12 2023 +0200 Split Vue.Router routes commitffbed3e044Author: negue <eugen.bolz@gmail.com> Date: Sun Jul 23 00:00:24 2023 +0200 remove console commit4c350b0180Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 23:34:20 2023 +0200 update Bailey Notification Text + fix popover commitc105b9ecf9Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 23:21:53 2023 +0200 fix change password setting commit06410b4807Author: negue <eugen.bolz@gmail.com> Date: Sat Jul 22 22:50:00 2023 +0200 fix reset account texts commitccfdd9bb9cMerge: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 commit35c75304f1Author: negue <eugen.bolz@gmail.com> Date: Sun Jul 2 20:16:06 2023 +0200 more fixes commit203e961464Author: negue <eugen.bolz@gmail.com> Date: Sun Jul 2 19:45:17 2023 +0200 fix notification settings commitec94604791Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 22:00:45 2023 +0200 applied same styling to promoCode.vue commit0177b3a76bAuthor: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 21:41:05 2023 +0200 move promoCode.vue to pages/settings commit8fbb600273Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 21:40:35 2023 +0200 saveCancelButtons.vue allow to hide the cancel part commit4915f2a3fbAuthor: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 21:09:07 2023 +0200 Hide Transactions Page again commit8b5ae17f02Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 20:52:03 2023 +0200 also check for invalid arguments in the password settings commitaa97ed5299Author: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 20:25:53 2023 +0200 fix localhost externalLinks check commit87a4e4931bAuthor: negue <eugen.bolz@gmail.com> Date: Sun Jun 25 20:01:31 2023 +0200 show notification on username change + fix userEmail checks commit6a6f55f6fcMerge: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 commitf9ff5e5c55Author: negue <eugen.bolz@gmail.com> Date: Tue May 30 22:41:42 2023 +0200 check password inputs and mark invalid for "password change" setting commit4497514eebAuthor: negue <eugen.bolz@gmail.com> Date: Tue May 30 21:59:21 2023 +0200 show notification when chaning display name commit3232f12f0dAuthor: 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" commit582a2f1304Author: 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 commit8e3b8a962aAuthor: negue <eugen.bolz@gmail.com> Date: Tue May 30 21:24:46 2023 +0200 refactor currentPasswordInput.vue to use validatedTextInput.vue commit61521507a4Author: 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 commitf74c29a065Merge: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 commitc4b6f0c39cMerge: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 commit37eee140adAuthor: negue <eugen.bolz@gmail.com> Date: Fri May 12 21:57:27 2023 +0200 delete account without password commit48a6801f4eAuthor: negue <eugen.bolz@gmail.com> Date: Mon May 8 22:06:29 2023 +0200 fix duplicate json entry commit47a2189f49Merge: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 commita56b4a4457Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 21:37:31 2023 +0200 show current class on setting panel commit9c973cca2aAuthor: negue <eugen.bolz@gmail.com> Date: Mon May 8 21:15:46 2023 +0200 fix selectDifficulty.vue - refactor selectList.vue commit95b37b3ba3Author: negue <eugen.bolz@gmail.com> Date: Mon May 8 20:45:09 2023 +0200 migrate restoreValues fix to new setting component commit7947b1c67dMerge: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 commitad3e4d604aAuthor: negue <eugen.bolz@gmail.com> Date: Sat Apr 29 01:18:25 2023 +0200 style fixes commitcea13d5bc3Merge: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 commit73a5e5fcabAuthor: negue <eugen.bolz@gmail.com> Date: Tue Apr 25 20:51:14 2023 +0200 style / padding issues commit0a10eb32ccAuthor: negue <eugen.bolz@gmail.com> Date: Sat Apr 15 20:54:08 2023 +0200 fix "setting new password" invalid check commita79bec3fa5Author: negue <eugen.bolz@gmail.com> Date: Tue Apr 11 23:15:15 2023 +0200 add password for other logins commit9ff17fd6ddAuthor: negue <eugen.bolz@gmail.com> Date: Tue Apr 11 23:05:19 2023 +0200 "fix values" use keydown event to mark as change commit1f470942a9Author: negue <eugen.bolz@gmail.com> Date: Thu Apr 6 00:19:18 2023 +0200 delete old api.vue commitb4904a8b84Merge: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 commitb5da7ccc70Author: negue <eugen.bolz@gmail.com> Date: Thu Apr 6 00:11:36 2023 +0200 refactor webhook ui to use save/cancel buttons commitf49f67ff5cAuthor: negue <eugen.bolz@gmail.com> Date: Wed Apr 5 22:56:37 2023 +0200 remove unused settings commitcc73b44b25Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 29 23:40:30 2023 +0200 remove advancedCollapsed settings to start it opened commite0300e8710Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 29 22:58:09 2023 +0200 remove displayInviteToPartyWhenPartyIs1 setting commit1741ddfc64Author: negue <eugen.bolz@gmail.com> Date: Mon Mar 20 23:00:17 2023 +0100 webhook margins commit24a43d027cAuthor: negue <eugen.bolz@gmail.com> Date: Mon Mar 20 22:40:19 2023 +0100 userid tooltip commit42fcb20bc4Author: negue <eugen.bolz@gmail.com> Date: Thu Mar 16 00:51:10 2023 +0100 remove balance for choosing class commit160848473dAuthor: negue <eugen.bolz@gmail.com> Date: Thu Mar 16 00:20:56 2023 +0100 show real class setting modal if enough gems available commitf74ba9738dAuthor: negue <eugen.bolz@gmail.com> Date: Thu Mar 16 00:10:53 2023 +0100 update apple icon and size commitbf961bc728Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:59:42 2023 +0100 Copied API Token Notification commit28f0220b4eAuthor: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:53:33 2023 +0100 remove blue color of setting links commitb53ccace95Author: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:43:06 2023 +0100 fix username/email setting input width commit1dfa5b275dAuthor: negue <eugen.bolz@gmail.com> Date: Wed Mar 15 23:11:32 2023 +0100 developer mode commit776618d2dbAuthor: negue <eugen.bolz@gmail.com> Date: Tue Mar 14 21:11:52 2023 +0100 Add new Pause Dailies Setting commit576c80af7eMerge: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 commitdec1a1159dAuthor: negue <eugen.bolz@gmail.com> Date: Tue Mar 14 21:00:52 2023 +0100 developer mode dummy row commit1e80a7d145Author: negue <eugen.bolz@gmail.com> Date: Sat Mar 11 00:03:33 2023 +0100 WIP webhook row commitcc4bedbe2dAuthor: 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 commitf9833aa78aAuthor: negue <eugen.bolz@gmail.com> Date: Thu Mar 9 02:23:39 2023 +0100 API Token Row commit123c9b9bb1Author: negue <eugen.bolz@gmail.com> Date: Mon Mar 6 22:46:50 2023 +0100 "Your User Data" Row instead of Page commit0ade5663aeAuthor: negue <eugen.bolz@gmail.com> Date: Fri Mar 3 22:43:03 2023 +0100 userid row commitb4f2236ab8Author: negue <eugen.bolz@gmail.com> Date: Fri Mar 3 22:22:32 2023 +0100 rename folder of setting rows commit3b050861c4Author: 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 commitb09298fb01Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 20:56:03 2023 +0100 move taskSettings.vue and add it to the settings list commit5ed25066ecAuthor: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 20:06:13 2023 +0100 size/margin for transactions commit25e77cbd95Author: negue <eugen.bolz@gmail.com> Date: Tue Feb 21 19:52:12 2023 +0100 move purchaseHistory.vue commit8e4e1bcb0fMerge:bb14d09aa485c50d50e9Author: 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 commit85c50d50e9Author: SabreCat <sabe@habitica.com> Date: Thu Feb 16 14:23:27 2023 -0600 fix(css): remove redundant formatting for a elements commitbb14d09aa4Author: negue <eugen.bolz@gmail.com> Date: Thu Feb 16 01:34:09 2023 +0100 remove console commit8c5e722c72Author: negue <eugen.bolz@gmail.com> Date: Thu Feb 16 01:26:43 2023 +0100 first try with the refactored UI of Login Methods commit9c8770051dAuthor: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 19:13:16 2023 +0100 fix dayStartAdjustmentSetting.vue for 0 value commitee2ff3881bAuthor: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 18:37:46 2023 +0100 fix color after refactor commit121e7485caAuthor: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 18:29:00 2023 +0100 mark audioThemeSetting as changed commit98c6570003Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 18:05:55 2023 +0100 fix ul/li style in resetAccount.vue commitfed824f705Author: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 17:49:36 2023 +0100 fix color of gem price commit80365e537dAuthor: negue <eugen.bolz@gmail.com> Date: Sat Feb 11 17:44:55 2023 +0100 fix "fixValuesSetting.vue" commitd3e15c5413Author: negue <eugen.bolz@gmail.com> Date: Wed Feb 8 01:06:27 2023 +0100 open forgot password in new tab commit31edec9ec5Author: 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 commit2adfd8c259Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 20:19:30 2023 +0100 hide class setting until level 10 commit64fb4c0cf9Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 19:32:40 2023 +0100 delete old modals (refactored into new settings ui) commitb5be137a8dAuthor: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 19:27:26 2023 +0100 enable forgot password link in settings commitbec75c6e12Author: negue <eugen.bolz@gmail.com> Date: Sun Feb 5 18:52:54 2023 +0100 reset account + password required in api commit64f7e7a1d9Author: negue <eugen.bolz@gmail.com> Date: Mon Jan 30 23:22:55 2023 +0100 fix compile commit7ffb5101beMerge: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 commit2bfb130b92Author: 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 commit89530a133cAuthor: negue <eugen.bolz@gmail.com> Date: Wed Jan 18 19:22:36 2023 +0100 wip fix values commit428647fc71Author: 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 commit1f16819bc1Author: negue <eugen.bolz@gmail.com> Date: Wed Jan 11 22:41:05 2023 +0100 WIP fix values commit6fef3d0579Author: negue <eugen.bolz@gmail.com> Date: Sat Jan 7 22:51:30 2023 +0100 check for unsaved changes when pressing cancel commitbef8a4cdfcMerge: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 commit494f32c3e3Author: negue <eugen.bolz@gmail.com> Date: Wed Dec 21 00:55:31 2022 +0100 Class Setting commitbda210cfbbAuthor: negue <eugen.bolz@gmail.com> Date: Tue Dec 20 23:01:41 2022 +0100 removes username, email and display name from site.vue commit38198d7df6Author: negue <eugen.bolz@gmail.com> Date: Tue Dec 20 22:36:27 2022 +0100 WIP class setting commitdddcfa637fAuthor: negue <eugen.bolz@gmail.com> Date: Tue Dec 20 22:31:36 2022 +0100 fix styles commitce0a5cf974Author: negue <eugen.bolz@gmail.com> Date: Sun Dec 11 23:57:07 2022 +0100 Scroll into opened Setting commit7e0a95ddffAuthor: negue <eugen.bolz@gmail.com> Date: Sun Dec 11 23:43:44 2022 +0100 Audio Theme Setting commit9c556662feAuthor: negue <eugen.bolz@gmail.com> Date: Sun Dec 11 00:25:30 2022 +0100 prepare header settings but still hidden commit30d8b27534Merge: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 commita1d1a788b2Author: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 23:34:33 2022 +0100 DayStartAdjustmentSetting commitddee94a393Author: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 20:00:12 2022 +0100 disable reset account button when password empty commit30a6db4c2dAuthor: negue <eugen.bolz@gmail.com> Date: Sat Dec 10 19:54:21 2022 +0100 hide & reset previous setting when switching to a different one commit78093848d7Author: negue <eugen.bolz@gmail.com> Date: Wed Dec 7 22:19:15 2022 +0100 validated text input (in/valid border color + icon) commite1b444ea63Author: negue <eugen.bolz@gmail.com> Date: Tue Dec 6 22:09:54 2022 +0100 re-enable box-shadow on hover commit96dc4e47aeAuthor: negue <negue@users.noreply.github.com> Date: Mon Nov 28 01:13:47 2022 +0100 remove console log commit69ad07daadAuthor: negue <negue@users.noreply.github.com> Date: Mon Nov 28 01:01:17 2022 +0100 dateFormatSetting commitbc11c0cf75Author: negue <negue@users.noreply.github.com> Date: Mon Nov 28 00:49:24 2022 +0100 move shared components / mixins commit0d1a189c64Author: negue <negue@users.noreply.github.com> Date: Mon Nov 28 00:44:21 2022 +0100 language Setting + imports cleanup commit29ebd89030Author: negue <negue@users.noreply.github.com> Date: Sun Nov 27 23:23:02 2022 +0100 fix icon size + fix display name valid checks commit5c7747517bMerge: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 commitfd5cbc3026Author: negue <negue@users.noreply.github.com> Date: Wed Nov 23 00:14:21 2022 +0100 fix conflicts commit49361217b0Merge: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 commitedb427158fAuthor: negue <negue@users.noreply.github.com> Date: Wed Nov 23 00:03:19 2022 +0100 disable save button if nothing was changed commitc7e40e9446Author: negue <negue@users.noreply.github.com> Date: Tue Nov 22 23:36:37 2022 +0100 delete account row commit4bf740c531Author: negue <negue@users.noreply.github.com> Date: Tue Nov 22 23:14:24 2022 +0100 Shared Modal Visible State commitd718153717Author: negue <negue@users.noreply.github.com> Date: Sun Nov 20 18:06:20 2022 +0100 resetAccount commite25922f8b3Author: negue <negue@users.noreply.github.com> Date: Wed Nov 16 23:39:26 2022 +0100 rename functional components for compiler commitfdbc2c0eeeAuthor: negue <negue@users.noreply.github.com> Date: Wed Nov 16 01:44:50 2022 +0100 password setting row commit5fd5e6275aAuthor: negue <negue@users.noreply.github.com> Date: Tue Nov 15 17:35:44 2022 +0100 update package-lock.json again commit9d742fd9a1Author: negue <negue@users.noreply.github.com> Date: Tue Nov 15 17:24:15 2022 +0100 update package-lock.json commitcd588e74d5Author: negue <negue@users.noreply.github.com> Date: Mon Nov 14 02:12:39 2022 +0100 displayNameSetting.vue commit265970c5efAuthor: negue <negue@users.noreply.github.com> Date: Mon Nov 14 02:09:47 2022 +0100 fix lint commita2b510cacaMerge: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 commit0bae5fbe02Author: negue <negue@users.noreply.github.com> Date: Sun Nov 13 22:00:34 2022 +0100 userEmailSetting commit23da70fa2eAuthor: negue <negue@users.noreply.github.com> Date: Sun Nov 13 20:38:14 2022 +0100 extract save / cancel buttons and the shared inlineSetting "logic" commit82047380f3Author: negue <negue@users.noreply.github.com> Date: Sun Nov 13 20:18:21 2022 +0100 first setting (username) in the new layout commit39150349c7Author: negue <negue@users.noreply.github.com> Date: Wed Nov 2 21:42:12 2022 +0100 Working on M1 - will be reverted on full merge commitf7787b318cMerge: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 commit4c0ecc9938Merge: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 commit2f53613a45Author: negue <eugen.bolz@gmail.com> Date: Mon Oct 10 22:54:41 2022 +0200 split routes for ease of dev commit390f0fc69dMerge: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 commitcf222ee63aAuthor: negue <eugen.bolz@gmail.com> Date: Sun Oct 2 23:15:35 2022 +0200 Update remaining Notification labels commitf837cce125Author: negue <eugen.bolz@gmail.com> Date: Sun Oct 2 22:45:12 2022 +0200 move site popup settings to notifications commitfc5181c3a7Author: negue <eugen.bolz@gmail.com> Date: Sun Oct 2 21:12:24 2022 +0200 fix styling in notification settings commit7b5568ed23Author: 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-babel": "^8.0.0",
|
||||||
"gulp-imagemin": "^7.1.0",
|
"gulp-imagemin": "^7.1.0",
|
||||||
"gulp-nodemon": "^2.5.0",
|
"gulp-nodemon": "^2.5.0",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
"gulp.spritesmith": "^6.13.0",
|
"gulp.spritesmith": "^6.13.0",
|
||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^3.0.0",
|
||||||
"helmet": "^4.6.0",
|
"helmet": "^4.6.0",
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ describe('POST /user/reset', () => {
|
|||||||
type: 'habit',
|
type: 'habit',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.post('/user/reset');
|
await user.post('/user/reset', {
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||||
@@ -39,7 +41,9 @@ describe('POST /user/reset', () => {
|
|||||||
type: 'daily',
|
type: 'daily',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.post('/user/reset');
|
await user.post('/user/reset', {
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||||
@@ -57,7 +61,9 @@ describe('POST /user/reset', () => {
|
|||||||
type: 'todo',
|
type: 'todo',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.post('/user/reset');
|
await user.post('/user/reset', {
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
||||||
@@ -75,7 +81,9 @@ describe('POST /user/reset', () => {
|
|||||||
type: 'reward',
|
type: 'reward',
|
||||||
});
|
});
|
||||||
|
|
||||||
await user.post('/user/reset');
|
await user.post('/user/reset', {
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({
|
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;
|
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 () => {
|
it('does not delete challenge or group tasks', async () => {
|
||||||
const guild = await generateGroup(user, {}, { 'purchased.plan.customerId': 'group-unlimited' });
|
const guild = await generateGroup(user, {}, { 'purchased.plan.customerId': 'group-unlimited' });
|
||||||
const challenge = await generateChallenge(user, guild);
|
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(`/tasks/${groupTask._id}/assign`, [user._id]);
|
||||||
|
|
||||||
await user.post('/user/reset');
|
await user.post('/user/reset', {
|
||||||
|
password: 'password',
|
||||||
|
});
|
||||||
await user.sync();
|
await user.sync();
|
||||||
|
|
||||||
await user.put('/user', {
|
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}`);
|
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)._editing).not.be.ok;
|
||||||
expect(addTask(user)._edit).to.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/no-unresolved': 'off',
|
||||||
'import/extensions': 'off',
|
'import/extensions': 'off',
|
||||||
'vue/no-v-html': 'off',
|
'vue/no-v-html': 'off',
|
||||||
|
'vue/no-mutating-props': 'warn',
|
||||||
'vue/html-self-closing': ['error', {
|
'vue/html-self-closing': ['error', {
|
||||||
html: {
|
html: {
|
||||||
void: 'never',
|
void: 'never',
|
||||||
|
|||||||
@@ -55,3 +55,13 @@ in a separate `.add('function of component', ...`
|
|||||||
### Storybook Build
|
### Storybook Build
|
||||||
|
|
||||||
After each client build, storybook build is also triggered and will be available in `dist/storybook`
|
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": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"assert": {
|
"assert": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz",
|
||||||
"integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
|
"integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"object-assign": "^4.1.1",
|
"object.assign": "^4.1.4",
|
||||||
"util": "0.10.3"
|
"util": "^0.10.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"inherits": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
|
"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": {
|
"util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "2.0.1"
|
"inherits": "2.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15543,9 +15568,9 @@
|
|||||||
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
"integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w=="
|
||||||
},
|
},
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "5.1.3",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
|
||||||
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ=="
|
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
|
||||||
},
|
},
|
||||||
"bonjour": {
|
"bonjour": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
@@ -15762,7 +15787,7 @@
|
|||||||
"brorand": {
|
"brorand": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
|
||||||
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
|
"integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
|
||||||
},
|
},
|
||||||
"browser-process-hrtime": {
|
"browser-process-hrtime": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -15834,9 +15859,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"string_decoder": "^1.1.1",
|
"string_decoder": "^1.1.1",
|
||||||
@@ -15915,12 +15940,12 @@
|
|||||||
"buffer-xor": {
|
"buffer-xor": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
|
||||||
"integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
|
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ=="
|
||||||
},
|
},
|
||||||
"builtin-status-codes": {
|
"builtin-status-codes": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
|
"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": {
|
"cacache": {
|
||||||
"version": "12.0.3",
|
"version": "12.0.3",
|
||||||
@@ -16793,7 +16818,7 @@
|
|||||||
"constants-browserify": {
|
"constants-browserify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
"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": {
|
"contains-path": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@@ -16907,9 +16932,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.32.1",
|
"version": "3.32.2",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.1.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.2.tgz",
|
||||||
"integrity": "sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ=="
|
"integrity": "sha512-pxXSw1mYZPDGvTQqEc5vgIb83jGQKFGYWY76z4a7weZXUolw3G+OvpZqSRcfYOoOVUQJYEPsWeQK8pKEnUtWxQ=="
|
||||||
},
|
},
|
||||||
"core-js-compat": {
|
"core-js-compat": {
|
||||||
"version": "3.11.0",
|
"version": "3.11.0",
|
||||||
@@ -17037,9 +17062,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -17376,7 +17401,7 @@
|
|||||||
"de-indent": {
|
"de-indent": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||||
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0="
|
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@@ -17670,9 +17695,9 @@
|
|||||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||||
},
|
},
|
||||||
"des.js": {
|
"des.js": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
|
||||||
"integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==",
|
"integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
"minimalistic-assert": "^1.0.0"
|
"minimalistic-assert": "^1.0.0"
|
||||||
@@ -17819,9 +17844,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -18598,13 +18623,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-plugin-vue": {
|
"eslint-plugin-vue": {
|
||||||
"version": "6.2.2",
|
"version": "7.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.20.0.tgz",
|
||||||
"integrity": "sha512-Nhc+oVAHm0uz/PkJAWscwIT4ijTrK5fqNqz9QB1D35SbbuMG1uB6Yr5AJpvPSWg+WOw7nYNswerYh0kOk64gqQ==",
|
"integrity": "sha512-oVNDqzBC9h3GO+NTgWeLMhhGigy6/bQaQbHS+0z7C4YEu/qK/yxHvca/2PTZtGNPsCrHwOTgKMrwu02A9iPBmw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"eslint-utils": "^2.1.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"semver": "^5.6.0",
|
"semver": "^6.3.0",
|
||||||
"vue-eslint-parser": "^7.0.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": {
|
"eslint-scope": {
|
||||||
@@ -20578,9 +20619,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"string_decoder": "^1.1.1",
|
"string_decoder": "^1.1.1",
|
||||||
@@ -20724,7 +20765,7 @@
|
|||||||
"hmac-drbg": {
|
"hmac-drbg": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
|
||||||
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
|
"integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"hash.js": "^1.0.3",
|
"hash.js": "^1.0.3",
|
||||||
"minimalistic-assert": "^1.0.0",
|
"minimalistic-assert": "^1.0.0",
|
||||||
@@ -21030,7 +21071,7 @@
|
|||||||
"https-browserify": {
|
"https-browserify": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
"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": {
|
"https-proxy-agent": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
@@ -21886,7 +21927,7 @@
|
|||||||
"is-window": {
|
"is-window": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz",
|
||||||
"integrity": "sha1-LIlspT25feRdPDMTOmXYyfVjSA0="
|
"integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg=="
|
||||||
},
|
},
|
||||||
"is-windows": {
|
"is-windows": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -23254,9 +23295,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -23323,7 +23364,7 @@
|
|||||||
"minimalistic-crypto-utils": {
|
"minimalistic-crypto-utils": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
|
||||||
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
|
"integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
@@ -24388,7 +24429,7 @@
|
|||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
"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": {
|
"os-browserify": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
|
"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": {
|
"os-homedir": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -25089,9 +25130,9 @@
|
|||||||
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="
|
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ=="
|
||||||
},
|
},
|
||||||
"pbkdf2": {
|
"pbkdf2": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
|
||||||
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
|
"integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"create-hash": "^1.1.2",
|
"create-hash": "^1.1.2",
|
||||||
"create-hmac": "^1.1.4",
|
"create-hmac": "^1.1.4",
|
||||||
@@ -26147,9 +26188,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.9",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -26350,7 +26391,7 @@
|
|||||||
"querystring-es3": {
|
"querystring-es3": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
|
||||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
|
"integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA=="
|
||||||
},
|
},
|
||||||
"querystringify": {
|
"querystringify": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
@@ -29503,7 +29544,7 @@
|
|||||||
"to-arraybuffer": {
|
"to-arraybuffer": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
|
"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": {
|
"to-fast-properties": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -29779,7 +29820,7 @@
|
|||||||
"tty-browserify": {
|
"tty-browserify": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||||
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
|
"integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw=="
|
||||||
},
|
},
|
||||||
"tunnel-agent": {
|
"tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
@@ -30249,7 +30290,7 @@
|
|||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
"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": {
|
"uuid-browser": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz",
|
"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": {
|
"v8-compile-cache": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@@ -30579,29 +30620,90 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-eslint-parser": {
|
"vue-eslint-parser": {
|
||||||
"version": "7.0.0",
|
"version": "7.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
|
||||||
"integrity": "sha512-yR0dLxsTT7JfD2YQo9BhnQ6bUTLsZouuzt9SKRP7XNaZJV459gvlsJo4vT2nhZ/2dH9j3c53bIx9dnqU2prM9g==",
|
"integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"eslint-scope": "^5.0.0",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-visitor-keys": "^1.1.0",
|
"eslint-visitor-keys": "^1.1.0",
|
||||||
"espree": "^6.1.2",
|
"espree": "^6.2.1",
|
||||||
"esquery": "^1.0.1",
|
"esquery": "^1.4.0",
|
||||||
"lodash": "^4.17.15"
|
"lodash": "^4.17.21",
|
||||||
|
"semver": "^6.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"eslint-scope": {
|
||||||
"version": "5.0.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||||
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
|
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"esrecurse": "^4.1.0",
|
"esrecurse": "^4.3.0",
|
||||||
"estraverse": "^4.1.1"
|
"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": {
|
"vue-functional-data-merge": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
|
"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": {
|
"vue-template-compiler": {
|
||||||
"version": "2.7.10",
|
"version": "2.7.10",
|
||||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz",
|
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz",
|
||||||
@@ -30728,9 +31164,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": {
|
"anymatch": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@@ -30753,19 +31189,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "~3.1.1",
|
"anymatch": "~3.1.2",
|
||||||
"braces": "~3.0.2",
|
"braces": "~3.0.2",
|
||||||
"fsevents": "~2.3.1",
|
"fsevents": "~2.3.2",
|
||||||
"glob-parent": "~5.1.0",
|
"glob-parent": "~5.1.2",
|
||||||
"is-binary-path": "~2.1.0",
|
"is-binary-path": "~2.1.0",
|
||||||
"is-glob": "~4.0.1",
|
"is-glob": "~4.0.1",
|
||||||
"normalize-path": "~3.0.0",
|
"normalize-path": "~3.0.0",
|
||||||
"readdirp": "~3.5.0"
|
"readdirp": "~3.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
@@ -30778,15 +31214,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fsevents": {
|
"fsevents": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -30808,9 +31244,9 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"readdirp": {
|
"readdirp": {
|
||||||
"version": "3.5.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@@ -30863,9 +31299,9 @@
|
|||||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
||||||
},
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "4.46.0",
|
"version": "4.47.0",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.47.0.tgz",
|
||||||
"integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==",
|
"integrity": "sha512-td7fYwgLSrky3fI1EuU5cneU4+pbH6GgOfuKNS1tNPcfdGinGELAqsb/BP4nnvZyKSG2i/xFGU7+n2PvZA8HJQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@webassemblyjs/ast": "1.9.0",
|
"@webassemblyjs/ast": "1.9.0",
|
||||||
"@webassemblyjs/helper-module-context": "1.9.0",
|
"@webassemblyjs/helper-module-context": "1.9.0",
|
||||||
@@ -30890,37 +31326,6 @@
|
|||||||
"terser-webpack-plugin": "^1.4.3",
|
"terser-webpack-plugin": "^1.4.3",
|
||||||
"watchpack": "^1.7.4",
|
"watchpack": "^1.7.4",
|
||||||
"webpack-sources": "^1.4.1"
|
"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": {
|
"webpack-bundle-analyzer": {
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
"bootstrap": "^4.6.0",
|
"bootstrap": "^4.6.0",
|
||||||
"bootstrap-vue": "^2.23.1",
|
"bootstrap-vue": "^2.23.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"core-js": "^3.32.1",
|
"core-js": "^3.32.2",
|
||||||
"dompurify": "^3.0.3",
|
"dompurify": "^3.0.3",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-habitrpg": "^6.2.0",
|
"eslint-config-habitrpg": "^6.2.0",
|
||||||
"eslint-plugin-mocha": "^5.3.0",
|
"eslint-plugin-mocha": "^5.3.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^7.20.0",
|
||||||
"habitica-markdown": "^3.0.0",
|
"habitica-markdown": "^3.0.0",
|
||||||
"hellojs": "^1.20.0",
|
"hellojs": "^1.20.0",
|
||||||
"inspectpack": "^4.7.1",
|
"inspectpack": "^4.7.1",
|
||||||
@@ -58,12 +58,14 @@
|
|||||||
"validator": "^13.9.0",
|
"validator": "^13.9.0",
|
||||||
"vue": "^2.7.10",
|
"vue": "^2.7.10",
|
||||||
"vue-cli-plugin-storybook": "2.1.0",
|
"vue-cli-plugin-storybook": "2.1.0",
|
||||||
|
"vue-fragment": "^1.6.0",
|
||||||
"vue-mugen-scroll": "^0.2.6",
|
"vue-mugen-scroll": "^0.2.6",
|
||||||
"vue-router": "^3.6.5",
|
"vue-router": "^3.6.5",
|
||||||
"vue-template-compiler": "^2.7.10",
|
"vue-template-compiler": "^2.7.10",
|
||||||
|
"vue-template-babel-compiler": "^2.0.0",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
|
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#153d339e4dbebb73733658aeda1d5b7fcc55b0a0",
|
||||||
"webpack": "^4.46.0"
|
"webpack": "^4.47.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
box-shadow: 0 3px 6px 0 rgba($black, 0.16), 0 3px 6px 0 rgba($black, 0.24);
|
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;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,6 +264,10 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
color: $blue-10;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-small {
|
.btn-small {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.33;
|
line-height: 1.33;
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
.dropdown-toggle:hover {
|
.dropdown-toggle:hover {
|
||||||
--caret-color: #{$purple-300};
|
--caret-color: #{$purple-300};
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown.show > .dropdown-toggle:not(.btn-success) {
|
.dropdown.show > .dropdown-toggle:not(.btn-success) {
|
||||||
@@ -136,6 +140,8 @@
|
|||||||
|
|
||||||
.dropdown-menu.show {
|
.dropdown-menu.show {
|
||||||
min-width: 100% !important;
|
min-width: 100% !important;
|
||||||
|
overflow: scroll;
|
||||||
|
max-height: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ input, textarea, input.form-control, textarea.form-control {
|
|||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
border: 1px solid $gray-400;
|
border: 1px solid $gray-400;
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled):not(:read-only) {
|
||||||
border-color: $gray-300;
|
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;
|
border-color: $purple-400;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -56,13 +56,13 @@ input, textarea, input.form-control, textarea.form-control {
|
|||||||
|
|
||||||
&.input-valid, &.input-invalid {
|
&.input-valid, &.input-invalid {
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center right 16px;
|
background-position: center right 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-valid {
|
&.input-valid {
|
||||||
padding-right: 37px;
|
padding-right: 27px;
|
||||||
background-image: url(~@/assets/svg/for-css/check.svg);
|
background-image: url(~@/assets/svg/for-css/check.svg);
|
||||||
background-size: 13px 10px;
|
background-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.input-invalid {
|
&.input-invalid {
|
||||||
@@ -91,8 +91,10 @@ input, textarea, input.form-control, textarea.form-control {
|
|||||||
border-color: $gray-300;
|
border-color: $gray-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus, &:active, &:focus-within {
|
&:not(:read-only) {
|
||||||
border: solid 1px $purple-400;
|
&:focus, &:active, &:focus-within {
|
||||||
|
border: solid 1px $purple-400;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-prepend , .input-group-append {
|
.input-group-prepend , .input-group-append {
|
||||||
@@ -163,8 +165,22 @@ input, textarea, input.form-control, textarea.form-control {
|
|||||||
input {
|
input {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border: 0;
|
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 {
|
.input-group-spaced {
|
||||||
@@ -231,20 +247,20 @@ $bg-disabled-control: $gray-10;
|
|||||||
background-color: inherit;
|
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 {
|
&:active:not(:checked):not(:disabled)~.custom-control-label::before {
|
||||||
border: 2px solid $gray-300;
|
border: 2px solid $gray-300;
|
||||||
box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5);
|
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 {
|
&:active:checked:not(:disabled)~.custom-control-label::before {
|
||||||
box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5);
|
box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5);
|
||||||
border-color: 2 px solid $purple-400;
|
border-color: 2 px solid $purple-400;
|
||||||
background-color: $purple-400;
|
background-color: $purple-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus:disabled~.custom-control-label::before,
|
&:focus:disabled~.custom-control-label::before,
|
||||||
&:active:disabled~.custom-control-label::before {
|
&:active:disabled~.custom-control-label::before {
|
||||||
box-shadow: 0 0 0 6px rgba($bg-disabled-control, 0.1);
|
box-shadow: 0 0 0 6px rgba($bg-disabled-control, 0.1);
|
||||||
}
|
}
|
||||||
@@ -398,8 +414,6 @@ $bg-color: $purple-400;
|
|||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Disable default style Firefox for invalid elements.
|
// Disable default style Firefox for invalid elements.
|
||||||
// Selectors taken from view-source:resource://gre-resources/forms.css on Firefox
|
// Selectors taken from view-source:resource://gre-resources/forms.css on Firefox
|
||||||
:not(output):-moz-ui-invalid {
|
:not(output):-moz-ui-invalid {
|
||||||
|
|||||||
@@ -1,55 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="13" height="16" viewBox="0 0 13 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
<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"/>
|
||||||
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>
|
</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">
|
<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">
|
||||||
<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"/>
|
<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>
|
</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 () {
|
mounted () {
|
||||||
|
this.forgotPassword = this.$route.path.startsWith('/forgot-password');
|
||||||
|
|
||||||
hello.init({
|
hello.init({
|
||||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -245,12 +245,13 @@ import notifications from '@/mixins/notifications';
|
|||||||
import closeX from '../ui/closeX';
|
import closeX from '../ui/closeX';
|
||||||
|
|
||||||
import copyIcon from '@/assets/svg/copy.svg';
|
import copyIcon from '@/assets/svg/copy.svg';
|
||||||
|
import copyToClipboard from '@/mixins/copyToClipboard';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
closeX,
|
closeX,
|
||||||
},
|
},
|
||||||
mixins: [notifications],
|
mixins: [notifications, copyToClipboard],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
@@ -287,17 +288,10 @@ export default {
|
|||||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||||
},
|
},
|
||||||
copyUsername () {
|
copyUsername () {
|
||||||
if (navigator.clipboard) {
|
this.mixinCopyToClipboard(
|
||||||
navigator.clipboard.writeText(this.user.auth.local.username);
|
this.user.auth.local.username,
|
||||||
} else {
|
this.$t('usernameCopied'),
|
||||||
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'));
|
|
||||||
},
|
},
|
||||||
seekParty () {
|
seekParty () {
|
||||||
this.$store.dispatch('user:set', {
|
this.$store.dispatch('user:set', {
|
||||||
|
|||||||
@@ -86,11 +86,6 @@
|
|||||||
color: $gray-50;
|
color: $gray-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-error {
|
|
||||||
color: $red-50;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
.input-group {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: solid 1px $gray-400;
|
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 { hasCompletedOnboarding } from '@/../../common/script/libs/onboarding';
|
||||||
import notificationsIcon from '@/assets/svg/notifications.svg';
|
import notificationsIcon from '@/assets/svg/notifications.svg';
|
||||||
import MenuDropdown from '../ui/customMenuDropdown';
|
import MenuDropdown from '../ui/customMenuDropdown';
|
||||||
import MessageCount from './messageCount';
|
import MessageCount from './messageCount.functional.vue';
|
||||||
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
|
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
|
||||||
import successImage from '@/assets/svg/success.svg';
|
import successImage from '@/assets/svg/success.svg';
|
||||||
import starBadge from '@/assets/svg/star-badge.svg';
|
import starBadge from '@/assets/svg/star-badge.svg';
|
||||||
|
|
||||||
// Notifications
|
// Notifications
|
||||||
import CARD_RECEIVED from './notifications/cardReceived';
|
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 GIFT_ONE_GET_ONE from './notifications/g1g1';
|
||||||
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||||
import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
import GROUP_TASK_CLAIMED from './notifications/groupTaskClaimed';
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
>{{ $t('achievements') }}</a>
|
>{{ $t('achievements') }}</a>
|
||||||
<router-link
|
<router-link
|
||||||
class="topbar-dropdown-item dropdown-item"
|
class="topbar-dropdown-item dropdown-item"
|
||||||
:to="{name: 'site'}"
|
:to="{name: 'general'}"
|
||||||
>
|
>
|
||||||
{{ $t('settings') }}
|
{{ $t('settings') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import userIcon from '@/assets/svg/user.svg';
|
import userIcon from '@/assets/svg/user.svg';
|
||||||
import MenuDropdown from '../ui/customMenuDropdown';
|
import MenuDropdown from '../ui/customMenuDropdown';
|
||||||
import MessageCount from './messageCount';
|
import MessageCount from './messageCount.functional.vue';
|
||||||
import { EVENTS } from '@/libs/events';
|
import { EVENTS } from '@/libs/events';
|
||||||
import { PAGES } from '@/libs/consts';
|
import { PAGES } from '@/libs/consts';
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
<div v-if="currentDraggingEgg != null">
|
<div v-if="currentDraggingEgg != null">
|
||||||
<div
|
<div
|
||||||
class="potion-icon"
|
class="potion-icon"
|
||||||
:class="'Pet_Egg_'+currentDraggingEgg.key"
|
:class="`Pet_Egg_${currentDraggingEgg.key}`"
|
||||||
></div>
|
></div>
|
||||||
<div class="popover">
|
<div class="popover">
|
||||||
<div class="popover-content">
|
<div class="popover-content">
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
<div v-if="currentDraggingEgg != null">
|
<div v-if="currentDraggingEgg != null">
|
||||||
<div
|
<div
|
||||||
class="potion-icon"
|
class="potion-icon"
|
||||||
:class="'Pet_Egg_'+currentDraggingEgg.key"
|
:class="`Pet_Egg_${currentDraggingEgg.key}`"
|
||||||
></div>
|
></div>
|
||||||
<div class="popover">
|
<div class="popover">
|
||||||
<div
|
<div
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
<div v-if="currentDraggingPotion != null">
|
<div v-if="currentDraggingPotion != null">
|
||||||
<div
|
<div
|
||||||
class="potion-icon"
|
class="potion-icon"
|
||||||
:class="'Pet_HatchingPotion_'+currentDraggingPotion.key"
|
:class="`Pet_HatchingPotion_${currentDraggingPotion.key}`"
|
||||||
></div>
|
></div>
|
||||||
<div class="popover">
|
<div class="popover">
|
||||||
<div
|
<div
|
||||||
@@ -285,7 +285,7 @@
|
|||||||
<div v-if="currentDraggingPotion != null">
|
<div v-if="currentDraggingPotion != null">
|
||||||
<div
|
<div
|
||||||
class="potion-icon"
|
class="potion-icon"
|
||||||
:class="'Pet_HatchingPotion_'+currentDraggingPotion.key"
|
:class="`Pet_HatchingPotion_${currentDraggingPotion.key}`"
|
||||||
></div>
|
></div>
|
||||||
<div class="popover">
|
<div class="popover">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<span
|
<span
|
||||||
v-drag.food="item.key"
|
v-drag.food="item.key"
|
||||||
class="item-content"
|
class="item-content"
|
||||||
:class="'Pet_Food_'+item.key"
|
:class="`Pet_Food_${item.key}`"
|
||||||
@itemDragEnd="dragend($event)"
|
@itemDragEnd="dragend($event)"
|
||||||
@itemDragStart="dragstart($event)"
|
@itemDragStart="dragstart($event)"
|
||||||
></span>
|
></span>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
>
|
>
|
||||||
<div class="potionEggGroup">
|
<div class="potionEggGroup">
|
||||||
<div class="potionEggBackground">
|
<div class="potionEggBackground">
|
||||||
<div :class="'Pet_HatchingPotion_'+hatchablePet.potionKey"></div>
|
<div :class="`Pet_HatchingPotion_${hatchablePet.potionKey}`"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="potionEggBackground">
|
<div class="potionEggBackground">
|
||||||
<div :class="'Pet_Egg_'+hatchablePet.eggKey"></div>
|
<div :class="`Pet_Egg_${hatchablePet.eggKey}`"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="title">
|
<h4 class="title">
|
||||||
|
|||||||
@@ -268,7 +268,7 @@
|
|||||||
<div v-if="currentDraggingFood != null">
|
<div v-if="currentDraggingFood != null">
|
||||||
<div
|
<div
|
||||||
class="food-icon"
|
class="food-icon"
|
||||||
:class="'Pet_Food_'+currentDraggingFood.key"
|
:class="`Pet_Food_${currentDraggingFood.key}`"
|
||||||
></div>
|
></div>
|
||||||
<div class="popover">
|
<div class="popover">
|
||||||
<div
|
<div
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
<div v-if="currentDraggingFood != null">
|
<div v-if="currentDraggingFood != null">
|
||||||
<div
|
<div
|
||||||
class="food-icon"
|
class="food-icon"
|
||||||
:class="'Pet_Food_'+currentDraggingFood.key"
|
:class="`Pet_Food_${currentDraggingFood.key}`"
|
||||||
></div>
|
></div>
|
||||||
<div class="popover">
|
<div class="popover">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -22,18 +22,19 @@
|
|||||||
v-if="currentEvent && currentEvent.promo === 'g1g1'"
|
v-if="currentEvent && currentEvent.promo === 'g1g1'"
|
||||||
class="g1g1-margin d-flex flex-column align-items-center"
|
class="g1g1-margin d-flex flex-column align-items-center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="svg-big-gift"
|
v-once
|
||||||
v-once
|
class="svg-big-gift"
|
||||||
v-html="icons.bigGift"
|
v-html="icons.bigGift"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="d-flex flex-column align-items-center">
|
class="d-flex flex-column align-items-center"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="svg-big-gift"
|
|
||||||
v-once
|
v-once
|
||||||
|
class="svg-big-gift"
|
||||||
v-html="icons.bigGift"
|
v-html="icons.bigGift"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,9 +50,10 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="modal-close"
|
class="modal-close"
|
||||||
@click="close()">
|
@click="close()"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="svg-icon"
|
class="svg-icon"
|
||||||
v-html="icons.close"
|
v-html="icons.close"
|
||||||
@@ -65,26 +67,15 @@
|
|||||||
name="selectUser"
|
name="selectUser"
|
||||||
novalidate="novalidate"
|
novalidate="novalidate"
|
||||||
>
|
>
|
||||||
<div class="input-group">
|
<validated-text-input
|
||||||
<input
|
id="selectUser"
|
||||||
id="selectUser"
|
v-model="userSearchTerm"
|
||||||
v-model="userSearchTerm"
|
:is-valid="foundUser._id"
|
||||||
class="form-control"
|
|
||||||
type="text"
|
:placeholder="$t('usernameOrUserId')"
|
||||||
ref="textBox"
|
:invalid-issues="userInputInvalidIssues"
|
||||||
: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>
|
|
||||||
<div class="d-flex flex-column justify-content-center align-items-middle mt-3">
|
<div class="d-flex flex-column justify-content-center align-items-middle mt-3">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary mx-auto mt-2"
|
class="btn btn-primary mx-auto mt-2"
|
||||||
@@ -104,16 +95,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
v-if="currentEvent && currentEvent.promo ==='g1g1'"
|
v-if="currentEvent && currentEvent.promo ==='g1g1'"
|
||||||
class="g1g1-cancel d-flex justify-content-center"
|
class="g1g1-cancel d-flex justify-content-center"
|
||||||
v-html="$t('cancel')"
|
@click="close()"
|
||||||
@click="close()"
|
v-html="$t('cancel')"
|
||||||
>
|
>
|
||||||
{{ $t('cancel') }}
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,182 +108,179 @@
|
|||||||
slot="modal-footer"
|
slot="modal-footer"
|
||||||
class="g1g1-fine-print text-center pt-3"
|
class="g1g1-fine-print text-center pt-3"
|
||||||
>
|
>
|
||||||
<strong>
|
<strong v-once>
|
||||||
{{ $t ('howItWorks') }}
|
{{ $t('howItWorks') }}
|
||||||
</strong>
|
</strong>
|
||||||
<p
|
<p
|
||||||
|
v-once
|
||||||
class="mx-5 mt-1"
|
class="mx-5 mt-1"
|
||||||
>
|
>
|
||||||
{{ $t ('g1g1HowItWorks') }}
|
{{ $t('g1g1HowItWorks') }}
|
||||||
</p>
|
</p>
|
||||||
<strong>
|
<strong v-once>
|
||||||
{{ $t ('limitations') }}
|
{{ $t('limitations') }}
|
||||||
</strong>
|
</strong>
|
||||||
<p
|
<p
|
||||||
|
v-once
|
||||||
class="mx-5 mt-1"
|
class="mx-5 mt-1"
|
||||||
>
|
>
|
||||||
{{ $t ('g1g1Limitations') }}
|
{{ $t('g1g1Limitations') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '~@/assets/scss/mixins.scss';
|
@import '~@/assets/scss/mixins.scss';
|
||||||
|
|
||||||
#select-user-modal {
|
#select-user-modal {
|
||||||
.modal-content {
|
.modal-content {
|
||||||
width:448px;
|
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 {
|
body.modal-open .modal {
|
||||||
margin-top: 0rem;
|
display: flex !important;
|
||||||
}
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-dialog {
|
body.modal-open .modal .modal-dialog {
|
||||||
width: 448px;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
a:not([href]) {
|
a:not([href]) {
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.71;
|
|
||||||
}
|
|
||||||
|
|
||||||
#selectUser {
|
font-size: 0.875rem;
|
||||||
width: 22rem;
|
line-height: 1.71;
|
||||||
border: 0px;
|
}
|
||||||
color: $gray-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g1g1 {
|
#selectUser {
|
||||||
background-image: url('~@/assets/images/g1g1-send.png');
|
width: 22rem;
|
||||||
background-size: 446px 152px;
|
border: 0px;
|
||||||
width: 446px;
|
color: $gray-50;
|
||||||
height: 152px;
|
}
|
||||||
margin: -16px 0px 0px -16px;
|
|
||||||
border-radius: 4.8px 4.8px 0px 0px;
|
.g1g1 {
|
||||||
padding: 24px;
|
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;
|
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 {
|
p {
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g1g1-cancel {
|
|
||||||
margin-top: 16px;
|
|
||||||
color: $blue-10;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.g1g1-fine-print {
|
|
||||||
color: $gray-100;
|
|
||||||
background-color: $gray-700;
|
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
line-height: 1.33;
|
line-height: 1.33;
|
||||||
|
margin-left: 4rem;
|
||||||
|
margin-right: 4rem;
|
||||||
|
margin-bottom: 0rem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.g1g1-modal-close {
|
.g1g1-margin {
|
||||||
position: absolute;
|
margin-top: 24px;
|
||||||
width: 18px;
|
}
|
||||||
height: 18px;
|
|
||||||
padding: 4px;
|
|
||||||
right: 16px;
|
|
||||||
top: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.g1g1-svg-icon {
|
.g1g1-cancel {
|
||||||
width: 12px;
|
margin-top: 16px;
|
||||||
height: 12px;
|
color: $blue-10;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
& ::v-deep svg path {
|
.g1g1-fine-print {
|
||||||
fill: #FFFFFF;
|
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 {
|
.g1g1-modal-dialog {
|
||||||
margin-top: 10vh;
|
margin-top: 10vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-error {
|
.input-group {
|
||||||
color: $red-50;
|
border-radius: 2px;
|
||||||
font-size: 90%;
|
border: solid 1px $gray-400;
|
||||||
width: 100%;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
.input-group:focus-within {
|
||||||
border-radius: 2px;
|
border-color: $purple-500;
|
||||||
border: solid 1px $gray-400;
|
}
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
.input-group:focus-within {
|
line-height: 1.75rem;
|
||||||
border-color: $purple-500;
|
color: $purple-300;
|
||||||
}
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
h2 {
|
|
||||||
font-size: 1.25rem;
|
.svg-big-gift {
|
||||||
line-height: 1.75rem;
|
width: 176px;
|
||||||
color: $purple-300;
|
height: 64px;
|
||||||
padding-top: 1rem;
|
}
|
||||||
}
|
|
||||||
|
.modal-close {
|
||||||
.svg-big-gift {
|
position: absolute;
|
||||||
width: 176px;
|
width: 18px;
|
||||||
height: 64px;
|
height: 18px;
|
||||||
}
|
padding: 4px;
|
||||||
|
right: 16px;
|
||||||
.modal-close {
|
top: 16px;
|
||||||
position: absolute;
|
cursor: pointer;
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
.svg-icon {
|
||||||
padding: 4px;
|
width: 12px;
|
||||||
right: 16px;
|
height: 12px;
|
||||||
top: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -308,8 +292,10 @@ import isUUID from 'validator/lib/isUUID';
|
|||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import closeIcon from '@/assets/svg/close.svg';
|
import closeIcon from '@/assets/svg/close.svg';
|
||||||
import bigGiftIcon from '@/assets/svg/big-gift.svg';
|
import bigGiftIcon from '@/assets/svg/big-gift.svg';
|
||||||
|
import ValidatedTextInput from '@/components/ui/validatedTextInput.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { ValidatedTextInput },
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
userNotFound: false,
|
userNotFound: false,
|
||||||
@@ -332,6 +318,12 @@ export default {
|
|||||||
if (this.userSearchTerm.length < 1) return true;
|
if (this.userSearchTerm.length < 1) return true;
|
||||||
return typeof this.foundUser._id === 'undefined';
|
return typeof this.foundUser._id === 'undefined';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userInputInvalidIssues () {
|
||||||
|
return this.userSearchTerm.length > 0 && this.userNotFound
|
||||||
|
? [this.$t('userWithUsernameOrUserIdNotFound')]
|
||||||
|
: [''];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
userSearchTerm: {
|
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 {
|
.input-error {
|
||||||
color: $red-50;
|
|
||||||
font-size: 90%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
:key="item.key"
|
:key="item.key"
|
||||||
:item="item"
|
:item="item"
|
||||||
:price="item.value"
|
:price="item.value"
|
||||||
:item-content-class="'shop_'+item.key"
|
:item-content-class="`shop_${item.key}`"
|
||||||
:empty-item="false"
|
:empty-item="false"
|
||||||
:popover-position="'top'"
|
:popover-position="'top'"
|
||||||
@click="featuredItemSelected(item)"
|
@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"
|
:item="item"
|
||||||
:price="item.goldValue ? item.goldValue : item.value"
|
:price="item.goldValue ? item.goldValue : item.value"
|
||||||
:price-type="item.goldValue ? 'gold' : 'gem'"
|
: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"
|
:empty-item="false"
|
||||||
:popover-position="'top'"
|
:popover-position="'top'"
|
||||||
@click="selectItem(item)"
|
@click="selectItem(item)"
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
|
|
||||||
<item-with-label
|
<item-with-label
|
||||||
v-for="drop in getDropsList(quest.drop.items, false)"
|
v-for="drop in getDropsList(quest.drop.items, false)"
|
||||||
:key="drop.type+'_'+drop.key"
|
:key="`${drop.type}_${drop.key}`"
|
||||||
:item="{}"
|
:item="{}"
|
||||||
class="item-with-label"
|
class="item-with-label"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -880,8 +880,6 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
hello.init({
|
hello.init({
|
||||||
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
|
|
||||||
// windows: WINDOWS_CLIENT_ID,
|
|
||||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||||
});
|
});
|
||||||
this.$store.dispatch('common:setTitle', {
|
this.$store.dispatch('common:setTitle', {
|
||||||
|
|||||||
@@ -682,7 +682,9 @@ export default {
|
|||||||
// as default filter for daily
|
// as default filter for daily
|
||||||
// and set the filter as 'due' only when the component first
|
// and set the filter as 'due' only when the component first
|
||||||
// loads and not on subsequent reloads.
|
// 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
|
filter = 'due'; // eslint-disable-line no-param-reassign
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
</span>
|
</span>
|
||||||
<label
|
<label
|
||||||
v-once
|
v-once
|
||||||
class="mb-1"
|
|
||||||
v-html="text"
|
v-html="text"
|
||||||
></label>
|
></label>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,11 +22,11 @@
|
|||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
label {
|
label {
|
||||||
height: 1.5rem;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
line-height: 1.71;
|
line-height: 1.71;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gray-200 {
|
.gray-200 {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<select-list
|
<select-list
|
||||||
:items="items"
|
:items="items"
|
||||||
:key-prop="'icon'"
|
|
||||||
class="difficulty-select"
|
class="difficulty-select"
|
||||||
:class="{disabled: disabled}"
|
:class="{disabled: disabled}"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@@ -10,7 +9,7 @@
|
|||||||
:hide-icon="true"
|
:hide-icon="true"
|
||||||
@select="$emit('select', $event.value)"
|
@select="$emit('select', $event.value)"
|
||||||
>
|
>
|
||||||
<template v-slot:item="{ item, button }">
|
<template #item="{ item, button }">
|
||||||
<div
|
<div
|
||||||
v-if="item"
|
v-if="item"
|
||||||
class="difficulty-item"
|
class="difficulty-item"
|
||||||
|
|||||||
@@ -203,16 +203,15 @@
|
|||||||
<template
|
<template
|
||||||
v-if="task.type !== 'reward'"
|
v-if="task.type !== 'reward'"
|
||||||
>
|
>
|
||||||
<div class="d-flex mt-3">
|
<div class="d-flex mt-3 align-items-center">
|
||||||
<lockable-label
|
<lockable-label
|
||||||
:locked="challengeAccessRequired"
|
:locked="challengeAccessRequired"
|
||||||
:text="$t('difficulty')"
|
:text="$t('difficulty')"
|
||||||
/>
|
/>
|
||||||
<div
|
<information-icon
|
||||||
v-b-tooltip.hover.righttop="$t('difficultyHelp')"
|
tooltip-id="difficultyHelp"
|
||||||
class="svg-icon info-icon mb-auto ml-1"
|
:tooltip="$t('difficultyHelp')"
|
||||||
v-html="icons.information"
|
/>
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
<select-difficulty
|
<select-difficulty
|
||||||
:value="task.priority"
|
:value="task.priority"
|
||||||
@@ -452,7 +451,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-if="task.type === 'daily' && isUserTask && purpose === 'edit'"
|
v-if="advancedSettingsShowRestoreStreak"
|
||||||
class="option mt-3"
|
class="option mt-3"
|
||||||
>
|
>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -479,8 +478,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="task.type === 'habit'
|
v-if="advancedSettingsShowAdjustCounter"
|
||||||
&& isUserTask && purpose === 'edit' && (task.up || task.down)"
|
|
||||||
class="option mt-3"
|
class="option mt-3"
|
||||||
>
|
>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -539,6 +537,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
</div>
|
</div>
|
||||||
@@ -882,6 +905,11 @@
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.habit-option {
|
.habit-option {
|
||||||
&-container {
|
&-container {
|
||||||
min-width: 3rem;
|
min-width: 3rem;
|
||||||
@@ -997,7 +1025,6 @@ import lockableLabel from '@/components/tasks/modal-controls/lockableLabel';
|
|||||||
|
|
||||||
import syncTask from '../../mixins/syncTask';
|
import syncTask from '../../mixins/syncTask';
|
||||||
|
|
||||||
import informationIcon from '@/assets/svg/information.svg';
|
|
||||||
import positiveIcon from '@/assets/svg/positive.svg';
|
import positiveIcon from '@/assets/svg/positive.svg';
|
||||||
import negativeIcon from '@/assets/svg/negative.svg';
|
import negativeIcon from '@/assets/svg/negative.svg';
|
||||||
import streakIcon from '@/assets/svg/streak.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 chevronIcon from '@/assets/svg/chevron.svg';
|
||||||
import calendarIcon from '@/assets/svg/calendar.svg';
|
import calendarIcon from '@/assets/svg/calendar.svg';
|
||||||
import gripIcon from '@/assets/svg/grip.svg';
|
import gripIcon from '@/assets/svg/grip.svg';
|
||||||
|
import InformationIcon from '@/components/ui/informationIcon.vue';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
InformationIcon,
|
||||||
SelectMulti,
|
SelectMulti,
|
||||||
Datepicker,
|
Datepicker,
|
||||||
checklist,
|
checklist,
|
||||||
@@ -1029,7 +1058,6 @@ export default {
|
|||||||
showAssignedSelect: false,
|
showAssignedSelect: false,
|
||||||
newChecklistItem: null,
|
newChecklistItem: null,
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
information: informationIcon,
|
|
||||||
negative: negativeIcon,
|
negative: negativeIcon,
|
||||||
positive: positiveIcon,
|
positive: positiveIcon,
|
||||||
destroy: deleteIcon,
|
destroy: deleteIcon,
|
||||||
@@ -1064,25 +1092,25 @@ export default {
|
|||||||
dayMapping: 'constants.DAY_MAPPING',
|
dayMapping: 'constants.DAY_MAPPING',
|
||||||
ATTRIBUTES: 'constants.ATTRIBUTES',
|
ATTRIBUTES: 'constants.ATTRIBUTES',
|
||||||
}),
|
}),
|
||||||
advancedSettingsAvailable () {
|
// region advanced settings
|
||||||
if (
|
advancedSettingsShowAdjustCounter () {
|
||||||
this.task.type === 'reward'
|
return this.task.type === 'habit'
|
||||||
|| this.task.type === 'todo'
|
&& this.isUserTask && this.purpose === 'edit'
|
||||||
|| this.purpose === 'create'
|
&& (this.task.up || this.task.down);
|
||||||
|| !this.isUserTask
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.task.type === 'habit'
|
|
||||||
&& !this.task.up
|
|
||||||
&& !this.task.down
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
|
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 () {
|
checklistEnabled () {
|
||||||
return ['daily', 'todo'].indexOf(this.task.type) > -1 && !this.isOriginalChallengeTask;
|
return ['daily', 'todo'].indexOf(this.task.type) > -1 && !this.isOriginalChallengeTask;
|
||||||
},
|
},
|
||||||
@@ -1157,7 +1185,6 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted () {
|
async mounted () {
|
||||||
this.showAdvancedOptions = !this.user.preferences.advancedCollapsed;
|
|
||||||
if (this.groupId) {
|
if (this.groupId) {
|
||||||
const groupResponse = await axios.get(`/api/v4/groups/${this.groupId}`);
|
const groupResponse = await axios.get(`/api/v4/groups/${this.groupId}`);
|
||||||
this.managers = Object.keys(groupResponse.data.data.managers);
|
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"
|
@show="isOpened = true"
|
||||||
@hide="isOpened = false"
|
@hide="isOpened = false"
|
||||||
>
|
>
|
||||||
<template v-slot:button-content>
|
<template #button-content>
|
||||||
<slot
|
<slot
|
||||||
name="item"
|
name="item"
|
||||||
:item="selected || placeholder"
|
:item="selected || placeholder"
|
||||||
@@ -21,13 +21,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<b-dropdown-item
|
<b-dropdown-item
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="keyProp ? item[keyProp] : item"
|
:key="getKeyProp(item)"
|
||||||
:disabled="typeof item[disabledProp] === 'undefined' ? false : item[disabledProp]"
|
:disabled="isDisabled(item)"
|
||||||
:active="item === selected"
|
:active="isSelected(item)"
|
||||||
:class="{
|
:class="{
|
||||||
active: item === selected,
|
active: isSelected(item),
|
||||||
selectListItem: true,
|
selectListItem: true,
|
||||||
showIcon: !hideIcon && item === selected
|
showIcon: !hideIcon && isSelected(item)
|
||||||
}"
|
}"
|
||||||
@click="selectItem(item)"
|
@click="selectItem(item)"
|
||||||
>
|
>
|
||||||
@@ -51,39 +51,39 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~@/assets/scss/colors.scss';
|
@import '~@/assets/scss/colors.scss';
|
||||||
|
|
||||||
.select-list ::v-deep {
|
.select-list ::v-deep {
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding-right: 25px; /* To allow enough room for the down arrow to be displayed */
|
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 {
|
&:not(.showIcon) {
|
||||||
position: relative;
|
.svg-icon.check-icon {
|
||||||
|
display: none;
|
||||||
.dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.showIcon) {
|
.svg-icon.check-icon.color {
|
||||||
.svg-icon.check-icon {
|
margin-left: 10px; /* So the flex item (checkmark) will have some spacing from the text */
|
||||||
display: none;
|
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>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -101,6 +101,9 @@ export default {
|
|||||||
keyProp: {
|
keyProp: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
activeKeyProp: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
disabledProp: {
|
disabledProp: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
@@ -128,10 +131,23 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getKeyProp (item) {
|
||||||
|
return this.keyProp ? item[this.keyProp] : item;
|
||||||
|
},
|
||||||
|
isDisabled (item) {
|
||||||
|
return typeof item[this.disabledProp] === 'undefined' ? false : item[this.disabledProp];
|
||||||
|
},
|
||||||
selectItem (item) {
|
selectItem (item) {
|
||||||
this.selected = item;
|
this.selected = this.getKeyProp(item);
|
||||||
this.$emit('select', item);
|
this.$emit('select', item);
|
||||||
},
|
},
|
||||||
|
isSelected (item) {
|
||||||
|
if (this.activeKeyProp) {
|
||||||
|
return item[this.activeKeyProp] === this.selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item === this.selected;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</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 Vue from 'vue';
|
||||||
import BootstrapVue from 'bootstrap-vue';
|
import BootstrapVue from 'bootstrap-vue';
|
||||||
|
import Fragment from 'vue-fragment';
|
||||||
import AppComponent from './app';
|
import AppComponent from './app';
|
||||||
import {
|
import {
|
||||||
setup as setupAnalytics,
|
setup as setupAnalytics,
|
||||||
@@ -28,6 +29,7 @@ Vue.config.productionTip = IS_PRODUCTION;
|
|||||||
Vue.use(i18n, { i18nData: window && window['habitica-i18n'] });
|
Vue.use(i18n, { i18nData: window && window['habitica-i18n'] });
|
||||||
Vue.use(StoreModule);
|
Vue.use(StoreModule);
|
||||||
Vue.use(BootstrapVue);
|
Vue.use(BootstrapVue);
|
||||||
|
Vue.use(Fragment.Plugin);
|
||||||
|
|
||||||
setUpLogging();
|
setUpLogging();
|
||||||
setupAnalytics(); // just create queues for analytics, no scripts loaded at this time
|
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];
|
return num.toString().match(re)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export const NotificationMixins = {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({ notifications: 'notificationStore' }),
|
...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 => {
|
export const userCustomStateMixin = fieldname => {
|
||||||
const map = { };
|
const map = { };
|
||||||
map[fieldname] = 'user.data';
|
map[fieldname] = 'user.data';
|
||||||
return { // eslint-disable-line import/prefer-default-export
|
return {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(map),
|
...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>
|
<script>
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import PurchaseHistoryTable from '../ui/purchaseHistoryTable.vue';
|
import PurchaseHistoryTable from '../../components/ui/purchaseHistoryTable.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
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: 'logout', path: '/logout', component: Logout },
|
||||||
{
|
{
|
||||||
name: 'resetPassword', path: '/reset-password', component: RegisterLoginReset, meta: { requiresLogin: false },
|
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 },
|
{ name: 'tasks', path: '/', component: UserTasks },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import ParentPage from '@/components/parentPage.vue';
|
import ParentPage from '@/components/parentPage.vue';
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
|
const Settings = () => import(/* webpackChunkName: "settings" */'@/pages/settings-overview');
|
||||||
const API = () => import(/* webpackChunkName: "settings" */'@/components/settings/api');
|
const GeneralSettings = () => import(/* webpackChunkName: "settings" */'@/pages/settings/generalSettings');
|
||||||
const DataExport = () => import(/* webpackChunkName: "settings" */'@/components/settings/dataExport');
|
const Notifications = () => import(/* webpackChunkName: "settings" */'@/pages/settings/notificationSettings');
|
||||||
const Notifications = () => import(/* webpackChunkName: "settings" */'@/components/settings/notifications');
|
const Transactions = () => import(/* webpackChunkName: "settings" */'@/pages/settings/purchaseHistory.vue');
|
||||||
const PromoCode = () => import(/* webpackChunkName: "settings" */'@/components/settings/promoCode');
|
|
||||||
const Site = () => import(/* webpackChunkName: "settings" */'@/components/settings/site');
|
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 Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
|
||||||
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
|
|
||||||
|
|
||||||
export const USER_ROUTES = {
|
export const USER_ROUTES = {
|
||||||
path: '/user',
|
path: '/user',
|
||||||
@@ -20,20 +22,16 @@ export const USER_ROUTES = {
|
|||||||
component: Settings,
|
component: Settings,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'site',
|
name: 'general',
|
||||||
path: 'site',
|
path: 'general',
|
||||||
component: Site,
|
component: GeneralSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'api',
|
name: 'siteData',
|
||||||
path: 'api',
|
path: 'siteData',
|
||||||
component: API,
|
component: SiteData,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dataExport',
|
|
||||||
path: 'data-export',
|
|
||||||
component: DataExport,
|
|
||||||
},
|
},
|
||||||
|
{ path: 'api', redirect: { name: 'siteData' } },
|
||||||
{
|
{
|
||||||
name: 'promoCode',
|
name: 'promoCode',
|
||||||
path: 'promo-code',
|
path: 'promo-code',
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export async function sleep (store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function addWebhook (store, payload) {
|
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;
|
return response.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const nconf = require('nconf');
|
const nconf = require('nconf');
|
||||||
|
const vueTemplateCompiler = require('vue-template-babel-compiler');
|
||||||
const { DuplicatesPlugin } = require('inspectpack/plugin');
|
const { DuplicatesPlugin } = require('inspectpack/plugin');
|
||||||
const setupNconf = require('../server/libs/setupNconf');
|
const setupNconf = require('../server/libs/setupNconf');
|
||||||
const pkg = require('./package.json');
|
const pkg = require('./package.json');
|
||||||
@@ -126,6 +127,15 @@ module.exports = {
|
|||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
config.plugins.delete('preload');
|
config.plugins.delete('preload');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// enable optional chaining in templates
|
||||||
|
config.module
|
||||||
|
.rule('vue')
|
||||||
|
.use('vue-loader')
|
||||||
|
.tap(options => {
|
||||||
|
options.compiler = vueTemplateCompiler;
|
||||||
|
return options;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"frequentlyAskedQuestions": "Frequently Asked Questions",
|
"frequentlyAskedQuestions": "Frequently Asked Questions",
|
||||||
"general": "General",
|
|
||||||
|
|
||||||
"faqQuestion0": "I'm confused. Where do I get an overview?",
|
"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.",
|
"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.",
|
"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.",
|
"missingPassword": "Missing password.",
|
||||||
"missingNewPassword": "Missing new password.",
|
"missingNewPassword": "Missing new password.",
|
||||||
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
|
"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.",
|
"incorrectDeletePhrase": "Please type <%= magicWord %> in all capital letters to delete your account.",
|
||||||
"notAnEmail": "Invalid email address.",
|
"notAnEmail": "Invalid email address.",
|
||||||
"emailTaken": "Email address is already used in an account.",
|
"emailTaken": "Email address is already used in an account.",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"modalAchievement": "Achievement!",
|
"modalAchievement": "Achievement!",
|
||||||
"special": "Special",
|
"special": "Special",
|
||||||
"site": "Site",
|
"site": "Site",
|
||||||
|
"general": "General",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"market": "Market",
|
"market": "Market",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"error": "Error",
|
"error": "Error",
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
|
"allNotifications": "All Notifications",
|
||||||
"noNotifications": "You're all caught up!",
|
"noNotifications": "You're all caught up!",
|
||||||
"noNotificationsText": "The notification fairies give you a raucous round of applause! Well done!",
|
"noNotificationsText": "The notification fairies give you a raucous round of applause! Well done!",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
{
|
{
|
||||||
"settings": "Settings",
|
"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",
|
"language": "Language",
|
||||||
|
"siteLanguage": "Site Language",
|
||||||
"americanEnglishGovern": "In the event of a discrepancy in the translations, the American English version governs.",
|
"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",
|
"stickyHeader": "Sticky header",
|
||||||
"newTaskEdit": "Open new tasks in edit mode",
|
"newTaskEdit": "Open new tasks in edit mode",
|
||||||
"reverseChatOrder": "Show chat messages in reverse order",
|
"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",
|
"dontShowAgain": "Don't show this again",
|
||||||
"suppressLevelUpModal": "Don't show popup when gaining a level",
|
"showLevelUpModal": "When Gaining a Level",
|
||||||
"suppressHatchPetModal": "Don't show popup when hatching a pet",
|
"showHatchPetModal": "When Hatching a Pet",
|
||||||
"suppressRaisePetModal": "Don't show popup when raising a pet into a mount",
|
"showRaisePetModal": "When Raising a Pet into a Mount",
|
||||||
"suppressStreakModal": "Don't show popup when attaining a Streak achievement",
|
"showStreakModal": "When Attaining a Streak Achievement",
|
||||||
|
"baileyAnnouncement": "Latest Bailey Announcement",
|
||||||
|
"view": "View",
|
||||||
"showTour": "Show Tour",
|
"showTour": "Show Tour",
|
||||||
"showBailey": "Show Bailey",
|
"showBailey": "Show Bailey",
|
||||||
"showBaileyPop": "Bring Bailey the Town Crier out of hiding so you can review past news.",
|
"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.",
|
"resetAccPop": "Start over, removing all levels, gold, gear, history, and tasks.",
|
||||||
"deleteAccount": "Delete Account",
|
"deleteAccount": "Delete Account",
|
||||||
"deleteAccPop": "Cancel and remove your Habitica 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",
|
"dataExport": "Data Export",
|
||||||
"saveData": "Here are a few options for saving your data.",
|
"saveData": "Here are a few options for saving your data.",
|
||||||
"habitHistory": "Habit History",
|
"habitHistory": "Habit History",
|
||||||
"exportHistory": "Export History:",
|
"exportHistory": "Export History:",
|
||||||
"csv": "(CSV)",
|
"csv": "(CSV)",
|
||||||
|
"downloadCSV": "Download CSV",
|
||||||
|
"downloadAs": "Download as",
|
||||||
"userData": "User Data",
|
"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:",
|
"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",
|
"export": "Export",
|
||||||
"xml": "(XML)",
|
"xml": "XML",
|
||||||
"json": "(JSON)",
|
"json": "JSON",
|
||||||
|
"api": "API",
|
||||||
"customDayStart": "Custom Day Start",
|
"customDayStart": "Custom Day Start",
|
||||||
"adjustment": "Adjustment",
|
"adjustment": "Adjustment",
|
||||||
"dayStartAdjustment": "Day Start 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.",
|
"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",
|
"misc": "Misc",
|
||||||
"showHeader": "Show Header",
|
"showHeader": "Show Header",
|
||||||
|
"currentPass": "Current Password",
|
||||||
"changePass": "Change Password",
|
"changePass": "Change Password",
|
||||||
"changeUsername": "Change Username",
|
"changeUsername": "Change Username",
|
||||||
"changeEmail": "Change Email Address",
|
"changeEmail": "Change Email Address",
|
||||||
@@ -54,11 +74,18 @@
|
|||||||
"confirmPass": "Confirm New Password",
|
"confirmPass": "Confirm New Password",
|
||||||
"newUsername": "New Username",
|
"newUsername": "New Username",
|
||||||
"dangerZone": "Danger Zone",
|
"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.",
|
"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.",
|
||||||
"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.",
|
"resetDetail1": "You will lose all your levels, Gold, and Experience points.",
|
||||||
"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.",
|
"resetDetail2": "You will keep your current class, achievements and your pets and mounts.",
|
||||||
"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.",
|
"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",
|
"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",
|
"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.",
|
"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!)",
|
"APIToken": "API Token (this is a password - see warning above!)",
|
||||||
@@ -70,13 +97,14 @@
|
|||||||
"resetDo": "Do it, reset my account!",
|
"resetDo": "Do it, reset my account!",
|
||||||
"resetComplete": "Reset complete!",
|
"resetComplete": "Reset complete!",
|
||||||
"fixValues": "Fix Values",
|
"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!",
|
"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": "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.",
|
"fixValuesText2": "<b>Note</b>: To restore Streaks on individual Tasks, edit the Task and use the Restore Streak field.",
|
||||||
"fix21Streaks": "21-Day Streaks",
|
"fix21Streaks": "21-Day Streaks",
|
||||||
"discardChanges": "Discard Changes",
|
"discardChanges": "Discard Changes",
|
||||||
"deleteDo": "Do it, delete my account!",
|
"deleteDo": "Do it, delete my account!",
|
||||||
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
|
"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.",
|
"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",
|
"displayNameSuccess": "Display name successfully changed",
|
||||||
"emailSuccess": "Email successfully changed",
|
"emailSuccess": "Email successfully changed",
|
||||||
"passwordSuccess": "Password successfully changed",
|
"passwordSuccess": "Password successfully changed",
|
||||||
@@ -98,8 +126,8 @@
|
|||||||
"giftedSubscriptionInfo": "<%= name %> gifted you a <%= months %> month subscription",
|
"giftedSubscriptionInfo": "<%= name %> gifted you a <%= months %> month subscription",
|
||||||
"giftedSubscriptionFull": "Hello <%= username %>, <%= sender %> has sent you <%= monthCount %> months of 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!",
|
"giftedSubscriptionWinterPromo": "Hello <%= username %>, you received <%= monthCount %> months of subscription as part of our holiday gift-giving promotion!",
|
||||||
"invitedParty": "You were invited to a Party",
|
"invitedParty": "Invited to a Party",
|
||||||
"invitedGuild": "You were invited to a Guild",
|
"invitedGuild": "Invited to a Guild",
|
||||||
"importantAnnouncements": "Reminders to check in to complete tasks and receive prizes",
|
"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!)",
|
"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",
|
"onboarding": "Guidance with setting up your Habitica account",
|
||||||
@@ -107,25 +135,24 @@
|
|||||||
"subscriptionReminders": "Subscriptions Reminders",
|
"subscriptionReminders": "Subscriptions Reminders",
|
||||||
"questStarted": "Your Quest has Begun",
|
"questStarted": "Your Quest has Begun",
|
||||||
"invitedQuest": "Invited to Quest",
|
"invitedQuest": "Invited to Quest",
|
||||||
"kickedGroup": "Kicked from group",
|
"kickedGroup": "Removed from group",
|
||||||
"remindersToLogin": "Reminders to check in to Habitica",
|
"remindersToLogin": "Reminders to check in to Habitica",
|
||||||
"unsubscribedSuccessfully": "Unsubscribed successfully!",
|
"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).",
|
"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.",
|
"unsubscribedTextOthers": "You won't receive any other email from Habitica.",
|
||||||
"unsubscribeAllEmails": "Check to Unsubscribe from Emails",
|
"unsubscribeAllEmails": "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.",
|
"unsubscribeAllEmailsText": "Habitica will be unable to notify you via email about important changes to the site or your account.",
|
||||||
"unsubscribeAllPush": "Check to Unsubscribe from all Push Notifications",
|
"unsubscribeAllPush": "Unsubscribe from all Push Notifications",
|
||||||
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from \"<%= emailType %>\" emails.",
|
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from \"<%= emailType %>\" emails.",
|
||||||
"subscriptionRateText": "Recurring <strong>$<%= price %> USD</strong> every <strong><%= months %> months</strong>",
|
"subscriptionRateText": "Recurring <strong>$<%= price %> USD</strong> every <strong><%= months %> months</strong>",
|
||||||
"giftSubscriptionRateText": "<strong>$<%= price %> USD</strong> for <strong><%= months %> months</strong>",
|
"giftSubscriptionRateText": "<strong>$<%= price %> USD</strong> for <strong><%= months %> months</strong>",
|
||||||
"benefits": "Benefits",
|
"benefits": "Benefits",
|
||||||
"coupon": "Coupon",
|
"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",
|
"apply": "Apply",
|
||||||
"promoCode": "Promo Code",
|
"promoCode": "Promo Code",
|
||||||
"promoCodeApplied": "Promo Code Applied! Check your inventory",
|
"promoCodeApplied": "Promo Code Applied! Check your inventory",
|
||||||
"promoPlaceholder": "Enter Promotion Code",
|
"promoPlaceholder": "Enter Promotion Code",
|
||||||
"displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.",
|
|
||||||
"saveCustomDayStart": "Save Custom Day Start",
|
"saveCustomDayStart": "Save Custom Day Start",
|
||||||
"registration": "Registration",
|
"registration": "Registration",
|
||||||
"addLocalAuth": "Add Email and Password Login",
|
"addLocalAuth": "Add Email and Password Login",
|
||||||
@@ -134,9 +161,10 @@
|
|||||||
"generate": "Generate",
|
"generate": "Generate",
|
||||||
"getCodes": "Get Codes",
|
"getCodes": "Get Codes",
|
||||||
"webhooks": "Webhooks",
|
"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",
|
"enabled": "Enabled",
|
||||||
"webhookURL": "Webhook URL",
|
"webhookURL": "Webhook URL",
|
||||||
|
"addWebhook": "Add Webhook",
|
||||||
"invalidUrl": "invalid url",
|
"invalidUrl": "invalid url",
|
||||||
"invalidWebhookId": "the \"id\" parameter should be a valid UUID.",
|
"invalidWebhookId": "the \"id\" parameter should be a valid UUID.",
|
||||||
"webhookBooleanOption": "\"<%= option %>\" must be a Boolean value.",
|
"webhookBooleanOption": "\"<%= option %>\" must be a Boolean value.",
|
||||||
@@ -179,7 +207,14 @@
|
|||||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||||
"usernameNotVerified": "Please confirm your username.",
|
"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.",
|
"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!",
|
"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",
|
"mentioning": "Mentioning",
|
||||||
"suggestMyUsername": "Suggest my username",
|
"suggestMyUsername": "Suggest my username",
|
||||||
"everywhere": "Everywhere",
|
"everywhere": "Everywhere",
|
||||||
@@ -189,6 +224,11 @@
|
|||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"note": "Note",
|
"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",
|
"remainingBalance": "Remaining Balance",
|
||||||
"transactions": "Transactions",
|
"transactions": "Transactions",
|
||||||
"hourglassTransactions": "Hourglass Transactions",
|
"hourglassTransactions": "Hourglass Transactions",
|
||||||
@@ -203,7 +243,6 @@
|
|||||||
"transaction_gift_receive": "<b>Received</b> from",
|
"transaction_gift_receive": "<b>Received</b> from",
|
||||||
"transaction_create_challenge": "<b>Created</b> challenge",
|
"transaction_create_challenge": "<b>Created</b> challenge",
|
||||||
"transaction_create_bank_challenge": "<b>Created</b> bank 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_create_guild": "<b>Created</b> guild",
|
||||||
"transaction_change_class": "<b>Class</b> change",
|
"transaction_change_class": "<b>Class</b> change",
|
||||||
"transaction_rebirth": "Used Orb of Rebirth",
|
"transaction_rebirth": "Used Orb of Rebirth",
|
||||||
@@ -212,5 +251,8 @@
|
|||||||
"transaction_reroll": "Used Fortify Potion",
|
"transaction_reroll": "Used Fortify Potion",
|
||||||
"transaction_subscription_perks": "<b>Subscription</b> perk",
|
"transaction_subscription_perks": "<b>Subscription</b> perk",
|
||||||
"transaction_admin_update_balance": "<b>Admin</b> given",
|
"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!",
|
"streakCoins": "Streak Bonus!",
|
||||||
"taskToTop": "To top",
|
"taskToTop": "To top",
|
||||||
"taskToBottom": "To bottom",
|
"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.",
|
"taskAliasAlreadyUsed": "Task alias already used on another task.",
|
||||||
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
|
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
|
||||||
"invalidTasksType": "Task type must be one of \"habits\", \"dailys\", \"todos\", \"rewards\".",
|
"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
|
// @TODO use those constants to replace hard-coded numbers
|
||||||
|
|
||||||
export const SUPPORTED_SOCIAL_NETWORKS = [
|
export const SUPPORTED_SOCIAL_NETWORKS = [
|
||||||
{ key: 'google', name: 'Google' },
|
|
||||||
{ key: 'apple', name: 'Apple' },
|
{ key: 'apple', name: 'Apple' },
|
||||||
|
{ key: 'google', name: 'Google' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GUILDS_PER_PAGE = 30; // number of guilds to return per page when using pagination
|
export const GUILDS_PER_PAGE = 30; // number of guilds to return per page when using pagination
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import moment from 'moment';
|
// sorting this also changes the class selection
|
||||||
|
|
||||||
export const CURRENT_SEASON = moment().isBefore('2020-08-02') ? 'summer' : '_NONE_';
|
|
||||||
|
|
||||||
export const CLASSES = [
|
export const CLASSES = [
|
||||||
'warrior',
|
|
||||||
'rogue',
|
|
||||||
'healer',
|
'healer',
|
||||||
'wizard',
|
'wizard',
|
||||||
|
'rogue',
|
||||||
|
'warrior',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GEAR_TYPES = [
|
export const GEAR_TYPES = [
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function addTask (user, req = { body: {} }) {
|
|||||||
if (task._editing) {
|
if (task._editing) {
|
||||||
task._edit = clone(task);
|
task._edit = clone(task);
|
||||||
}
|
}
|
||||||
task._advanced = !user.preferences.advancedCollapsed;
|
|
||||||
|
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|||||||