Squashed commit of the following:

commit 3aba0abedd
Author: SabreCat <sabe@habitica.com>
Date:   Mon Oct 2 20:51:20 2023 -0500

    fix(router): use state to pass modal launch info

commit 541eadd319
Merge: c0bb56c8c2 89fff49d02
Author: SabreCat <sabe@habitica.com>
Date:   Mon Oct 2 20:12:40 2023 -0500

    Merge branch 'release' into report-profile-modal

commit c0bb56c8c2
Author: SabreCat <sabe@habitica.com>
Date:   Wed Sep 27 16:15:28 2023 -0500

    test(profiles): add integrations

commit 9b644e9ad8
Author: SabreCat <sabe@habitica.com>
Date:   Tue Sep 26 17:17:22 2023 -0500

    fix(profile): adjust margin

commit bfefe5dfa9
Author: SabreCat <sabe@habitica.com>
Date:   Tue Sep 26 17:12:24 2023 -0500

    fix(profiles): moar layout fixes

commit 8f211ee3e2
Author: SabreCat <sabe@habitica.com>
Date:   Mon Sep 25 17:32:04 2023 -0500

    fix(profile): fix admin actions
    Correct "user is banned" banner
    Fix bouncing modal
    Add "Days" smart plural
    Fix leaky CSS on Market page
    Refactor some redundant functions

commit b1d23ec88b
Merge: ee9709a9e1 a63cc84779
Author: SabreCat <sabe@habitica.com>
Date:   Mon Sep 25 15:37:54 2023 -0500

    Merge branch 'release' into report-profile-modal

commit ee9709a9e1
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Sep 18 16:30:30 2023 -0400

    WIP(profile): add banned banner, toggle switches now toggle, add "days" to Next Login Reward

commit f80928a895
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Sep 18 13:43:34 2023 -0400

    update(node): update node modules

commit 1d552f7e80
Author: SabreCat <sabe@habitica.com>
Date:   Fri Sep 15 16:52:22 2023 -0500

    fix(import): remove empty import

commit f55d74a95d
Author: SabreCat <sabe@habitica.com>
Date:   Fri Sep 15 16:39:50 2023 -0500

    refactor(profiles): remove email feature
    also still more visual cleanup of profile modal

commit 311c743284
Author: SabreCat <sabe@habitica.com>
Date:   Fri Sep 15 15:44:56 2023 -0500

    refactor(profile): remove page view

commit f8632bf50d
Merge: ec85159c65 9e25360102
Author: SabreCat <sabe@habitica.com>
Date:   Fri Sep 15 15:23:21 2023 -0500

    Merge branch 'release' into report-profile-modal

commit ec85159c65
Author: SabreCat <sabe@habitica.com>
Date:   Mon Sep 11 22:53:14 2023 -0500

    feat(profiles): load modal instead of page?

commit 9986082914
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 8 14:49:57 2023 -0400

    WIP(profile): fixed a comment, woohoo

commit 6262a9ba0c
Merge: ae2b614df2 ea2b007b1a
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 8 13:40:23 2023 -0400

    Merge remote-tracking branch 'origin/report-profile-modal' into report-profile-modal

commit ea2b007b1a
Author: SabreCat <sabe@habitica.com>
Date:   Thu Sep 7 16:54:19 2023 -0500

    fix(profile): focus behavior

commit ae2b614df2
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Sep 7 17:47:08 2023 -0400

    WIP(profile): styling updates

commit 2e0723f1b9
Author: SabreCat <sabe@habitica.com>
Date:   Thu Sep 7 15:37:59 2023 -0500

    feat(moderation): unflag profile
    Also a few stylistic tweaks

commit edcf8113de
Author: SabreCat <sabe@habitica.com>
Date:   Wed Sep 6 16:39:02 2023 -0500

    WIP(profile): dropdown draft

commit 0691483d63
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Sep 6 16:33:30 2023 -0400

    WIP(profile): Styling and string updates

commit 7e9d57d10a
Author: SabreCat <sabe@habitica.com>
Date:   Wed Sep 6 11:40:31 2023 -0500

    feat(profile): functional dropdown buttons

commit a2989b2833
Merge: af6575e40c e072d7c09c
Author: SabreCat <sabe@habitica.com>
Date:   Wed Sep 6 10:04:57 2023 -0500

    Merge branch 'release' into report-profile-modal

commit af6575e40c
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Sep 6 11:01:05 2023 -0400

    WIP(profile): comment cleanup

commit 7b1de37202
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Sep 5 17:22:14 2023 -0400

    WIP(profile): remove shadowban tooltip

commit d1177c32b9
Merge: 321a01b081 31f821021b
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Sep 5 17:02:40 2023 -0400

    Merge branch 'sabrecat/report-profile' into report-profile-modal

commit 321a01b081
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 1 16:14:36 2023 -0400

    WIP(profile): close button finally workinating

commit e143d36d28
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 1 15:52:38 2023 -0400

    WIP(profile): close icon moved to profile.vue

commit 31f821021b
Merge: a8f5e25d38 8957c5c009
Author: SabreCat <sabe@habitica.com>
Date:   Fri Sep 1 14:52:31 2023 -0500

    Merge branch 'report-profile-modal' into sabrecat/report-profile

commit 8957c5c009
Merge: d340f06a22 0aec3866a4
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 1 15:38:12 2023 -0400

    Merge remote-tracking branch 'origin/report-profile-modal' into report-profile-modal

commit d340f06a22
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Sep 1 15:37:57 2023 -0400

    WIP(profile): fixed user not found error

commit 0aec3866a4
Merge: b01f323b14 ac7c8e0eb6
Author: Natalie <78037386+CuriousMagpie@users.noreply.github.com>
Date:   Fri Sep 1 15:28:58 2023 -0400

    Merge branch 'HabitRPG:develop' into report-profile-modal

commit a8f5e25d38
Author: SabreCat <sabe@habitica.com>
Date:   Thu Aug 31 17:02:07 2023 -0500

    feat(community): basic "report profile"

commit b01f323b14
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Aug 31 17:42:12 2023 -0400

    WIP(profile): removed refactoring crud, located where close icon should be (profileModal.vue)

commit ce7d51a20c
Merge: 010f2299f0 ac7c8e0eb6
Author: SabreCat <sabe@habitica.com>
Date:   Thu Aug 31 14:20:37 2023 -0500

    Merge branch 'release' into sabrecat/report-profile

commit 18b41acd94
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Aug 31 12:23:41 2023 -0400

    WIP(profile): moar buttonz

commit 9387b3a6bc
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 30 17:21:36 2023 -0400

    WIP(profile): buttons

commit b3ea48c4f5
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Aug 25 15:52:41 2023 -0400

    WIP(profile): work on achievement component

commit a1ceb2ea75
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Aug 25 14:39:12 2023 -0400

    WIP(profile): create achievements component

commit 4a24d9b80b
Merge: 8fe263a377 1e05297e96
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 23 13:14:39 2023 -0400

    Merge branch 'develop' into report-profile-modal

commit 1e05297e96
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 23 13:12:52 2023 -0400

    package updates

commit 8fe263a377
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 23 12:12:36 2023 -0400

    update(dependencies): ran npm install to update dependencies

commit 190fe048a1
Merge: 3ea48ab5cb fa83d1a9cf
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 23 11:52:08 2023 -0400

    Merge branch 'develop' into report-profile-modal

commit 3ea48ab5cb
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Aug 11 17:12:31 2023 -0400

    WIP(user profile): dropdown menu and toggles and colors oh my

commit c301a2b460
Merge: 1da6af11b5 647b27c55f
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Aug 11 12:40:07 2023 -0400

    Merge branch 'develop' into report-profile-modal

commit 1da6af11b5
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Aug 10 16:50:07 2023 -0400

    WIP(user profile): moved some CSS classes out of unscoped and into the scoped section, started on toggle buttons

commit dd55cbc928
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 9 15:38:46 2023 -0400

    WIP(user profile): workin on the hamburger (kebab?) menu

commit 3834093207
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Tue Aug 8 14:14:40 2023 -0400

    WIP(user profiles): working on the drop down menu

commit f2be588195
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Mon Aug 7 16:10:30 2023 -0400

    WIP(user profile): options menu

commit 010f2299f0
Author: SabreCat <sabe@habitica.com>
Date:   Mon Aug 7 11:49:04 2023 -0500

    fix(lint): eof and const

commit 4551dbf4b3
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Aug 4 15:34:05 2023 -0400

    WIP(user profile): styling the top portion of the modal

commit 19a9fe3644
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Aug 3 15:06:51 2023 -0400

    WIP(user profile): adding buttons

commit dfdb305b1c
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 2 14:41:20 2023 -0400

    WIP(user profile): layout

commit ded4eee693
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Aug 2 12:04:02 2023 -0400

    WIP(user profile): start flex grid & tidy up CSS

commit aaca48be32
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Fri Jul 28 16:44:06 2023 -0400

    WIP(user profile): mostly css updates

commit e531985b87
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Thu Jul 27 16:49:44 2023 -0400

    WIP(user profile): one infinitesimal change that's hardly worth the electricity it's made from

commit eb4021fcc7
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Jul 26 16:33:05 2023 -0400

    feat(content): upgrade profile page

commit 1b25394f3e
Merge: c50cee0d88 8558dcc3a8
Author: CuriousMagpie <eilatan@gmail.com>
Date:   Wed Jul 26 11:50:12 2023 -0400

    Merge branch 'develop' into report-profile-modal

commit c50cee0d88
Author: SabreCat <sabe@habitica.com>
Date:   Wed Jul 12 16:32:25 2023 -0500

    fix(flagging): debug params issue
    Also add and document the "source" body param

commit 55848c58be
Author: SabreCat <sabe@habitica.com>
Date:   Mon Jul 10 16:24:20 2023 -0500

    WIP(members): basic report a user API

commit dda6180792
Author: SabreCat <sabe@habitica.com>
Date:   Thu Jul 6 10:05:07 2023 -0500

    fix(lint): remove console.info
This commit is contained in:
SabreCat
2023-10-03 13:29:26 -05:00
parent 89fff49d02
commit a9757b2d74
41 changed files with 2440 additions and 1096 deletions

463
package-lock.json generated
View File

@@ -27,33 +27,74 @@
"integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==" "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA=="
}, },
"@babel/core": { "@babel/core": {
"version": "7.22.5", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz",
"integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "integrity": "sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==",
"requires": { "requires": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.5", "@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.22.5", "@babel/generator": "^7.22.10",
"@babel/helper-compilation-targets": "^7.22.5", "@babel/helper-compilation-targets": "^7.22.10",
"@babel/helper-module-transforms": "^7.22.5", "@babel/helper-module-transforms": "^7.22.9",
"@babel/helpers": "^7.22.5", "@babel/helpers": "^7.22.10",
"@babel/parser": "^7.22.5", "@babel/parser": "^7.22.10",
"@babel/template": "^7.22.5", "@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.5", "@babel/traverse": "^7.22.10",
"@babel/types": "^7.22.5", "@babel/types": "^7.22.10",
"convert-source-map": "^1.7.0", "convert-source-map": "^1.7.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
"json5": "^2.2.2", "json5": "^2.2.2",
"semver": "^6.3.0" "semver": "^6.3.1"
}, },
"dependencies": { "dependencies": {
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.22.5", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
"requires": { "requires": {
"@babel/highlight": "^7.22.5" "@babel/highlight": "^7.22.10",
"chalk": "^2.4.2"
}
},
"@babel/compat-data": {
"version": "7.22.9",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz",
"integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ=="
},
"@babel/generator": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
"integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
"requires": {
"@babel/types": "^7.22.10",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
"@babel/helper-compilation-targets": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz",
"integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==",
"requires": {
"@babel/compat-data": "^7.22.9",
"@babel/helper-validator-option": "^7.22.5",
"browserslist": "^4.21.9",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
}
},
"@babel/helper-module-transforms": {
"version": "7.22.9",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz",
"integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==",
"requires": {
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-module-imports": "^7.22.5",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-validator-identifier": "^7.22.5"
} }
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
@@ -62,25 +103,72 @@
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ=="
}, },
"@babel/highlight": { "@babel/highlight": {
"version": "7.22.5", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5",
"chalk": "^2.0.0", "chalk": "^2.4.2",
"js-tokens": "^4.0.0" "js-tokens": "^4.0.0"
} }
}, },
"@babel/types": { "@babel/parser": {
"version": "7.22.5", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
"integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ=="
},
"@babel/traverse": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
"integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
"requires": { "requires": {
"@babel/helper-string-parser": "^7.22.5", "@babel/code-frame": "^7.22.10",
"@babel/helper-validator-identifier": "^7.22.5", "@babel/generator": "^7.22.10",
"to-fast-properties": "^2.0.0" "@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.22.10",
"@babel/types": "^7.22.10",
"debug": "^4.1.0",
"globals": "^11.1.0"
} }
}, },
"@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"browserslist": {
"version": "4.21.10",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz",
"integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==",
"requires": {
"caniuse-lite": "^1.0.30001517",
"electron-to-chromium": "^1.4.477",
"node-releases": "^2.0.13",
"update-browserslist-db": "^1.0.11"
}
},
"caniuse-lite": {
"version": "1.0.30001522",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz",
"integrity": "sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg=="
},
"chalk": { "chalk": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -91,10 +179,32 @@
"supports-color": "^5.3.0" "supports-color": "^5.3.0"
} }
}, },
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
},
"update-browserslist-db": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
"integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
"requires": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
}
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
} }
} }
}, },
@@ -542,28 +652,99 @@
} }
}, },
"@babel/helpers": { "@babel/helpers": {
"version": "7.22.5", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz",
"integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "integrity": "sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==",
"requires": { "requires": {
"@babel/template": "^7.22.5", "@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.5", "@babel/traverse": "^7.22.10",
"@babel/types": "^7.22.5" "@babel/types": "^7.22.10"
}, },
"dependencies": { "dependencies": {
"@babel/code-frame": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
"integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
"requires": {
"@babel/highlight": "^7.22.10",
"chalk": "^2.4.2"
}
},
"@babel/generator": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
"integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
"requires": {
"@babel/types": "^7.22.10",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
}
},
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ=="
}, },
"@babel/types": { "@babel/highlight": {
"version": "7.22.5", "version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
"integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
"requires": { "requires": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5",
"to-fast-properties": "^2.0.0" "chalk": "^2.4.2",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
"integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ=="
},
"@babel/traverse": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
"integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
"requires": {
"@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.22.10",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.22.10",
"@babel/types": "^7.22.10",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
} }
} }
} }
@@ -2105,13 +2286,13 @@
"integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw=="
}, },
"@parse/node-apn": { "@parse/node-apn": {
"version": "5.1.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.1.3.tgz", "resolved": "https://registry.npmjs.org/@parse/node-apn/-/node-apn-5.2.3.tgz",
"integrity": "sha512-Bwhmbm895lEIF2772PJ8dSvBjrtOG9/q/TDMxmX40IgZxQFoXS73+JUIKTq3CA7SUB/Szu5roJINQ0L2U/1MJw==", "integrity": "sha512-uBUTTbzk0YyMOcE5qTcNdit5v1BdaECCRSQYbMGU/qY1eHwBaqeWOYd8rwi2Caga3K7IZyQGhpvL4/56H+uvrQ==",
"requires": { "requires": {
"debug": "4.3.3", "debug": "4.3.3",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "9.0.0",
"node-forge": "1.3.0", "node-forge": "1.3.1",
"verror": "1.10.1" "verror": "1.10.1"
}, },
"dependencies": { "dependencies": {
@@ -2124,20 +2305,14 @@
} }
}, },
"jsonwebtoken": { "jsonwebtoken": {
"version": "8.5.1", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
"requires": { "requires": {
"jws": "^3.2.2", "jws": "^3.2.2",
"lodash.includes": "^4.3.0", "lodash": "^4.17.21",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1", "ms": "^2.1.1",
"semver": "^5.6.0" "semver": "^7.3.8"
} }
}, },
"jwa": { "jwa": {
@@ -2159,10 +2334,13 @@
"safe-buffer": "^5.0.1" "safe-buffer": "^5.0.1"
} }
}, },
"node-forge": { "semver": {
"version": "1.3.0", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==" "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"requires": {
"lru-cache": "^6.0.0"
}
}, },
"verror": { "verror": {
"version": "1.10.1", "version": "1.10.1",
@@ -2177,9 +2355,9 @@
} }
}, },
"@sindresorhus/is": { "@sindresorhus/is": {
"version": "4.2.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
}, },
"@sinonjs/commons": { "@sinonjs/commons": {
"version": "3.0.0", "version": "3.0.0",
@@ -2283,14 +2461,14 @@
} }
}, },
"@types/cacheable-request": { "@types/cacheable-request": {
"version": "6.0.2", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"requires": { "requires": {
"@types/http-cache-semantics": "*", "@types/http-cache-semantics": "*",
"@types/keyv": "*", "@types/keyv": "^3.1.4",
"@types/node": "*", "@types/node": "*",
"@types/responselike": "*" "@types/responselike": "^1.0.0"
} }
}, },
"@types/color-name": { "@types/color-name": {
@@ -2383,9 +2561,9 @@
} }
}, },
"@types/keyv": { "@types/keyv": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
} }
@@ -3355,9 +3533,9 @@
"integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA=="
}, },
"axios": { "axios": {
"version": "1.3.6", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dev": true, "dev": true,
"requires": { "requires": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
@@ -4210,9 +4388,9 @@
"optional": true "optional": true
}, },
"bootstrap": { "bootstrap": {
"version": "4.6.0", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ=="
}, },
"boxen": { "boxen": {
"version": "4.2.0", "version": "4.2.0",
@@ -8265,9 +8443,9 @@
} }
}, },
"got": { "got": {
"version": "11.8.3", "version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz", "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
"integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==", "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"requires": { "requires": {
"@sindresorhus/is": "^4.0.0", "@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5", "@szmarczak/http-timer": "^4.0.5",
@@ -8291,9 +8469,9 @@
} }
}, },
"cacheable-request": { "cacheable-request": {
"version": "7.0.2", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
"integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
"requires": { "requires": {
"clone-response": "^1.0.2", "clone-response": "^1.0.2",
"get-stream": "^5.1.0", "get-stream": "^5.1.0",
@@ -8331,9 +8509,9 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
}, },
"keyv": { "keyv": {
"version": "4.0.4", "version": "4.5.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz",
"integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==", "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==",
"requires": { "requires": {
"json-buffer": "3.0.1" "json-buffer": "3.0.1"
} }
@@ -8359,9 +8537,9 @@
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="
}, },
"responselike": { "responselike": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"requires": { "requires": {
"lowercase-keys": "^2.0.0" "lowercase-keys": "^2.0.0"
} }
@@ -10256,41 +10434,6 @@
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"dev": true "dev": true
}, },
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"log-driver": { "log-driver": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
@@ -11113,16 +11256,17 @@
} }
}, },
"mongoose": { "mongoose": {
"version": "5.13.7", "version": "5.13.20",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.7.tgz", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.20.tgz",
"integrity": "sha512-ADIvftZ+KfoTALMZ0n8HvBlezFhcUd73hQaHQDwQ+3X+JZlqE47fUy9yhFZ2SjT+qzmuaCcIXCfhewIc38t2fQ==", "integrity": "sha512-TjGFa/XnJYt+wLmn8y9ssjyO2OhBMeEBtOHb9iJM16EWu2Du6L1Q6zSiEK2ziyYQM8agb4tumNIQFzqbxId7MA==",
"requires": { "requires": {
"@types/bson": "1.x || 4.0.x",
"@types/mongodb": "^3.5.27", "@types/mongodb": "^3.5.27",
"bson": "^1.1.4", "bson": "^1.1.4",
"kareem": "2.3.2", "kareem": "2.3.2",
"mongodb": "3.6.11", "mongodb": "3.7.4",
"mongoose-legacy-pluralize": "1.0.2", "mongoose-legacy-pluralize": "1.0.2",
"mpath": "0.8.3", "mpath": "0.8.4",
"mquery": "3.2.5", "mquery": "3.2.5",
"ms": "2.1.2", "ms": "2.1.2",
"optional-require": "1.0.x", "optional-require": "1.0.x",
@@ -11142,22 +11286,32 @@
} }
}, },
"mongodb": { "mongodb": {
"version": "3.6.11", "version": "3.7.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
"integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==", "integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
"requires": { "requires": {
"bl": "^2.2.1", "bl": "^2.2.1",
"bson": "^1.1.4", "bson": "^1.1.4",
"denque": "^1.4.1", "denque": "^1.4.1",
"optional-require": "^1.0.3", "optional-require": "^1.1.8",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"saslprep": "^1.0.0" "saslprep": "^1.0.0"
},
"dependencies": {
"optional-require": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz",
"integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==",
"requires": {
"require-at": "^1.0.6"
}
}
} }
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.7", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"requires": { "requires": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@@ -11300,9 +11454,9 @@
} }
}, },
"mpath": { "mpath": {
"version": "0.8.3", "version": "0.8.4",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz",
"integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g=="
}, },
"mquery": { "mquery": {
"version": "3.2.5", "version": "3.2.5",
@@ -11327,7 +11481,7 @@
"ms": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
} }
} }
}, },
@@ -12203,9 +12357,9 @@
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
}, },
"passport": { "passport": {
"version": "0.5.0", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.3.tgz",
"integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", "integrity": "sha512-gGc+70h4gGdBWNsR3FuV3byLDY6KBTJAIExGFXTpQaYfbbcHCBlRRKx7RBQSpqEqc5Hh2qVzRs7ssvSfOpkUEA==",
"requires": { "requires": {
"passport-strategy": "1.x.x", "passport-strategy": "1.x.x",
"pause": "0.0.1" "pause": "0.0.1"
@@ -12319,7 +12473,7 @@
"pause": { "pause": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
}, },
"pause-stream": { "pause-stream": {
"version": "0.0.11", "version": "0.0.11",
@@ -13153,6 +13307,11 @@
"integrity": "sha1-7W2Lm9Y4wTMosV3YOL1mYRHdeBw=", "integrity": "sha1-7W2Lm9Y4wTMosV3YOL1mYRHdeBw=",
"dev": true "dev": true
}, },
"require-at": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz",
"integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g=="
},
"require-directory": { "require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -13809,7 +13968,7 @@
"sliced": { "sliced": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz",
"integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA=="
}, },
"snapdragon": { "snapdragon": {
"version": "0.8.2", "version": "0.8.2",
@@ -14402,9 +14561,9 @@
} }
}, },
"stripe": { "stripe": {
"version": "12.9.0", "version": "12.18.0",
"resolved": "https://registry.npmjs.org/stripe/-/stripe-12.9.0.tgz", "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.18.0.tgz",
"integrity": "sha512-stYtrWetRYUsEbsUVyJaPG9Sppt0ds2szBqXsuDG6KZPPuUmCccbpceLrhoOBwNl1RziEfNB7oG9wg1n2eW+EQ==", "integrity": "sha512-cYjgBM2SY/dTm8Lr6eMyyONaHTZHA/QjHxFUIW5WH8FevSRIGAVtXEmBkUXF1fsqe7QvvRgQSGSJZmjDacegGg==",
"requires": { "requires": {
"@types/node": ">=8.1.0", "@types/node": ">=8.1.0",
"qs": "^6.11.0" "qs": "^6.11.0"
@@ -15636,9 +15795,9 @@
} }
}, },
"validator": { "validator": {
"version": "13.9.0", "version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
"integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==" "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ=="
}, },
"value-or-function": { "value-or-function": {
"version": "3.0.0", "version": "3.0.0",

View File

@@ -4,11 +4,11 @@
"version": "5.6.0", "version": "5.6.0",
"main": "./website/server/index.js", "main": "./website/server/index.js",
"dependencies": { "dependencies": {
"@babel/core": "^7.22.5", "@babel/core": "^7.22.10",
"@babel/preset-env": "^7.22.10", "@babel/preset-env": "^7.22.10",
"@babel/register": "^7.22.5", "@babel/register": "^7.22.5",
"@google-cloud/trace-agent": "^7.1.2", "@google-cloud/trace-agent": "^7.1.2",
"@parse/node-apn": "^5.1.3", "@parse/node-apn": "^5.2.3",
"@slack/webhook": "^6.1.0", "@slack/webhook": "^6.1.0",
"accepts": "^1.3.8", "accepts": "^1.3.8",
"amazon-payments": "^0.2.9", "amazon-payments": "^0.2.9",
@@ -17,7 +17,7 @@
"apple-auth": "^1.0.9", "apple-auth": "^1.0.9",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"bootstrap": "^4.6.0", "bootstrap": "^4.6.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-session": "^2.0.0", "cookie-session": "^2.0.0",
"coupon-code": "^0.4.5", "coupon-code": "^0.4.5",
@@ -31,7 +31,7 @@
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"express-validator": "^5.2.0", "express-validator": "^5.2.0",
"glob": "^8.1.0", "glob": "^8.1.0",
"got": "^11.8.3", "got": "^11.8.6",
"gulp": "^4.0.0", "gulp": "^4.0.0",
"gulp-babel": "^8.0.0", "gulp-babel": "^8.0.0",
"gulp-imagemin": "^7.1.0", "gulp-imagemin": "^7.1.0",
@@ -49,12 +49,12 @@
"method-override": "^3.0.0", "method-override": "^3.0.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"moment-recur": "^1.0.7", "moment-recur": "^1.0.7",
"mongoose": "^5.13.7", "mongoose": "^5.13.20",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"nconf": "^0.12.0", "nconf": "^0.12.0",
"node-gcm": "^1.0.5", "node-gcm": "^1.0.5",
"on-headers": "^1.0.2", "on-headers": "^1.0.2",
"passport": "^0.5.0", "passport": "^0.5.3",
"passport-facebook": "^3.0.0", "passport-facebook": "^3.0.0",
"passport-google-oauth2": "^0.2.0", "passport-google-oauth2": "^0.2.0",
"passport-google-oauth20": "2.0.0", "passport-google-oauth20": "2.0.0",
@@ -67,12 +67,12 @@
"remove-markdown": "^0.5.0", "remove-markdown": "^0.5.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"short-uuid": "^4.2.2", "short-uuid": "^4.2.2",
"stripe": "^12.9.0", "stripe": "^12.18.0",
"superagent": "^8.1.2", "superagent": "^8.1.2",
"universal-analytics": "^0.5.3", "universal-analytics": "^0.5.3",
"useragent": "^2.1.9", "useragent": "^2.1.9",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"validator": "^13.9.0", "validator": "^13.11.0",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"winston": "^3.10.0", "winston": "^3.10.0",
"winston-loggly-bulk": "^3.2.1", "winston-loggly-bulk": "^3.2.1",
@@ -110,7 +110,7 @@
"apidoc": "gulp apidoc" "apidoc": "gulp apidoc"
}, },
"devDependencies": { "devDependencies": {
"axios": "^1.3.6", "axios": "^1.4.0",
"chai": "^4.3.7", "chai": "^4.3.7",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"chai-moment": "^0.1.0", "chai-moment": "^0.1.0",

View File

@@ -0,0 +1,64 @@
import { v4 as generateUUID } from 'uuid';
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('POST /members/:memberId/clear-flags', () => {
let reporter;
let admin;
let moderator;
beforeEach(async () => {
reporter = await generateUser();
admin = await generateUser({ permissions: { userSupport: true } });
moderator = await generateUser({ permissions: { moderator: true } });
await reporter.post(`/members/${admin._id}/flag`);
});
context('error cases', () => {
it('returns error when memberId is not a UUID', async () => {
await expect(moderator.post('/members/gribbly/clear-flags'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns error when member with UUID is not found', async () => {
const randomId = generateUUID();
await expect(moderator.post(`/members/${randomId}/clear-flags`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', { userId: randomId }),
});
});
it('returns error when requesting user is not a moderator', async () => {
await expect(reporter.post(`/members/${admin._id}/clear-flags`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Only a moderator may clear reports from a profile.',
});
});
});
context('valid request', () => {
it('removes a single flag from user', async () => {
await expect(moderator.post(`/members/${admin._id}/clear-flags`)).to.eventually.be.ok;
const updatedTarget = await admin.get(`/hall/heroes/${admin._id}`);
expect(updatedTarget.profile.flags).to.eql({});
});
it('removes multiple flags from user', async () => {
await moderator.post(`/members/${admin._id}/flag`);
await expect(moderator.post(`/members/${admin._id}/clear-flags`)).to.eventually.be.ok;
const updatedTarget = await admin.get(`/hall/heroes/${admin._id}`);
expect(updatedTarget.profile.flags).to.eql({});
});
});
});

View File

@@ -0,0 +1,151 @@
import { v4 as generateUUID } from 'uuid';
import moment from 'moment';
import nconf from 'nconf';
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import { IncomingWebhook } from '@slack/webhook';
describe('POST /members/:memberId/flag', () => {
let reporter;
let target;
beforeEach(async () => {
reporter = await generateUser();
target = await generateUser({
'profile.blurb': 'Naughty Text',
'profile.imageUrl': 'https://evil.com/',
});
});
context('error cases', () => {
it('returns error when memberId is not a UUID', async () => {
await expect(reporter.post('/members/gribbly/flag'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns error when member with UUID is not found', async () => {
const randomId = generateUUID();
await expect(reporter.post(`/members/${randomId}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', { userId: randomId }),
});
});
it('returns error when non-admin flags same profile twice', async () => {
await reporter.post(`/members/${target._id}/flag`);
await expect (reporter.post(`/members/${target._id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'A profile can not be flagged more than once by the same user.',
});
});
});
context('valid request', () => {
let admin;
const comment = 'this profile is bad';
const source = 'Third Party Script';
beforeEach(async () => {
admin = await generateUser({ 'permissions.userSupport': true });
sandbox.stub(IncomingWebhook.prototype, 'send').returns(Promise.resolve());
});
afterEach(() => {
sandbox.restore();
});
it('adds flags object to target user', async () => {
await reporter.post(`/members/${target._id}/flag`);
const updatedTarget = await admin.get(`/hall/heroes/${target._id}`);
expect(updatedTarget.profile.flags[reporter._id]).to.have.all.keys([
'comment',
'source',
'timestamp',
]);
expect(moment(updatedTarget.profile.flags[reporter._id].timestamp).toDate()).to.be.a('date');
});
it('allows addition of a comment and source', async () => {
await reporter.post(`/members/${target._id}/flag`, {
comment,
source,
});
const updatedTarget = await admin.get(`/hall/heroes/${target._id}`);
expect(updatedTarget.profile.flags[reporter._id].comment).to.eql(comment);
expect(updatedTarget.profile.flags[reporter._id].source).to.eql(source);
});
it('allows moderator to flag twice', async () => {
const moderator = await generateUser({ 'permissions.moderator': true });
await moderator.post(`/members/${target._id}/flag`);
await expect(moderator.post(`/members/${target._id}/flag`)).to.eventually.be.ok;
});
it('allows multiple non-moderators to flag individually', async () => {
await admin.post(`/members/${target._id}/flag`);
await reporter.post(`/members/${target._id}/flag`);
const updatedTarget = await admin.get(`/hall/heroes/${target._id}`);
expect(updatedTarget.profile.flags[admin._id]).to.exist;
expect(updatedTarget.profile.flags[reporter._id]).to.exist;
});
it('sends a flag report to moderation Slack', async () => {
const BASE_URL = nconf.get('BASE_URL');
await reporter.post(`/members/${target._id}/flag`, {
comment,
source,
});
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `@${reporter.auth.local.username} (${reporter._id}; language: ${reporter.preferences.language}) flagged @${target.auth.local.username}'s profile from ${source} and commented: ${comment}`,
attachments: [{
fallback: 'Flag Profile',
color: 'danger',
title: 'User Profile Report',
title_link: `${BASE_URL}/profile/${target._id}`,
text: `Display Name: ${target.profile.name}\n\nImage URL: ${target.profile.imageUrl}\n\nAbout: ${target.profile.blurb}`,
mrkdwn_in: [
'text',
],
}],
});
/* eslint-enable camelcase */
});
it('excludes empty fields when sending Slack message', async () => {
const BASE_URL = nconf.get('BASE_URL');
await reporter.post(`/members/${admin._id}/flag`, {
comment,
source,
});
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `@${reporter.auth.local.username} (${reporter._id}; language: ${reporter.preferences.language}) flagged @${admin.auth.local.username}'s profile from ${source} and commented: ${comment}`,
attachments: [{
fallback: 'Flag Profile',
color: 'danger',
title: 'User Profile Report',
title_link: `${BASE_URL}/profile/${admin._id}`,
text: `Display Name: ${admin.profile.name}`,
mrkdwn_in: [
'text',
],
}],
});
/* eslint-enable camelcase */
});
});
});

View File

@@ -13318,11 +13318,34 @@
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
}, },
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"optional": true
},
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
}, },
"loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -13354,6 +13377,38 @@
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
} }
}, },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.8.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
}
}
},
"wrap-ansi": { "wrap-ansi": {
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -30581,76 +30636,6 @@
} }
} }
}, },
"vue-loader-v16": {
"version": "npm:vue-loader@16.8.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
"integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q=="
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-mugen-scroll": { "vue-mugen-scroll": {
"version": "0.2.6", "version": "0.2.6",
"resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz", "resolved": "https://registry.npmjs.org/vue-mugen-scroll/-/vue-mugen-scroll-0.2.6.tgz",

View File

@@ -5,8 +5,8 @@
font-weight: bold; font-weight: bold;
line-height: 1.71; line-height: 1.71;
border: 1px solid transparent; border: 1px solid transparent;
padding: 0.219rem 0.75rem; padding: 4px 12px;
border-radius: 2px; border-radius: 4px;
box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24); box-shadow: 0 1px 3px 0 rgba($black, 0.12), 0 1px 2px 0 rgba($black, 0.24);
color: $white; color: $white;

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#nw2v1izcda)">
<path d="m8 4.01 2.2 5.4 3.8-3.4-2 6H4l-2-6 3.8 3.4L8 4.01zm0-2c-.81 0-1.55.49-1.85 1.25L5.02 6.03 3.33 4.52A1.98 1.98 0 0 0 2 4.01 2.002 2.002 0 0 0 .1 6.64l2 6A2 2 0 0 0 4 14.01h8a2 2 0 0 0 1.9-1.37l1.97-5.9a1.997 1.997 0 0 0-1.85-2.73h-.04c-.5.01-.95.2-1.3.51L11 6.02 9.87 3.25A2.012 2.012 0 0 0 8.02 2L8 2.01z" fill="#4E4A57"/>
</g>
<defs>
<clipPath id="nw2v1izcda">
<path fill="#fff" d="M0 0h16v16H0z"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 617 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#464rik5fna)">
<path d="M12 2c1.1 0 2 .9 2 2v6c0 1.1-.9 2-2 2l-4 2v-2H4c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h8zm0-2H4C1.79 0 0 1.79 0 4v6c0 2.21 1.79 4 4 4h2c0 .69.36 1.34.95 1.7a1.993 1.993 0 0 0 1.94.09l3.65-1.83A4 4 0 0 0 15.99 10V4c0-2.21-1.79-4-4-4H12zM9.41 7l1.29-1.29A.996.996 0 1 0 9.29 4.3L8 5.59 6.71 4.3A.996.996 0 1 0 5.3 5.71L6.59 7 5.3 8.29a.996.996 0 0 0 .71 1.7c.26 0 .51-.1.71-.29l1.29-1.29L9.3 9.7c.2.2.45.29.71.29.26 0 .51-.1.71-.29a.996.996 0 0 0 0-1.41L9.43 7h-.02z" fill="#4E4A57"/>
</g>
<defs>
<clipPath id="464rik5fna">
<path fill="#fff" d="M0 0h16v16H0z"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 770 B

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#mzh0y8jf2a)">
<path d="M12 2c1.1 0 2 .9 2 2v6c0 1.1-.9 2-2 2l-4 2v-2H4c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h8zm0-2H4C1.79 0 0 1.79 0 4v6c0 2.21 1.79 4 4 4h2c0 .69.36 1.34.95 1.7a1.993 1.993 0 0 0 1.94.09l3.65-1.83A4 4 0 0 0 15.99 10V4c0-2.21-1.79-4-4-4H12zm-.17 7c0-.55-.45-1-1-1H5.17c-.55 0-1 .45-1 1s.45 1 1 1h5.66c.55 0 1-.45 1-1z" fill="#4E4A57"/>
</g>
<defs>
<clipPath id="mzh0y8jf2a">
<path fill="#fff" d="M0 0h16v16H0z"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 618 B

View File

@@ -84,7 +84,7 @@
v-if="invites.length > 0" v-if="invites.length > 0"
class="row" class="row"
> >
<div class="col-6 offset-3 nav"> <div class="col-6 offset-3 nav mt-2 mb-3">
<div <div
class="nav-item" class="nav-item"
:class="{active: selectedPage === 'members'}" :class="{active: selectedPage === 'members'}"
@@ -111,17 +111,18 @@
:key="member._id" :key="member._id"
class="row" class="row"
> >
<div class="col-11 no-padding-left"> <div class="col-11 pl-0">
<member-details <member-details
:member="member" :member="member"
:class-badge-position="'next-to-name'" :class-badge-position="'next-to-name'"
class="ml-4"
/> />
</div> </div>
<div class="col-1 actions"> <div class="col-1 actions">
<b-dropdown right="right"> <b-dropdown right="right">
<div <div
slot="button-content" slot="button-content"
class="svg-icon inline dots" class="svg-icon inline dots pt-1"
v-html="icons.dots" v-html="icons.dots"
></div> ></div>
<b-dropdown-item @click="sendMessage(member)"> <b-dropdown-item @click="sendMessage(member)">
@@ -216,7 +217,7 @@
:key="member._id" :key="member._id"
class="row" class="row"
> >
<div class="col-11 no-padding-left"> <div class="col-11 pl-0">
<member-details :member="member" /> <member-details :member="member" />
</div> </div>
<div class="col-1 actions"> <div class="col-1 actions">
@@ -259,10 +260,6 @@
color: #878190; color: #878190;
} }
.no-padding-left {
padding-left: 0;
}
.modal-body { .modal-body {
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
@@ -303,21 +300,15 @@
} }
.actions { .actions {
padding-top: 5em; .b-dropdown {
position: absolute;
right: 24px;
top: 8px;
}
.dots { .dots {
height: 16px; height: 16px;
width: 4px; width: 4px;
} }
.btn-group {
margin-left: -2em;
margin-top: -2em;
}
.action-icon {
margin-right: 1em;
}
} }
#members-modal_modal_body { #members-modal_modal_body {
@@ -353,8 +344,6 @@
.nav { .nav {
font-weight: bold; font-weight: bold;
margin-bottom: .5em;
margin-top: .5em;
} }
.nav-item { .nav-item {

View File

@@ -16,6 +16,7 @@
:class-badge-position="'next-to-name'" :class-badge-position="'next-to-name'"
:is-header="true" :is-header="true"
:disable-name-styling="true" :disable-name-styling="true"
class="mr-3"
/> />
<div <div
v-if="hasParty" v-if="hasParty"

View File

@@ -3,6 +3,7 @@
<creator-intro /> <creator-intro />
<profileModal /> <profileModal />
<report-flag-modal /> <report-flag-modal />
<report-member-modal />
<send-gift-modal /> <send-gift-modal />
<select-user-modal /> <select-user-modal />
<b-navbar <b-navbar
@@ -732,6 +733,7 @@ import creatorIntro from '../creatorIntro';
import notificationMenu from './notificationsDropdown'; import notificationMenu from './notificationsDropdown';
import profileModal from '../userMenu/profileModal'; import profileModal from '../userMenu/profileModal';
import reportFlagModal from '../chat/reportFlagModal'; import reportFlagModal from '../chat/reportFlagModal';
import reportMemberModal from '../members/reportMemberModal';
import sendGiftModal from '@/components/payments/sendGiftModal'; import sendGiftModal from '@/components/payments/sendGiftModal';
import selectUserModal from '@/components/payments/selectUserModal'; import selectUserModal from '@/components/payments/selectUserModal';
import sync from '@/mixins/sync'; import sync from '@/mixins/sync';
@@ -744,6 +746,7 @@ export default {
notificationMenu, notificationMenu,
profileModal, profileModal,
reportFlagModal, reportFlagModal,
reportMemberModal,
sendGiftModal, sendGiftModal,
selectUserModal, selectUserModal,
userDropdown, userDropdown,

View File

@@ -1,10 +1,10 @@
<template> <template>
<div <div
class="member-details" class="member-details d-flex"
:class="{ condensed, expanded, 'd-flex': isHeader, row: !isHeader, }" :class="{ condensed, expanded }"
@click="showMemberModal(member)" @click="showMemberModal(member)"
> >
<div class="avatar-container" :class="{ 'col-4': !isHeader }"> <div class="avatar-container">
<avatar <avatar
:member="member" :member="member"
:hide-class-badge="classBadgePosition !== 'under-avatar'" :hide-class-badge="classBadgePosition !== 'under-avatar'"
@@ -15,14 +15,17 @@
</div> </div>
<div <div
class="member-stats" class="member-stats"
:class="{'col-8': !expanded && !isHeader}" :class="{ 'mt-2': !isHeader }"
> >
<div class="d-flex align-items-center profile-first-row"> <div class="d-flex align-items-center profile-first-row">
<class-badge <class-badge
v-if="classBadgePosition === 'next-to-name'" v-if="classBadgePosition === 'next-to-name'"
:member-class="member.stats.class" :member-class="member.stats.class"
/> />
<div class="d-flex flex-column profile-name-character"> <div
class="d-flex flex-column"
:class="{ 'ml-2': classBadgePosition === 'next-to-name' }"
>
<h3 class="character-name"> <h3 class="character-name">
<span v-if="member.contributor && member.contributor.level > 0 && !disableNameStyling"> <span v-if="member.contributor && member.contributor.level > 0 && !disableNameStyling">
<user-link <user-link
@@ -30,19 +33,23 @@
:name="member.profile.name" :name="member.profile.name"
:backer="member.backer" :backer="member.backer"
:contributor="member.contributor" :contributor="member.contributor"
:showBuffed="isBuffed"
:context="'profile'"
/> />
</span> </span>
<span v-else>{{ member.profile.name }}</span> <div v-else>
<span>{{ member.profile.name }}</span>
<div <div
v-if="isBuffed" v-if="isBuffed"
v-b-tooltip.hover.bottom="$t('buffed')" v-b-tooltip.hover.bottom="$t('buffed')"
class="is-buffed" class="is-buffed ml-2 mt-n1"
> >
<div <div
class="svg-icon" class="svg-icon"
v-html="icons.buff" v-html="icons.buff"
></div> ></div>
</div> </div>
</div>
</h3> </h3>
<div class="small-text character-level"> <div class="small-text character-level">
<span <span
@@ -98,9 +105,12 @@
} }
} }
.standard-page .member-details {
padding-left: 24px;
}
.member-stats { .member-stats {
padding-left: 12px; padding-left: 12px;
padding-right: 24px;
opacity: 1; opacity: 1;
transition: width 0.15s ease-out; transition: width 0.15s ease-out;
} }
@@ -114,10 +124,6 @@
color: $header-color; color: $header-color;
} }
.profile-name-character {
margin-left: 12px;
}
.character-name { .character-name {
margin-bottom: 1px; margin-bottom: 1px;
color: $white; color: $white;
@@ -133,7 +139,6 @@
height: 20px; height: 20px;
background: $header-dark-background; background: $header-dark-background;
display: inline-block; display: inline-block;
margin-left: 16px;
vertical-align: middle; vertical-align: middle;
padding-top: 4px; padding-top: 4px;

View File

@@ -0,0 +1,199 @@
<template>
<b-modal
id="report-profile"
:title="$t('reportPlayer')"
:hide-footer="!hasPermission(user, 'moderator')"
size="md"
>
<div slot="modal-header">
<h2 class="mt-2 mb-0"> {{ $t('reportPlayer') }} </h2>
<close-x
@close="close()"
/>
</div>
<div>
<blockquote>
<strong> {{ displayName }} </strong>
<p class="mb-0"> {{ username }} </p>
</blockquote>
<div>
<strong>{{ $t('whyReportingPlayer') }}</strong>
<textarea
v-model="reportComment"
class="mt-2 form-control"
:placeholder="$t('whyReportingPlayerPlaceholder')"
></textarea>
</div>
<p
class="mb-2"
v-html="$t('playerReportModalBody', abuseFlagModalBody)">
</p>
</div>
<div class="footer text-center d-flex flex-column">
<button
class="btn btn-danger mx-auto mb-3"
:disabled="!reportComment"
:class="{ disabled: !reportComment }"
@click="reportAbuse()"
>
{{ $t('report') }}
</button>
<a
class="cancel-link"
@click.prevent="close()"
>{{ $t('cancel') }}</a>
</div>
<div
slot="modal-footer"
>
<div
class="d-flex"
@click="resetFlags()"
>
<div
v-once
class="svg-icon icon-16 color my-auto mr-2"
v-html="icons.report"
></div>
<a>Reset Flags</a>
</div>
</div>
</b-modal>
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
#report-profile {
.modal-header {
padding: 24px;
border-bottom: none;
}
.modal-body {
padding: 0px 24px 24px 24px;
}
.modal-footer {
color: $maroon-50;
display: flex;
justify-content: center;
border-top: none;
height: 48px;
background-color: rgba($red-500, 0.25);
margin-top: -8px;
padding: 0px;
a {
margin-top: 2px;
color: $maroon-50;
}
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
strong, p {
line-height: 1.71;
}
h2 {
color: $maroon-100;
}
blockquote {
border-radius: 4px;
background-color: $gray-700;
padding: .5rem 1rem;
}
textarea {
margin-top: 1em;
margin-bottom: 1em;
border-radius: 2px;
border: solid 1px $gray-400;
min-height: 106px;
}
.footer {
padding: 1rem 1rem 0rem 1rem;
}
</style>
<script>
import closeX from '@/components/ui/closeX';
import notifications from '@/mixins/notifications';
import markdownDirective from '@/directives/markdown';
import { userStateMixin } from '../../mixins/userState';
import report from '@/assets/svg/report.svg';
export default {
components: {
closeX,
},
directives: {
markdown: markdownDirective,
},
mixins: [notifications, userStateMixin],
data () {
const abuseFlagModalBody = {
firstLinkStart: '<a href="/static/community-guidelines" target="_blank">',
secondLinkStart: '<a href="/static/terms" target="_blank">',
linkEnd: '</a>',
};
return {
abuseFlagModalBody,
displayName: '',
username: '',
reportComment: '',
icons: Object.freeze({
report,
}),
};
},
mounted () {
this.$root.$on('habitica::report-profile', this.handleReport);
},
beforeDestroy () {
this.$root.$off('habitica::report-profile', this.handleReport);
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'report-profile');
},
async reportAbuse () {
const result = await this.$store.dispatch('members:reportMember', {
memberId: this.memberId,
source: this.$route.fullPath,
comment: this.reportComment,
});
if (result.status === 200) {
this.text(this.$t('abuseReported'));
this.$root.$emit('habitica:report-profile-result', result.data.data);
} else {
this.error(result.statusText);
}
this.close();
},
handleReport (data) {
if (!data.memberId) return;
this.displayName = data.displayName;
this.username = `@${data.username}`;
this.memberId = data.memberId;
this.reportComment = '';
this.$root.$emit('bv::show::modal', 'report-profile');
},
async resetFlags () {
const result = await this.$store.dispatch('members:clearMemberFlags', {
memberId: this.memberId,
});
if (result.status === 200) {
this.text('Flags cleared.');
} else {
this.err(result.statusText);
}
this.close();
},
},
};
</script>

View File

@@ -265,7 +265,6 @@
align-items: center; align-items: center;
} }
} }
}
.how-many-to-sell { .how-many-to-sell {
font-weight: bold !important; font-weight: bold !important;
@@ -313,8 +312,7 @@
line-height: 1.33; line-height: 1.33;
color: $gray-200; color: $gray-200;
} }
}
} }
</style> </style>

View File

@@ -486,6 +486,15 @@ export default {
this.$store.dispatch('common:setTitle', { this.$store.dispatch('common:setTitle', {
section: this.$t('tasks'), section: this.$t('tasks'),
}); });
if (this.$store.state.postLoadModal) {
const modalToLoad = this.$store.state.postLoadModal;
if (modalToLoad.includes('profile')) {
this.$router.push(modalToLoad);
} else {
this.$root.$emit('bv::show::modal', modalToLoad);
}
this.$store.state.postLoadModal = '';
}
}, },
methods: { methods: {
...mapActions({ setUser: 'user:set' }), ...mapActions({ setUser: 'user:set' }),

View File

@@ -9,8 +9,19 @@
{{ displayName }} {{ displayName }}
<div <div
class="svg-icon icon-12" class="svg-icon icon-12"
:class="{ 'margin-bump': context === 'profile' }"
v-html="tierIcon()" v-html="tierIcon()"
></div> ></div>
<div
v-if="showBuffed"
v-b-tooltip.hover.bottom="$t('buffed')"
class="is-buffed ml-2 d-flex align-items-center"
>
<div
class="svg-icon m-auto"
v-html="icons.buff"
></div>
</div>
</router-link> </router-link>
</template> </template>
@@ -39,7 +50,12 @@
&[class*="tier"] .svg-icon { &[class*="tier"] .svg-icon {
margin-top: 5px; margin-top: 5px;
&.margin-bump {
margin-top: 7px;
} }
}
&.npc .svg-icon { &.npc .svg-icon {
margin-top: 4px; margin-top: 4px;
} }
@@ -52,6 +68,21 @@
} }
} }
} }
.is-buffed {
width: 20px;
height: 20px;
background: $header-dark-background;
display: inline-block;
margin-top: 2px;
.svg-icon {
display: block;
width: 10px;
height: 12px;
margin: 0 auto;
}
}
</style> </style>
<script> <script>
@@ -70,6 +101,7 @@ import tier7 from '@/assets/svg/tier-7.svg';
import tier8 from '@/assets/svg/tier-mod.svg'; import tier8 from '@/assets/svg/tier-mod.svg';
import tier9 from '@/assets/svg/tier-staff.svg'; import tier9 from '@/assets/svg/tier-staff.svg';
import tierNPC from '@/assets/svg/tier-npc.svg'; import tierNPC from '@/assets/svg/tier-npc.svg';
import buffIcon from '@/assets/svg/buff.svg';
export default { export default {
mixins: [styleHelper], mixins: [styleHelper],
@@ -81,6 +113,8 @@ export default {
'contributor', 'contributor',
'hideTooltip', 'hideTooltip',
'smallerStyle', 'smallerStyle',
'showBuffed',
'context',
], ],
data () { data () {
return { return {
@@ -95,6 +129,7 @@ export default {
tier8, tier8,
tier9, tier9,
tierNPC, tierNPC,
buff: buffIcon,
}), }),
}; };
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
<template>
<div
id="achievements"
class="standard-page"
>
<div
v-for="(category, key) in achievements"
:key="key"
class="row category-row"
>
<h3 class="text-center">
{{ $t(`${key}Achievs`) }}
</h3>
<div class="">
<div class="row achievements-row justify-content-center">
<div
v-for="(achievement, achievKey) in achievementsCategory(key, category)"
:key="achievKey"
class="achievement-wrapper col text-center"
>
<div
:id="achievKey + '-achievement'"
class="box achievement-container"
:class="{'achievement-unearned': !achievement.earned}"
>
<b-popover
:target="'#' + achievKey + '-achievement'"
triggers="hover"
placement="top"
>
<h4 class="popover-content-title">
{{ achievement.title }}
</h4>
<div
class="popover-content-text"
v-html="achievement.text"
></div>
</b-popover>
<div
v-if="achievement.earned"
class="achievement"
:class="achievement.icon + '2x'"
>
<div
v-if="achievement.optionalCount"
class="counter badge badge-pill stack-count"
>
{{ achievement.optionalCount }}
</div>
</div>
<div
v-if="!achievement.earned"
class="achievement achievement-unearned achievement-unearned2x"
></div>
</div>
</div>
</div>
<div
v-if="achievementsCategories[key].number > 5"
class="btn btn-flat btn-show-more"
@click="toggleAchievementsCategory(key)"
>
{{ achievementsCategories[key].open ?
$t('hideAchievements', {category: $t(`${key}Achievs`)}) :
$t('showAllAchievements', {category: $t(`${key}Achievs`)})
}}
</div>
</div>
</div>
<hr class="">
<div class="row">
<div
v-if="user.achievements.challenges"
class="col-12 col-md-6"
>
<div class="achievement-icon achievement-karaoke-2x"></div>
<h3 class="text-center">
{{ $t('challengesWon') }}
</h3>
<div
v-for="chal in user.achievements.challenges"
:key="chal"
class="achievement-list-item"
>
<span v-markdown="chal"></span>
</div>
</div>
<div
v-if="user.achievements.quests"
class="col-12 col-md-6"
>
<div class="achievement-icon achievement-alien2x"></div>
<h3 class="text-center">
{{ $t('questsCompleted') }}
</h3>
<div
v-for="(value, key) in user.achievements.quests"
:key="key"
class="achievement-list-item d-flex justify-content-between"
>
<span>{{ content.quests[key].text() }}</span>
<span
v-if="value > 1"
class="badge badge-pill stack-count"
>
{{ value }}
</span>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
#achievements {
.category-row {
margin-bottom: 34px;
justify-content: center;
&:last-child {
margin-bottom: 0px;
}
}
.achievements-row {
margin: 0 auto;
max-width: 590px;
}
.achievement-wrapper {
margin-left: 12px;
margin-right: 12px;
max-width: 94px;
min-width: 94px;
padding: 0px;
width: 94px;
}
.box {
background: $white;
margin: 0 auto;
margin-bottom: 16px;
padding-top: 20px;
}
hr {
margin-bottom: 48px;
margin-top: 48px;
}
.box.achievement-unearned {
background-color: $gray-600;
}
.counter.badge {
background-color: $orange-100;
color: $white;
max-height: 24px;
position: absolute;
right: -8px;
top: -12.8px;
}
.achievement-icon {
margin: 0 auto;
}
.achievement-list-item {
border-top: 1px solid $gray-500;
padding-bottom: 12px;
padding-top: 11px;
&:last-child {
border-bottom: 1px solid $gray-500;
}
.badge {
background: $gray-600;
color: $gray-300;
height: fit-content;
margin-right: 8px;
}
}
}
</style>
<script>
// import moment from 'moment';
// import axios from 'axios';
// import each from 'lodash/each';
// import cloneDeep from 'lodash/cloneDeep';
// import closeX from '../ui/closeX';
import achievementsLib from '@/../../common/script/libs/achievements';
import Content from '@/../../common/script/content';
import error404 from '../404';
// import { userCustomStateMixin } from '../../mixins/userState';
export default {
components:
error404,
// closeX,
props: ['userId', 'startingPage'],
data () {
return {
selectedPage: 'achievements',
achievements: {},
achievementsCategories: {}, // number, open
content: Content,
};
},
methods: {
async loadUser () {
let user = null;
const profileUserId = this.userId;
if (profileUserId && profileUserId !== this.userLoggedIn._id) {
const response = await this.$store.dispatch('members:fetchMember', {
memberId: profileUserId,
unpack: false,
});
if (response.response && response.response.status === 404) {
user = null;
this.$store.dispatch('snackbars:add', {
title: 'Habitica',
text: this.$t('messageDeletedUser'),
type: 'error',
timeout: false,
});
} else if (response.status && response.status === 200) {
user = response.data.data;
}
} else {
user = this.userLoggedIn;
}
if (user) {
if (!user.achievements.quests) user.achievements.quests = {};
if (!user.achievements.challenges) user.achievements.challenges = {};
// @TODO: this common code should handle the above
this.achievements = achievementsLib.getAchievementsForProfile(user);
const achievementsCategories = {};
Object.keys(this.achievements).forEach(category => {
achievementsCategories[category] = {
open: false,
number: Object.keys(this.achievements[category].achievements).length,
};
});
this.achievementsCategories = achievementsCategories;
this.user = user;
}
this.userLoaded = true;
},
},
};
</script>

View File

@@ -1,12 +1,16 @@
<template> <template>
<b-modal <b-modal
id="profile" id="profile"
size="lg"
:hide-footer="true" :hide-footer="true"
:hide-header="true"
@hide="beforeHide" @hide="beforeHide"
@shown="onShown()" @shown="onShown()"
> >
<div slot="modal-header">
<close-x
@close="close()"
/>
</div>
<profile <profile
:user-id="userId" :user-id="userId"
:starting-page="startingPage" :starting-page="startingPage"
@@ -15,33 +19,62 @@
</b-modal> </b-modal>
</template> </template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
#profile {
.modal-header {
background-color: $white;
border-bottom: none;
padding: 0px;
}
.modal-dialog {
max-width: 684px;
}
.modal-body {
padding: 0;
border-radius: 12px;
background-color: $white;
}
.modal-content {
background: $gray-700;
padding: 0;
}
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '~@/assets/scss/colors.scss'; @import '~@/assets/scss/colors.scss';
.header { .modal-close {
width: 100%; z-index: 1;
} }
</style> </style>
<script> <script>
import profile from './profile'; import profile from './profile';
import closeX from '../ui/closeX';
export default { export default {
components: { components: {
profile, profile,
closeX,
}, },
data () { data () {
return { return {
userId: undefined, userId: undefined,
startingPage: undefined, startingPage: undefined,
path: undefined, fromPath: undefined,
toPath: undefined,
}; };
}, },
mounted () { mounted () {
this.$root.$on('habitica:show-profile', data => { this.$root.$on('habitica:show-profile', data => {
this.userId = data.userId; this.userId = data.userId;
this.startingPage = data.startingPage || 'profile'; this.startingPage = data.startingPage || 'profile';
this.path = data.path; this.fromPath = data.fromPath;
this.toPath = data.toPath;
this.$root.$emit('bv::show::modal', 'profile'); this.$root.$emit('bv::show::modal', 'profile');
}); });
}, },
@@ -50,13 +83,16 @@ export default {
}, },
methods: { methods: {
onShown () { onShown () {
window.history.pushState('', null, this.path); window.history.pushState('', null, this.toPath);
}, },
beforeHide () { beforeHide () {
if (this.$route.path !== window.location.pathname) { if (this.$route.path !== window.location.pathname) {
this.$router.back(); window.history.pushState('', null, this.fromPath);
} }
}, },
close () {
this.$root.$emit('bv::hide::modal', 'profile');
},
}, },
}; };

View File

@@ -1,35 +0,0 @@
<template>
<div class="container">
<div class="standard-page">
<profile
:user-id="userId"
:starting-page="startingPage"
/>
</div>
</div>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.header {
width: 100%;
}
</style>
<script>
import profile from './profile';
export default {
components: {
profile,
},
props: {
userId: String,
startingPage: {
type: String,
default: 'profile',
},
},
};
</script>

View File

@@ -8,7 +8,6 @@ import { PAGES } from '@/libs/consts';
import { STATIC_ROUTES } from './static-routes'; import { STATIC_ROUTES } from './static-routes';
import { USER_ROUTES } from './user-routes'; import { USER_ROUTES } from './user-routes';
import { DEPRECATED_ROUTES } from '@/router/deprecated-routes'; import { DEPRECATED_ROUTES } from '@/router/deprecated-routes';
import { ProfilePage } from './shared-route-imports';
// NOTE: when adding a page make sure to implement the `common:setTitle` action // NOTE: when adding a page make sure to implement the `common:setTitle` action
@@ -94,11 +93,7 @@ const router = new VueRouter({
{ {
name: 'userProfile', name: 'userProfile',
path: '/profile/:userId', path: '/profile/:userId',
component: ProfilePage,
props: true, props: true,
children: [
{ name: 'userProfilePage', path: ':startingPage', component: ProfilePage },
],
}, },
{ {
path: '/inventory', path: '/inventory',
@@ -319,34 +314,40 @@ router.beforeEach(async (to, from, next) => {
}); });
} }
if ((to.name === 'userProfile' || to.name === 'userProfilePage') && from.name !== null) { if ((to.name === 'userProfile')) {
let startingPage = 'profile'; let startingPage = 'profile';
if (to.params.startingPage !== undefined) { if (to.params.startingPage !== undefined) {
startingPage = to.params.startingPage; startingPage = to.params.startingPage;
} }
if (from.name === null) {
store.state.postLoadModal = `profile/${to.params.userId}`;
return next({ name: 'tasks' });
}
router.app.$emit('habitica:show-profile', { router.app.$emit('habitica:show-profile', {
userId: to.params.userId, userId: to.params.userId,
startingPage, startingPage,
path: to.path, fromPath: from.path,
toPath: to.path,
}); });
return null; return null;
} }
if (to.name === 'tasks' && to.query.openGemsModal === 'true') { if (to.name === 'tasks' && to.query.openGemsModal === 'true') {
setTimeout(() => router.app.$emit('bv::show::modal', 'buy-gems'), 500); store.state.postLoadModal = 'buy-gems';
return next({ name: 'tasks' }); return next({ name: 'tasks' });
} }
if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) { if ((to.name === 'stats' || to.name === 'achievements' || to.name === 'profile') && from.name !== null) {
router.app.$emit('habitica:show-profile', { router.app.$emit('habitica:show-profile', {
startingPage: to.name, startingPage: to.name,
path: to.path, fromPath: from.path,
toPath: to.path,
}); });
return null; return null;
} }
if (from.name === 'userProfile' || from.name === 'userProfilePage' || from.name === 'stats' || from.name === 'achievements' || from.name === 'profile') { if (from.name === 'userProfile' || from.name === 'stats' || from.name === 'achievements' || from.name === 'profile') {
router.app.$root.$emit('bv::hide::modal', 'profile'); router.app.$root.$emit('bv::hide::modal', 'profile');
} }

View File

@@ -1,3 +1 @@
export const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404'); export const NotFoundPage = () => import(/* webpackChunkName: "not-found" */'@/components/404');
export const ProfilePage = () => import(/* webpackChunkName: "user" */'@/components/userMenu/profilePage');

View File

@@ -1,6 +1,4 @@
import ParentPage from '@/components/parentPage.vue'; import ParentPage from '@/components/parentPage.vue';
import { ProfilePage } from './shared-route-imports';
// Settings // Settings
const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index'); const Settings = () => import(/* webpackChunkName: "settings" */'@/components/settings/index');
@@ -12,14 +10,10 @@ const Site = () => import(/* webpackChunkName: "settings" */'@/components/settin
const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription'); const Subscription = () => import(/* webpackChunkName: "settings" */'@/components/settings/subscription');
const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory'); const Transactions = () => import(/* webpackChunkName: "settings" */'@/components/settings/purchaseHistory');
export const USER_ROUTES = { export const USER_ROUTES = {
path: '/user', path: '/user',
component: ParentPage, component: ParentPage,
children: [ children: [
{ name: 'stats', path: 'stats', component: ProfilePage },
{ name: 'achievements', path: 'achievements', component: ProfilePage },
{ name: 'profile', path: 'profile', component: ProfilePage },
{ {
name: 'settings', name: 'settings',
path: 'settings', path: 'settings',

View File

@@ -114,3 +114,19 @@ export async function getPurchaseHistory (store, payload) {
const response = await axios.get(`${apiv4Prefix}/members/${payload.memberId}/purchase-history`); const response = await axios.get(`${apiv4Prefix}/members/${payload.memberId}/purchase-history`);
return response.data.data; return response.data.data;
} }
export async function reportMember (store, payload) {
const url = `${apiv4Prefix}/members/${payload.memberId}/flag`;
const data = {
comment: payload.comment,
source: payload.source,
};
const response = await axios.post(url, data);
return response;
}
export async function clearMemberFlags (store, payload) {
const url = `${apiv4Prefix}/members/${payload.memberId}/clear-flags`;
const response = await axios.post(url);
return response;
}

View File

@@ -151,6 +151,7 @@ export default function () {
bugReportOptions: { bugReportOptions: {
question: false, question: false,
}, },
postLoadModal: '',
}, },
}); });

View File

@@ -175,8 +175,9 @@
"photo": "Photo", "photo": "Photo",
"info": "Info", "info": "Info",
"joined": "Joined", "joined": "Joined",
"totalLogins": "Total Check Ins", "totalLogins": "Total Log Ins",
"latestCheckin": "Latest Check In", "latestCheckin": "Latest Log In",
"nextReward": "Next Log In Reward",
"editProfile": "Edit Profile", "editProfile": "Edit Profile",
"challengesWon": "Challenges Won", "challengesWon": "Challenges Won",
"questsCompleted": "Quests Completed", "questsCompleted": "Quests Completed",

View File

@@ -213,6 +213,16 @@
"loadEarlierMessages": "Load Earlier Messages", "loadEarlierMessages": "Load Earlier Messages",
"askQuestion": "Ask a Question", "askQuestion": "Ask a Question",
"emptyReportBugMessage": "Report Bug Message missing", "emptyReportBugMessage": "Report Bug Message missing",
"reportPlayer": "Report Player",
"blockPlayer": "Block Player",
"unblockPlayer": "Unblock Player",
"adminTools": "Admin Tools",
"viewAdminPanel": "View Admin Panel",
"shadowMute": "Shadow Mute",
"mutePlayer": "Mute",
"banPlayer": "Ban Player",
"unbanPlayer": "Unban Player",
"bannedPlayer": "This player is banned.",
"refreshList": "Refresh List", "refreshList": "Refresh List",
"leaveHabitica": "You are about to leave Habitica.com", "leaveHabitica": "You are about to leave Habitica.com",
"leaveHabiticaText": "Habitica is not responsible for the content of any linked website that is not owned or operated by HabitRPG.<br>Please note that these websites' practices may differ from Habiticas community guidelines.", "leaveHabiticaText": "Habitica is not responsible for the content of any linked website that is not owned or operated by HabitRPG.<br>Please note that these websites' practices may differ from Habiticas community guidelines.",
@@ -222,5 +232,9 @@
"question": "Question", "question": "Question",
"questionDescriptionText": "It's okay to ask your questions in your primary language if you aren't comfortable speaking in English.", "questionDescriptionText": "It's okay to ask your questions in your primary language if you aren't comfortable speaking in English.",
"questionPlaceholder": "Ask your question here", "questionPlaceholder": "Ask your question here",
"submitQuestion": "Submit Question" "submitQuestion": "Submit Question",
"reportPlayer": "Report Player",
"whyReportingPlayer": "Why are you reporting this player?",
"whyReportingPlayerPlaceholder": "Reason for report",
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habiticas Community Guidelines."
} }

View File

@@ -87,10 +87,12 @@
"PMCanNotReply": "You can not reply to this conversation", "PMCanNotReply": "You can not reply to this conversation",
"PMUserDoesNotReceiveMessages": "This user is no longer receiving private messages", "PMUserDoesNotReceiveMessages": "This user is no longer receiving private messages",
"PMUnblockUserToSendMessages": "Unblock this user to continue sending and receiving messages.", "PMUnblockUserToSendMessages": "Unblock this user to continue sending and receiving messages.",
"block": "Block", "block": "Block Player",
"unblock": "Un-block", "blockedUser": "<strong>You blocked this player.</strong>&nbsp;They cannot send you Private Messages but you will still see their posts.",
"bannedUser": "<strong>This player has been banned.</strong>",
"unblock": "Unblock Player",
"blockYourself": "You cannot block yourself", "blockYourself": "You cannot block yourself",
"blockWarning": "Block - This will have no effect if the player is a moderator now or becomes a moderator in future.", "blockWarning": "This will have no effect if the player is an admin.",
"inbox": "Inbox", "inbox": "Inbox",
"messageRequired": "A message is required.", "messageRequired": "A message is required.",
"toUserIDRequired": "A User ID is required", "toUserIDRequired": "A User ID is required",

View File

@@ -14,7 +14,7 @@ import {
NotAuthorized, NotAuthorized,
} from '../../libs/errors'; } from '../../libs/errors';
import { removeFromArray } from '../../libs/collectionManipulators'; import { removeFromArray } from '../../libs/collectionManipulators';
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email'; import { getUserInfo, getGroupUrl } from '../../libs/email';
import * as slack from '../../libs/slack'; import * as slack from '../../libs/slack';
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory'; import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
import { getAuthorEmailFromMessage } from '../../libs/chat'; import { getAuthorEmailFromMessage } from '../../libs/chat';
@@ -28,7 +28,6 @@ import { getAnalyticsServiceByEnvironment } from '../../libs/analyticsService';
const analytics = getAnalyticsServiceByEnvironment(); const analytics = getAnalyticsServiceByEnvironment();
const ACCOUNT_MIN_CHAT_AGE = Number(nconf.get('ACCOUNT_MIN_CHAT_AGE')); const ACCOUNT_MIN_CHAT_AGE = Number(nconf.get('ACCOUNT_MIN_CHAT_AGE'));
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map(email => ({ email, canSend: true }));
/** /**
* @apiDefine MessageNotFound * @apiDefine MessageNotFound
@@ -164,8 +163,6 @@ api.postChat = {
{ name: 'GROUP_URL', content: groupUrl }, { name: 'GROUP_URL', content: groupUrl },
]; ];
sendTxn(FLAG_REPORT_EMAILS, 'slur-report-to-mods', report);
// Slack the mods // Slack the mods
slack.sendSlurNotification({ slack.sendSlurNotification({
authorEmail, authorEmail,
@@ -241,8 +238,6 @@ api.postChat = {
{ name: 'GROUP_URL', content: groupUrl }, { name: 'GROUP_URL', content: groupUrl },
]; ];
sendTxn(FLAG_REPORT_EMAILS, 'shadow-muted-post-report-to-mods', report);
// Slack the mods // Slack the mods
slack.sendShadowMutedPostNotification({ slack.sendShadowMutedPostNotification({
authorEmail, authorEmail,
@@ -447,26 +442,6 @@ api.clearChatFlags = {
const authorEmail = getAuthorEmailFromMessage(message); const authorEmail = getAuthorEmailFromMessage(message);
const groupUrl = getGroupUrl(group); const groupUrl = getGroupUrl(group);
sendTxn(FLAG_REPORT_EMAILS, 'unflag-report-to-mods', [
{ name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString() },
{ name: 'MESSAGE_TEXT', content: message.text },
{ name: 'ADMIN_USERNAME', content: user.profile.name },
{ name: 'ADMIN_UUID', content: user._id },
{ name: 'ADMIN_EMAIL', content: adminEmailContent },
{ name: 'ADMIN_MODAL_URL', content: `/profile/${user._id}` },
{ name: 'AUTHOR_USERNAME', content: message.user },
{ name: 'AUTHOR_UUID', content: message.uuid },
{ name: 'AUTHOR_EMAIL', content: authorEmail },
{ name: 'AUTHOR_MODAL_URL', content: `/profile/${message.uuid}` },
{ name: 'GROUP_NAME', content: group.name },
{ name: 'GROUP_TYPE', content: group.type },
{ name: 'GROUP_ID', content: group._id },
{ name: 'GROUP_URL', content: groupUrl },
]);
res.respond(200, {}); res.respond(200, {});
}, },
}; };

View File

@@ -146,7 +146,7 @@ api.getHeroes = {
// Note, while the following routes are called getHero / updateHero // Note, while the following routes are called getHero / updateHero
// they can be used by admins to get/update any user // they can be used by admins to get/update any user
const heroAdminFields = 'auth balance contributor flags items lastCron party preferences profile.name purchased secret permissions'; const heroAdminFields = 'auth balance contributor flags items lastCron party preferences profile purchased secret permissions';
const heroAdminFieldsToFetch = heroAdminFields; // these variables will make more sense when... const heroAdminFieldsToFetch = heroAdminFields; // these variables will make more sense when...
const heroAdminFieldsToShow = heroAdminFields; // ... apiTokenObscured is added const heroAdminFieldsToShow = heroAdminFields; // ... apiTokenObscured is added
@@ -200,6 +200,7 @@ api.getHero = {
if (!heroRes.contributor) heroRes.contributor = {}; if (!heroRes.contributor) heroRes.contributor = {};
heroRes.secret = hero.getSecretData(); heroRes.secret = hero.getSecretData();
heroRes.profile.flags = hero.getFlagData();
res.respond(200, heroRes); res.respond(200, heroRes);
}, },

View File

@@ -11,6 +11,7 @@ import {
import { model as Group } from '../../models/group'; import { model as Group } from '../../models/group';
import { model as Challenge } from '../../models/challenge'; import { model as Challenge } from '../../models/challenge';
import { import {
BadRequest,
NotFound, NotFound,
NotAuthorized, NotAuthorized,
} from '../../libs/errors'; } from '../../libs/errors';
@@ -27,6 +28,7 @@ import {
} from '../../models/message'; } from '../../models/message';
import highlightMentions from '../../libs/highlightMentions'; import highlightMentions from '../../libs/highlightMentions';
import { handleGetMembersForChallenge } from '../../libs/challenges/handleGetMembersForChallenge'; import { handleGetMembersForChallenge } from '../../libs/challenges/handleGetMembersForChallenge';
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
const { achievements } = common; const { achievements } = common;
@@ -777,4 +779,92 @@ api.transferGems = {
}, },
}; };
/**
* @api {post} /api/v3/members/:memberId/flag Flag (report) a user
* @apiDescription Sends an email to staff about another user or their profile
* @apiName FlagUser
* @apiGroup Members
*
* @apiParam (Path) {UUID} memberId The unique ID of the user being flagged
* @apiParam (Body) {String} [comment] explain why the user was flagged
* @apiParam (Body) {String} [source] URL or view from which the user was flagged
*
* @apiSuccess {Object} data The flagged user
* @apiSuccess {UUID} data.id The id of the flagged user
* @apiSuccess {String} data.username The username of the flagged user
* @apiSuccess {Object} data.profile The flagged user's profile information
* @apiSuccess {String} data.profile.blurb Text of the flagged user's profile bio
* @apiSuccess {Object} data.profile.flags Data about flags the profile has received.
* Restricted to the reporting user's own flag
* unless the reporting user is a moderator.
* Each key is a UUID, and fields are comment,
* source, and timestamp.
* @apiSuccess {String} data.profile.imageUrl URL of the flagged user's profile image
* @apiSuccess {String} data.profile.name The flagged user's display name
*
* @apiError (400) {BadRequest} AlreadyFlagged A profile cannot be flagged
* more than once by the same user.
* @apiError (400) {BadRequest} MemberIdRequired The `memberId` param is required
* and must be a valid `UUID`.
* @apiError (404) {NotFound} UserWithIdNotFound The `memberId` param did not
* belong to an existing user.
*/
api.flagUser = {
method: 'POST',
url: '/members/:memberId/flag',
middlewares: [authWithHeaders()],
async handler (req, res) {
const chatReporter = chatReporterFactory('User', req, res);
const flaggedUser = await chatReporter.flag();
res.respond(200, flaggedUser);
},
};
/**
* @api {post} /api/v3/members/:memberId/clear-flags Delete flags from a user
* @apiDescription Removes any abuse reports flagged on a user profile.
* @apiPermission Admin
* @apiName ClearUserFlags
* @apiGroup Members
*
* @apiParam (Path) {UUID} memberId The unique ID of the flagged user to reset
*
* @apiSuccess {Object} data An empty object
*
* @apiError (400) {BadRequest} MemberIdRequired The `memberId` param is required
* and must be a valid `UUID`.
* @apiError (400) {BadRequest} MustBeAdmin Must be a moderator to use this route
* @apiError (404) {NotFound} UserWithIdNotFound The `memberId` param did not
* belong to an existing user.
*/
api.clearUserFlags = {
method: 'POST',
url: '/members/:memberId/clear-flags',
middlewares: [authWithHeaders()],
async handler (req, res) {
const { user } = res.locals;
const { memberId } = req.params;
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
if (!user.hasPermission('moderator')) {
throw new BadRequest('Only a moderator may clear reports from a profile.');
}
const flaggedUser = await User.findOne(
{ _id: memberId },
{ profile: 1 },
).exec();
if (!flaggedUser) {
throw new NotFound(res.t('userWithIDNotFound', { userId: memberId }));
}
flaggedUser.profile.flags = {};
await flaggedUser.save();
res.respond(200, {});
},
};
export default api; export default api;

View File

@@ -1,11 +1,14 @@
import GroupChatReporter from './groupChatReporter'; import GroupChatReporter from './groupChatReporter';
import InboxChatReporter from './inboxChatReporter'; import InboxChatReporter from './inboxChatReporter';
import ProfileReporter from './profileReporter';
export function chatReporterFactory (type, req, res) { // eslint-disable-line import/prefer-default-export, max-len export function chatReporterFactory (type, req, res) { // eslint-disable-line import/prefer-default-export, max-len
if (type === 'Group') { if (type === 'Group') {
return new GroupChatReporter(req, res); return new GroupChatReporter(req, res);
} if (type === 'Inbox') { } if (type === 'Inbox') {
return new InboxChatReporter(req, res); return new InboxChatReporter(req, res);
} if (type === 'Profile' || type === 'User') {
return new ProfileReporter(req, res);
} }
throw new Error('Invalid chat reporter type.'); throw new Error('Invalid chat reporter type.');

View File

@@ -6,17 +6,12 @@ import {
BadRequest, BadRequest,
NotFound, NotFound,
} from '../errors'; } from '../errors';
import { sendTxn } from '../email';
import * as slack from '../slack'; import * as slack from '../slack';
import { model as Group } from '../../models/group'; import { model as Group } from '../../models/group';
import { chatModel as Chat } from '../../models/message'; import { chatModel as Chat } from '../../models/message';
import apiError from '../apiError'; import apiError from '../apiError';
const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'); const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL');
const FLAG_REPORT_EMAILS = nconf
.get('FLAG_REPORT_EMAIL')
.split(',')
.map(email => ({ email, canSend: true }));
const USER_AGE_FOR_FLAGGING = 3; // accounts less than this many days old don't increment flagCount const USER_AGE_FOR_FLAGGING = 3; // accounts less than this many days old don't increment flagCount
export default class GroupChatReporter extends ChatReporter { export default class GroupChatReporter extends ChatReporter {
@@ -51,13 +46,6 @@ export default class GroupChatReporter extends ChatReporter {
} }
async notify (group, message, userComment, automatedComment = '') { async notify (group, message, userComment, automatedComment = '') {
let emailVariables = await this.getMessageVariables(group, message);
emailVariables = emailVariables.concat([
{ name: 'REPORTER_COMMENT', content: userComment || '' },
]);
sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods-with-comments', emailVariables);
slack.sendFlagNotification({ slack.sendFlagNotification({
authorEmail: this.authorEmail, authorEmail: this.authorEmail,
flagger: this.user, flagger: this.user,

View File

@@ -1,21 +1,16 @@
import nconf from 'nconf';
import { model as User } from '../../models/user'; import { model as User } from '../../models/user';
import ChatReporter from './chatReporter'; import ChatReporter from './chatReporter';
import { import {
BadRequest, BadRequest,
} from '../errors'; } from '../errors';
import { getUserInfo, sendTxn } from '../email'; import { getUserInfo } from '../email';
import * as slack from '../slack'; import * as slack from '../slack';
import apiError from '../apiError'; import apiError from '../apiError';
import * as inboxLib from '../inbox'; import * as inboxLib from '../inbox';
import { getAuthorEmailFromMessage } from '../chat'; import { getAuthorEmailFromMessage } from '../chat';
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL')
.split(',')
.map(email => ({ email, canSend: true }));
export default class InboxChatReporter extends ChatReporter { export default class InboxChatReporter extends ChatReporter {
constructor (req, res) { constructor (req, res) {
super(req, res); super(req, res);
@@ -49,13 +44,6 @@ export default class InboxChatReporter extends ChatReporter {
_id: 'N/A', _id: 'N/A',
}; };
let emailVariables = await this.getMessageVariables(group, message);
emailVariables = emailVariables.concat([
{ name: 'REPORTER_COMMENT', content: userComment || '' },
]);
sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods-with-comments', emailVariables);
slack.sendInboxFlagNotification({ slack.sendInboxFlagNotification({
messageUserEmail: this.messageUserEmail, messageUserEmail: this.messageUserEmail,
flagger: this.user, flagger: this.user,

View File

@@ -0,0 +1,81 @@
import { model as User } from '../../models/user';
import * as slack from '../slack';
import ChatReporter from './chatReporter';
import {
BadRequest,
NotFound,
} from '../errors';
export default class ProfileReporter extends ChatReporter {
constructor (req, res) {
super(req, res);
this.user = res.locals.user;
}
async validate () {
this.req.checkParams('memberId', this.res.t('memberIdRequired')).notEmpty().isUUID();
const validationErrors = this.req.validationErrors();
if (validationErrors) throw validationErrors;
const flaggedUser = await User.findOne(
{ _id: this.req.params.memberId },
{ auth: 1, profile: 1 },
).exec();
if (!flaggedUser) {
throw new NotFound(this.res.t('userWithIDNotFound', { userId: this.req.params.memberId }));
}
if (flaggedUser.profile.flags && flaggedUser.profile.flags[this.user._id]
&& !this.user.hasPermission('moderator')) {
throw new BadRequest('A profile can not be flagged more than once by the same user.');
}
const { comment, source } = this.req.body;
return { flaggedUser, comment, source };
}
async flagProfile (flaggedUser, comment, source) {
const timestamp = new Date();
// Log user ids that have flagged the account
if (!flaggedUser.profile.flags) {
flaggedUser.profile.flags = {};
}
flaggedUser.profile.flags[this.user._id] = {
comment,
source,
timestamp,
};
flaggedUser.markModified('profile.flags');
await flaggedUser.save();
return timestamp;
}
notify (flaggedUser, comment, source) {
slack.sendProfileFlagNotification({
reporter: this.user,
flaggedUser,
userComment: comment,
source,
});
}
async flag () {
const { flaggedUser, comment, source } = await this.validate();
const timestamp = await this.flagProfile(flaggedUser, comment, source);
this.notify(flaggedUser, comment, source);
if (!this.user.hasPermission('moderator')) {
flaggedUser.profile.flags = {};
flaggedUser.profile.flags[this.user._id] = {
comment,
source,
timestamp,
};
}
return flaggedUser;
}
}

View File

@@ -176,6 +176,43 @@ function sendInboxFlagNotification ({
.catch(err => logger.error(err, 'Error while sending flag data to Slack.')); .catch(err => logger.error(err, 'Error while sending flag data to Slack.'));
} }
function sendProfileFlagNotification ({
reporter,
flaggedUser,
userComment,
source,
}) {
const title = 'User Profile Report';
const titleLink = `${BASE_URL}/profile/${flaggedUser._id}`;
let text = `@${reporter.auth.local.username} (${reporter._id}; language: ${reporter.preferences.language}) flagged @${flaggedUser.auth.local.username}'s profile from ${source}`;
if (userComment) {
text += ` and commented: ${userComment}`;
}
let profileData = `Display Name: ${flaggedUser.profile.name}`;
if (flaggedUser.profile.imageUrl) {
profileData += `\n\nImage URL: ${flaggedUser.profile.imageUrl}`;
}
if (flaggedUser.profile.blurb) {
profileData += `\n\nAbout: ${flaggedUser.profile.blurb}`;
}
flagSlack
.send({
text,
attachments: [{
fallback: 'Flag Profile',
color: 'danger',
title,
title_link: titleLink,
text: profileData,
mrkdwn_in: [
'text',
],
}],
})
.catch(err => logger.error(err, 'Error while sending flag data to Slack.'));
}
function sendSubscriptionNotification ({ function sendSubscriptionNotification ({
buyer, buyer,
recipient, recipient,
@@ -302,6 +339,7 @@ function sendSlurNotification ({
export { export {
sendFlagNotification, sendFlagNotification,
sendInboxFlagNotification, sendInboxFlagNotification,
sendProfileFlagNotification,
sendSubscriptionNotification, sendSubscriptionNotification,
sendShadowMutedPostNotification, sendShadowMutedPostNotification,
sendSlurNotification, sendSlurNotification,

View File

@@ -24,7 +24,7 @@ schema.plugin(baseModel, {
// noSet is not used as updating uses a whitelist and creating only accepts // noSet is not used as updating uses a whitelist and creating only accepts
// specific params (password, email, username, ...) // specific params (password, email, username, ...)
noSet: [], noSet: [],
private: ['auth.local.hashed_password', 'auth.local.passwordHashMethod', 'auth.local.salt', '_cronSignature', '_ABtests', 'secret'], private: ['auth.local.hashed_password', 'auth.local.passwordHashMethod', 'auth.local.salt', '_cronSignature', '_ABtests', 'secret', 'profile.flags'],
toJSONTransform: function userToJSON (plainObj, originalDoc) { toJSONTransform: function userToJSON (plainObj, originalDoc) {
plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs

View File

@@ -548,6 +548,12 @@ schema.methods.getSecretData = function getSecretData () {
return user.secret; return user.secret;
}; };
schema.methods.getFlagData = function getFlagData () {
const user = this;
return user.profile.flags;
};
schema.methods.updateBalance = async function updateBalance (amount, schema.methods.updateBalance = async function updateBalance (amount,
transactionType, transactionType,
reference, reference,

View File

@@ -625,6 +625,7 @@ export default new Schema({
required: true, required: true,
trim: true, trim: true,
}, },
flags: { $type: Schema.Types.Mixed },
}, },
stats: { stats: {
hp: { $type: Number, default: shared.maxHealth }, hp: { $type: Number, default: shared.maxHealth },