Merge branch 'develop' into issue-11615

This commit is contained in:
alexthomson1
2020-01-12 00:20:13 -07:00
762 changed files with 47411 additions and 37824 deletions
+14 -10
View File
@@ -135,6 +135,15 @@
cursor: crosshair;
}
.closepadding {
margin: 11px 24px;
display: inline-block;
position: relative;
right: 0;
top: 0;
cursor: pointer;
}
.container-fluid {
flex: 1 0 auto;
}
@@ -165,15 +174,6 @@
margin: auto;
}
.closepadding {
margin: 11px 24px;
display: inline-block;
position: relative;
right: 0;
top: 0;
cursor: pointer;
}
@media only screen and (max-width: 768px) {
.content {
font-size: 12px;
@@ -239,7 +239,11 @@ import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
import subCanceledModal from '@/components/payments/canceledModal';
import spellsMixin from '@/mixins/spells';
import { CONSTANTS, getLocalSetting, removeLocalSetting } from '@/libs/userlocalManager';
import {
CONSTANTS,
getLocalSetting,
removeLocalSetting,
} from '@/libs/userlocalManager';
import svgClose from '@/assets/svg/close.svg';
import bannedAccountModal from '@/components/bannedAccountModal';
@@ -1,48 +1,78 @@
.promo_armoire_backgrounds_201911 {
.promo_armoire_backgrounds_202001 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -451px;
background-position: -424px -175px;
width: 423px;
height: 147px;
}
.promo_costume_achievement {
.promo_g1g1_2019 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -747px;
width: 144px;
height: 156px;
background-position: -403px -471px;
width: 357px;
height: 144px;
}
.promo_delightful_dinos {
.promo_mystery_202001 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -451px;
width: 423px;
background-position: -241px -619px;
width: 279px;
height: 147px;
}
.promo_ember_thunderstorm_potions {
.promo_snowballs {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -599px;
width: 423px;
height: 147px;
}
.promo_mystery_201910 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -599px;
width: 282px;
height: 147px;
background-position: 0px 0px;
width: 420px;
height: 174px;
}
.promo_take_this {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -451px -365px;
background-position: -761px -471px;
width: 96px;
height: 69px;
}
.scene_habitica_map {
.promo_winter_potions_2020 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px 0px;
width: 450px;
height: 450px;
background-position: 0px -323px;
width: 423px;
height: 147px;
}
.scene_seaserpent {
.promo_winter_quests_bundle {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -451px 0px;
width: 476px;
height: 364px;
background-position: 0px -175px;
width: 423px;
height: 147px;
}
.promo_winter_wonderland_2019 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -471px;
width: 402px;
height: 147px;
}
.promo_winter_wonderland_2020 {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -421px 0px;
width: 468px;
height: 147px;
}
.promo_wintery_hair {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -521px -619px;
width: 152px;
height: 75px;
}
.promo_wintery_skins {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -424px -323px;
width: 420px;
height: 147px;
}
.customize-option.promo_wintery_skins {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: -449px -338px;
width: 60px;
height: 60px;
}
.scene_list {
background-image: url('~@/assets/images/sprites/spritesmith-largeSprites-0.png');
background-position: 0px -619px;
width: 240px;
height: 195px;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 481 KiB

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 134 KiB

+15 -7
View File
@@ -20,7 +20,7 @@
@include btn-focus-hover-shadow();
}
&:hover:not(.btn-flat):not(.disabled) {
&:hover:not(.btn-flat):not(.disabled):not(:disabled) {
@include btn-focus-hover-shadow();
border-color: transparent;
}
@@ -49,7 +49,7 @@
background: $purple-200;
}
&:hover:not(:disabled), &:active, &.active, &:focus {
&:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) {
background: #5d3b9c !important;
color: $white;
}
@@ -59,7 +59,7 @@
color: $gray-50;
background: $white !important;
&:hover:not(:disabled):not(.disabled), &:active, &.active, &:focus {
&:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled), &:focus:not(:disabled):not(.disabled) {
color: $purple-200 !important;
}
@@ -80,7 +80,7 @@
background: $green-100;
}
&:hover:not(:disabled), &:active, &.active {
&:hover:not(:disabled):not(.disabled), &:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $green-50;
}
}
@@ -96,8 +96,12 @@
background: $blue-50;
}
&:hover:not(:disabled), &:active, &.active {
background: $blue-100;
&:hover:not(:disabled):not(.disabled) {
background-color: $blue-100;
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $blue-50;
}
}
@@ -108,7 +112,11 @@
background: $red-50;
}
&:hover:not(:disabled), &:active, &.active {
&:hover:not(:disabled):not(.disabled) {
background: $red-100;
}
&:active:not(:disabled):not(.disabled), &.active:not(:disabled):not(.disabled) {
background: $red-100;
}
}
+17
View File
@@ -65,6 +65,7 @@ input, textarea, input.form-control, textarea.form-control {
padding-right: 40px;
background-image: url(~@/assets/svg/for-css/alert.svg);
background-size: 16px 16px;
border-color: $red-100 !important;
}
}
@@ -276,3 +277,19 @@ $bg-disabled-control: #34303a;
.toggle-switch-container.no-margin {
margin-top: 0 !important;
}
// Disable default style Firefox for invalid elements.
// Selectors taken from view-source:resource://gre-resources/forms.css on Firefox
:not(output):-moz-ui-invalid {
box-shadow: none;
}
:not(output):-moz-ui-invalid:-moz-focusring {
box-shadow: none;
}
output:-moz-ui-invalid {
color: inherit;
}
+15
View File
@@ -50,3 +50,18 @@
width: 20px;
}
}
.close-icon {
position: absolute;
top: 24px;
right: 24px;
cursor: pointer;
& svg path {
stroke: $gray-200;
}
&:hover svg path {
stroke: $gray-100;
}
}
+4 -4
View File
@@ -17,7 +17,7 @@
.tier3 {
color: #d70e14;
:hover, :active, :focus {
&:hover, &:active, &:focus {
color: #d70e14;
}
}
@@ -25,7 +25,7 @@
.tier4 {
color: #c24d00;
&:hover, &:active, :focus {
&:hover, &:active, &:focus {
color: #c24d00;
}
}
@@ -33,7 +33,7 @@
.tier5 {
color: #9e650f;
:hover, :active, :focus {
&:hover, &:active, &:focus {
color: #9e650f;
}
}
@@ -41,7 +41,7 @@
.tier6 {
color: #2b8363;
:hover, :active, &:focus {
&:hover, &:active, &:focus {
color: #2b8363;
}
}
@@ -2,8 +2,8 @@
// possible values are: normal, fall, habitoween, thanksgiving, winter, nye, birthday, valentines, spring, summer
// more to be added on future seasons
$npc_market_flavor: 'normal';
$npc_quests_flavor: 'normal';
$npc_seasonal_flavor: 'normal';
$npc_timetravelers_flavor: 'normal';
$npc_tavern_flavor: 'normal';
$npc_market_flavor: 'nye';
$npc_quests_flavor: 'nye';
$npc_seasonal_flavor: 'nye';
$npc_timetravelers_flavor: 'winter';
$npc_tavern_flavor: 'nye';
@@ -0,0 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="62" viewBox="0 0 52 62">
<g fill="none" fill-rule="evenodd">
<path stroke="#FFA624" stroke-width="2" d="M18.286 6.252c.186-1.717-.286-3.6-1.658-4.473-1.373-.872-2.831-.21-2.994 1.027-.163 1.237.714 1.988 3.413 4.686.766.766 1.054.477 1.24-1.24z"/>
<path stroke="#FFBE5D" stroke-width="2" d="M21.882 8.328c1.395-1.018 3.262-1.552 4.704-.8 1.441.753 1.597 2.347.607 3.107-.99.76-2.079.375-5.764-.613-1.047-.28-.941-.675.453-1.694z"/>
<path fill="#EE9109" d="M20.698 6.227c-2.754-1.59-2.924 2.514-1.82 3.152 1.106.637 4.574-1.562 1.82-3.152z"/>
<path fill="#F8F9F9" d="M19.167 8.88l10.18 5.878-2.939 5.09-10.18-5.878z"/>
<path fill="#DDF3F3" d="M8.986 3.003l10.18 5.877-2.938 5.09-10.18-5.877z"/>
<path fill="#FFBE5D" d="M15.773 6.921l6.787 3.918-2.939 5.09-6.787-3.918z"/>
<path fill="#FFA624" d="M15.773 6.921l3.394 1.96-2.94 5.09-3.393-1.96z"/>
<path fill="#FFA624" d="M19.167 8.88l3.393 1.96-.98 1.696-3.393-1.96z"/>
<path fill="#EE9109" d="M15.773 6.921l3.394 1.96-.98 1.696-3.393-1.96z"/>
<path fill="#F8F9F9" d="M16.228 13.97l8.483 4.898-6.857 11.877-8.483-4.898z"/>
<path fill="#DDF3F3" d="M7.744 9.072l8.484 4.898L9.37 25.847.887 20.95z"/>
<path fill="#FFA624" d="M14.531 12.99l1.697.98L9.37 25.847l-1.697-.98z"/>
<path fill="#FFBE5D" d="M16.228 13.97l1.696.98-6.857 11.876-1.696-.98z"/>
<path fill="#EE9109" d="M8.654 23.171l1.696.98-.98 1.696-1.696-.98zM14.531 12.99l1.697.98-.98 1.697-1.697-.98z"/>
<path fill="#C1E9E9" d="M7.744 9.072l6.787 3.919-.98 1.696-6.786-3.918z"/>
<path fill="#C1E9E9" d="M7.027 6.396l6.787 3.918-.98 1.697-6.786-3.918zM1.867 19.253l6.787 3.918-.98 1.697-6.787-3.919z"/>
<path fill="#DDF3F3" d="M17.924 14.95l6.787 3.918-.98 1.697-6.786-3.918z"/>
<path fill="#DDF3F3" d="M20.6 14.233l6.788 3.918-.98 1.697-6.787-3.918zM12.047 25.13l6.787 3.919-.98 1.696-6.787-3.918z"/>
<path fill="#FFA624" d="M10.35 24.15l1.697.98-.98 1.697-1.696-.98zM16.228 13.97l1.696.98-.98 1.697-1.696-.98z"/>
<path fill="#8EEDF6" d="M32.753 23.621l-.564 2.362 2.228-.966 2.361.564-.965-2.228.564-2.361-2.228.965-2.361-.564zM17.733 45.172l-.564 2.362 2.228-.966 2.361.564-.966-2.228.565-2.361-2.228.965-2.362-.564zM42.733 15.02l.769 2.958 1.905-2.39 2.96-.769-2.391-1.905-.769-2.96-1.906 2.391-2.958.769z"/>
<g>
<path fill="#F8F9F9" d="M37.688 45.676l9.462-2.536 3.55 13.247-9.463 2.536z"/>
<path fill="#DDF3F3" d="M28.225 48.211l9.463-2.535 3.55 13.247-9.463 2.535z"/>
<path fill="#FFA624" d="M35.795 46.183l1.893-.507 3.55 13.247-1.893.507z"/>
<path fill="#FFBE5D" d="M37.688 45.676l1.892-.507 3.55 13.247-1.893.507z"/>
<path fill="#EE9109" d="M38.838 57.537l1.892-.507.507 1.893-1.892.507zM35.795 46.183l1.893-.507.507 1.892-1.893.507z"/>
<path fill="#C1E9E9" d="M28.225 48.211l7.57-2.028.507 1.892-7.57 2.028zM31.268 59.566l7.57-2.029.507 1.893-7.57 2.028z"/>
<path fill="#DDF3F3" d="M39.58 45.169l7.57-2.029.507 1.893-7.57 2.028zM42.622 56.523l7.57-2.028.507 1.892-7.57 2.029z"/>
<path fill="#FFA624" d="M40.73 57.03l1.892-.507.507 1.893-1.892.507zM37.688 45.676l1.892-.507.507 1.892-1.892.507z"/>
</g>
<g>
<path stroke="#FFA624" stroke-width="2" d="M37.861 35.357c-.471-1.661-1.614-3.232-3.214-3.526-1.599-.294-2.703.866-2.39 2.074.311 1.208 1.407 1.575 4.92 3.066.996.423 1.156.047.684-1.614z"/>
<path stroke="#FFBE5D" stroke-width="2" d="M41.973 35.935c.911-1.467 2.443-2.662 4.061-2.504 1.619.158 2.36 1.578 1.727 2.653-.633 1.075-1.787 1.126-5.574 1.591-1.075.132-1.125-.273-.214-1.74z"/>
<path fill="#EE9109" d="M40.088 34.43c-3.15-.442-1.77 3.426-.506 3.604 1.263.177 3.655-3.161.506-3.604z"/>
<path fill="#F8F9F9" d="M39.662 37.464l11.64 1.636-.818 5.82-11.64-1.636z"/>
<path fill="#DDF3F3" d="M28.02 35.828l11.642 1.636-.818 5.82-11.641-1.636z"/>
<path fill="#FFBE5D" d="M35.781 36.919l7.76 1.09-.817 5.82-7.76-1.09z"/>
<path fill="#FFA624" d="M35.781 36.919l3.88.545-.817 5.82-3.88-.545zM39.662 37.464l3.88.545-.273 1.94-3.88-.545z"/>
<path fill="#EE9109" d="M35.781 36.919l3.88.545-.272 1.94-3.88-.545z"/>
<path fill="#C1E9E9" d="M27.476 39.708l7.76 1.09-.273 1.941-7.76-1.09z"/>
<path fill="#DDF3F3" d="M42.997 41.89l7.76 1.09-.273 1.94-7.76-1.09z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="48" viewBox="0 0 150 48">
<defs>
<filter id="a" width="225%" height="225%" x="-62.5%" y="-62.5%" filterUnits="objectBoundingBox">
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="4"/>
<feColorMatrix in="shadowBlurOuter1" result="shadowMatrixOuter1" values="0 0 0 0 0.101960784 0 0 0 0 0.0941176471 0 0 0 0 0.11372549 0 0 0 0.32 0"/>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g fill="none" fill-rule="evenodd">
<g fill="#FFA624">
<path d="M125.45 35.414l3.346-2.967-4.382-.896-2.967-3.347-.896 4.382-3.347 2.967 4.382.896 2.967 3.347z" opacity=".75"/>
<path d="M136.43 30.407l3.325-.442-2.348-2.395-.442-3.325-2.395 2.348-3.325.442 2.348 2.395.442 3.325z" opacity=".5"/>
<path d="M146.901 35.41l2.067.854-.557-2.165.853-2.067-2.165.557-2.067-.853.557 2.165-.853 2.067z" opacity=".25"/>
<path d="M138.353 39.874l1.488-.093-.967-1.134-.093-1.488-1.134.967-1.488.093.967 1.134.093 1.488z" opacity=".35"/>
</g>
<g fill="#FFA624">
<path d="M24.55 35.414l-3.346-2.967 4.382-.896 2.967-3.347.896 4.382 3.347 2.967-4.382.896-2.967 3.347z" opacity=".75"/>
<path d="M13.57 30.407l-3.325-.442 2.348-2.395.442-3.325 2.395 2.348 3.325.442-2.348 2.395-.442 3.325z" opacity=".5"/>
<path d="M3.099 35.41l-2.067.854.557-2.165-.853-2.067 2.165.557 2.067-.853-.557 2.165.853 2.067z" opacity=".25"/>
<path d="M11.647 39.874l-1.488-.093.967-1.134.093-1.488 1.134.967 1.488.093-.967 1.134-.093 1.488z" opacity=".35"/>
</g>
<g opacity=".5" transform="translate(83 22)">
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
</g>
<g opacity=".5" transform="translate(43 22)">
<circle cx="12" cy="12" r="12" fill="#FFA623"/>
<path fill="#FFF" d="M6.3 17.7c-3.1-3.1-3.1-8.2 0-11.3 3.1-3.1 8.2-3.1 11.3 0" opacity=".5"/>
<path fill="#FFF" d="M17.7 6.3c3.1 3.1 3.1 8.2 0 11.3-3.1 3.1-8.2 3.1-11.3 0" opacity=".25"/>
<path fill="#BF7D1A" d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2zm0 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" opacity=".5"/>
<path fill="#BF7D1A" d="M13 9v2h-2V9H9v6h2v-2h2v2h2V9z" opacity=".75"/>
</g>
<g transform="translate(59 6)">
<circle cx="16" cy="16" r="16" fill="#FFA623"/>
<path fill="#FFF" d="M8.4 23.6c-4.133-4.133-4.133-10.933 0-15.067 4.133-4.133 10.933-4.133 15.067 0" opacity=".5"/>
<path fill="#FFF" d="M23.6 8.4c4.133 4.133 4.133 10.933 0 15.067-4.133 4.133-10.933 4.133-15.067 0" opacity=".25"/>
<path fill="#BF7D1A" d="M16 2.667c-7.333 0-13.333 6-13.333 13.333s6 13.333 13.333 13.333 13.333-6 13.333-13.333S23.333 2.667 16 2.667zm0 24c-5.867 0-10.667-4.8-10.667-10.667S10.133 5.333 16 5.333 26.667 10.133 26.667 16 21.867 26.667 16 26.667z" opacity=".5"/>
<path fill="#BF7D1A" d="M17.333 12v2.667h-2.666V12H12v8h2.667v-2.667h2.666V20H20v-8z" opacity=".75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="11" viewBox="0 0 12 11">
<path fill="#FFF" fill-rule="evenodd" d="M6 8.76l-3.527 2.094.902-4.001L.294 4.146l4.084-.379L6 0l1.622 3.767 4.084.379-3.081 2.707.902 4.001z"/>
</svg>

After

Width:  |  Height:  |  Size: 241 B

@@ -1,104 +1,16 @@
<template>
<div
class="modal-footer"
style="margin-top:0"
ng-init="loadWidgets()"
>
<div class="container-fluid share-buttons">
<div class="row">
<div class="col-12 text-center">
<a
class="twitter-share-button share-button"
:href="twitterLink"
target="_blank"
>
<div
class="social-icon twitter svg-icon"
v-html="icons.twitter"
></div>
{{ $t('tweet') }}
</a>
<a
class="fb-share-button share-button"
:href="facebookLink"
target="_blank"
>
<div
class="social-icon facebook svg-icon"
v-html="icons.facebook"
></div>
{{ $t('share') }}
</a>
<!-- @TODO: Still want this?
.col-4a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')-->
</div>
</div>
</div>
<!-- TODO disabled for now, see https://github.com/HabitRPG/habitica/issues/11283 -->
</div>
</template>
<style scoped>
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
<style lang="scss" scoped>
.modal-footer {
border-top: none;
}
</style>
<script>
// @TODO:
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default {
data () {
const tweet = this.$t('achievementShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
tweet,
achievementLink: `${BASE_URL}`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}`,
};
},
};
</script>
@@ -225,30 +225,30 @@ export default {
classGear (heroClass) {
if (heroClass === 'rogue') {
return {
armor: 'armor_rogue_5',
head: 'head_rogue_5',
shield: 'shield_rogue_6',
weapon: 'weapon_rogue_6',
armor: 'armor_special_winter2020Rogue',
head: 'head_special_winter2020Rogue',
shield: 'shield_special_winter2020Rogue',
weapon: 'weapon_special_winter2020Rogue',
};
} if (heroClass === 'wizard') {
return {
armor: 'armor_wizard_5',
head: 'head_wizard_5',
weapon: 'weapon_wizard_6',
armor: 'armor_special_winter2020Mage',
head: 'head_special_winter2020Mage',
weapon: 'weapon_special_winter2020Mage',
};
} if (heroClass === 'healer') {
return {
armor: 'armor_healer_5',
head: 'head_healer_5',
shield: 'shield_healer_5',
weapon: 'weapon_healer_6',
armor: 'armor_special_winter2020Healer',
head: 'head_special_winter2020Healer',
shield: 'shield_special_winter2020Healer',
weapon: 'weapon_special_winter2020Healer',
};
}
return {
armor: 'armor_warrior_5',
head: 'head_warrior_5',
shield: 'shield_warrior_5',
weapon: 'weapon_warrior_6',
armor: 'armor_special_winter2020Warrior',
head: 'head_special_winter2020Warrior',
shield: 'shield_special_winter2020Warrior',
weapon: 'weapon_special_winter2020Warrior',
};
},
selectionBox (selectedClass, heroClass) {
@@ -32,7 +32,7 @@
:avatar-only="true"
:with-background="true"
/>
<!-- @TOOD: Sleep +generatedAvatar({sleep:true})-->
<!-- @TODO: Sleep +generatedAvatar({sleep:true})-->
<span class="knockout"></span>
</div>
</div>
@@ -3,14 +3,32 @@
id="generic-achievement"
:title="data.message"
size="md"
:hide-footer="true"
:hide-header="true"
>
<div class="modal-body">
<div class="col-12">
<achievement-avatar class="avatar" />
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<div class="content">
<div
v-once
class="dialog-header title"
>
{{ $t('earnedAchievement') }}
</div>
<div class="col-6 offset-3 text-center">
<p v-html="data.modalText"></p>
<div class="inner-content">
<div class="achievement-background d-flex align-items-center">
<div
class="icon"
:class="achievementClass"
></div>
</div>
<h4
class="title"
v-html="$t(achievement.titleKey)"
>
</h4>
<button
class="btn btn-primary"
@click="close()"
@@ -19,33 +37,86 @@
</button>
</div>
</div>
<achievement-footer />
<div
slot="modal-footer"
class="clearfix"
></div>
</b-modal>
</template>
<style scoped>
.avatar {
width: 140px;
<style lang="scss">
@import '~@/assets/scss/modal.scss';
#generic-achievement {
@include centeredModal();
.modal-dialog {
width: 330px;
}
.modal-footer {
padding-top: 0px;
}
}
</style>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.content {
text-align: center;
}
.inner-content {
margin: 24px auto auto;
display: flex;
flex-direction: column;
align-items: center;
}
.achievement-background {
width: 112px;
height: 112px;
border-radius: 4px;
background-color: $gray-700;
}
.dialog-header {
margin-top: 16px !important;
color: $purple-200 !important;
}
.title {
margin-bottom: 24px !important;
}
.icon {
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import { mapState } from '@/libs/store';
import achievements from '@/../../common/script/content/achievements';
import svgClose from '@/assets/svg/close.svg';
export default {
components: {
achievementFooter,
achievementAvatar,
},
props: ['data'],
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
computed: {
...mapState({ user: 'user.data' }),
achievement () {
return achievements[this.data.achievement];
},
achievementClass () {
return `${this.achievement.icon}2x`;
},
},
methods: {
close () {
@@ -27,36 +27,6 @@
label(style='display:inline-block') {{ $t('dontShowAgain') }}
-->
</div>
<div class="container-fluid share-buttons">
<div class="row">
<div class="col-12 text-center">
<a
class="twitter-share-button share-button"
:href="twitterLink"
target="_blank"
>
<div
class="social-icon twitter svg-icon"
v-html="icons.twitter"
></div>
{{ $t('tweet') }}
</a>
<a
class="fb-share-button share-button"
:href="facebookLink"
target="_blank"
>
<div
class="social-icon facebook svg-icon"
v-html="icons.facebook"
></div>
{{ $t('share') }}
</a>
</div>
<!-- @TODO: Still want this? .col-4a.tumblr
-share-button(:data-href='socialLevelLink', data-notes='none')-->
</div>
</div>
</b-modal>
</template>
@@ -72,7 +42,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
.modal-body {
padding-top: 1em;
padding-bottom: 0;
}
.modal-footer {
@@ -99,45 +68,6 @@ label(style='display:inline-block') {{ $t('dontShowAgain') }}
margin-top: 1em;
min-height: 0px;
}
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
}
</style>
@@ -152,10 +82,6 @@ import Avatar from '../avatar';
import { mapState } from '@/libs/store';
import { MAX_HEALTH as maxHealth } from '@/../../common/script/constants';
import styleHelper from '@/mixins/styleHelper';
import twitter from '@/assets/svg/twitter.svg';
import facebook from '@/assets/svg/facebook.svg';
const BASE_URL = 'https://habitica.com';
export default {
components: {
@@ -163,18 +89,9 @@ export default {
},
mixins: [styleHelper],
data () {
const tweet = this.$t('levelUpShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
statsAllocationBoxIsOpen: true,
maxHealth,
tweet,
socialLevelLink: `${BASE_URL}/social/level-up`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/level-up&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/level-up`,
};
},
computed: {
@@ -183,16 +100,10 @@ export default {
return this.$store.getters['members:hasClass'](this.user) && !this.user.preferences.automaticAllocation;
},
},
mounted () {
this.loadWidgets();
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'level-up');
},
loadWidgets () {
// @TODO:
},
changeLevelupSuppress () {
// @TODO: dispatch set({"preferences.suppressModals.levelUp":
// user.preferences.suppressModals.levelUp?true: false})
@@ -0,0 +1,106 @@
<template>
<b-modal
id="onboarding-complete"
size="sm"
:hide-footer="true"
:hide-header="true"
>
<div class="content text-center">
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<h2>{{ $t('congratulations') }}</h2>
<img
class="onboarding-complete-banner d-block"
src="~@/assets/images/onboarding-complete-banner@2x.png"
>
<p
class="onboarding-complete-text"
v-html="$t('onboardingCompleteDesc')"
></p>
<button
class="btn btn-primary"
@click="closeWithAction()"
>
{{ $t('viewAchievements') }}
</button>
</div>
</b-modal>
</template>
<style lang="scss">
#onboarding-complete {
.modal-content {
min-width: 330px;
}
.modal-body {
padding-top: 1em;
padding-bottom: 0;
}
.modal-footer {
margin-top: 0;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
h2 {
color: $purple-200;
margin-bottom: 24px;
margin-top: 2px;
}
.content {
padding: 0 8px;
margin-top: 18px;
}
.onboarding-complete-banner {
width: 282px;
margin: 0 auto;
margin-bottom: 24px;
}
.onboarding-complete-text {
margin-bottom: 24px;
}
.onboarding-complete-text ::v-deep .gold-amount {
color: $yellow-5;
}
button {
margin-bottom: 24px;
}
</style>
<script>
import svgClose from '@/assets/svg/close.svg';
export default {
data () {
return {
icons: Object.freeze({
close: svgClose,
}),
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'onboarding-complete');
},
closeWithAction () {
this.close();
setTimeout(() => {
this.$router.push({ name: 'achievements' });
}, 200);
},
},
};
</script>
@@ -30,10 +30,10 @@
</button>
<div class="checkbox">
<input
id="user-preferences-suppressModals-streak"
v-model="user.preferences.suppressModals.streak"
type="checkbox"
@change="suppressModals"
id="user-preferences-suppressModals-streak"
>
<label for="user-preferences-suppressModals-streak">{{ $t('dontShowAgain') }}</label>
</div>
@@ -71,8 +71,8 @@ export default {
this.$root.$emit('bv::hide::modal', 'streak');
},
suppressModals () {
const surpress = !!this.user.preferences.suppressModals.streak;
this.$store.dispatch('user:set', { 'preferences.suppressModals.streak': surpress });
const suppress = !!this.user.preferences.suppressModals.streak;
this.$store.dispatch('user:set', { 'preferences.suppressModals.streak': suppress });
},
},
};
@@ -46,6 +46,13 @@
:placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
>
<div
v-for="issue in usernameIssues"
:key="issue"
class="input-error"
>
{{ issue }}
</div>
</div>
<div
v-if="!registering"
@@ -97,7 +104,17 @@
class="form-control"
type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-valid': registering ? passwordValid : false,
'input-invalid': registering ? passwordInvalid: false,
}"
>
<div
v-if="passwordInvalid && registering"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div>
<div
v-if="registering"
@@ -115,6 +132,12 @@
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
@@ -183,8 +206,11 @@
text-align: center;
}
.input-valid {
color: #fff;
.input-error {
margin-top: 0.25em;
font-weight: normal;
font-size: 90%;
width: 100%;
}
}
</style>
@@ -194,7 +220,7 @@ import hello from 'hellojs';
import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail';
import { setUpAxios } from '@/libs/auth';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
import googleIcon from '@/assets/svg/google.svg';
@@ -223,6 +249,7 @@ export default {
return isEmail(this.email);
},
emailInvalid () {
if (this.email.length <= 3) return false;
return !this.emailValid;
},
usernameValid () {
@@ -230,13 +257,23 @@ export default {
return this.usernameIssues.length === 0;
},
usernameInvalid () {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
},
passwordConfirmInvalid () {
if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid;
},
},
@@ -1,13 +1,25 @@
<template>
<div class="form-wrapper">
<div class="warning-banner d-flex" v-if="forgotPassword && preOutage">
<div class="warning-box ml-auto my-auto mr-2 d-flex">
<div
class="svg-icon exclamation m-auto"
v-html="icons.exclamation"
>
</div>
</div>
<div class="mr-auto my-auto">
Habitica emails will be temporarily unavailable on <strong>January 11, 2020</strong> from
<strong>1:00 - 7:00 AM EST</strong>.
</div>
</div>
<div id="top-background">
<div class="seamless_stars_varied_opacity_repeat"></div>
</div>
<form
v-if="!forgotPassword && !resetPasswordSetNewOne"
id="login-form"
@submit.prevent="handleSubmit"
@keyup.enter="handleSubmit"
@submit.prevent.stop="handleSubmit"
>
<div class="text-center">
<div>
@@ -69,7 +81,7 @@
<input
id="usernameInput"
v-model="username"
class="form-control"
class="form-control input-with-error"
type="text"
:placeholder="$t('usernamePlaceholder')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
@@ -132,7 +144,17 @@
class="form-control"
type="password"
:placeholder="$t(registering ? 'passwordPlaceholder' : 'password')"
:class="{
'input-invalid input-with-error': registering && passwordInvalid,
'input-valid': registering && passwordValid
}"
>
<div
v-if="passwordInvalid && registering"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div>
<div
v-if="registering"
@@ -145,11 +167,17 @@
<input
id="confirmPasswordInput"
v-model="passwordConfirm"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<small
v-once
class="form-text"
@@ -157,22 +185,22 @@
></small>
</div>
<div class="text-center">
<div
<button
v-if="registering"
v-once
type="submit"
class="btn btn-info"
@click="register()"
:disabled="signupFormInvalid"
>
{{ $t('joinHabitica') }}
</div>
<div
</button>
<button
v-if="!registering"
v-once
type="submit"
class="btn btn-info"
@click="login()"
>
{{ $t('login') }}
</div>
</button>
<div class="toggle-links">
<router-link
v-if="registering"
@@ -275,10 +303,17 @@
<input
id="passwordInput"
v-model="password"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('password')"
:class="{'input-invalid': passwordInvalid, 'input-valid': passwordValid}"
>
<div
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
</div>
<div class="form-group">
<label
@@ -288,10 +323,17 @@
<input
id="confirmPasswordInput"
v-model="passwordConfirm"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('confirmPasswordPlaceholder')"
:class="{'input-invalid': passwordConfirmInvalid, 'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
</div>
<div class="text-center">
<div
@@ -426,10 +468,14 @@
color: $white;
}
#usernameInput.input-invalid {
.input-with-error.input-invalid {
margin-bottom: 0.5em;
}
#confirmPasswordInput + .input-error {
margin-bottom: 2em;
}
.form-text {
font-size: 14px;
color: $white;
@@ -480,7 +526,7 @@
background-image: url('~@/assets/images/auth/seamless_mountains_demo.png');
background-repeat: repeat-x;
width: 100%;
height: 500px;
height: 300px;
position: absolute;
z-index: 0;
bottom: 0;
@@ -512,16 +558,37 @@
color: #fff;
font-size: 90%;
width: 100%;
text-align: center;
}
.warning-banner {
color: $white;
background-color: $maroon-100;
height: 2.5rem;
width: 100%;
}
.warning-box {
font-weight: bold;
width: 1rem;
height: 1rem;
border: 2px solid;
border-radius: 2px;
}
.exclamation {
width: 2px;
}
</style>
<script>
import axios from 'axios';
import hello from 'hellojs';
import moment from 'moment';
import debounce from 'lodash/debounce';
import isEmail from 'validator/lib/isEmail';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
import exclamation from '@/assets/svg/exclamation.svg';
import gryphon from '@/assets/svg/gryphon.svg';
import habiticaIcon from '@/assets/svg/habitica-logo.svg';
import facebookSquareIcon from '@/assets/svg/facebook-square.svg';
@@ -543,6 +610,7 @@ export default {
};
data.icons = Object.freeze({
exclamation,
gryphon,
habiticaIcon,
facebookIcon: facebookSquareIcon,
@@ -580,6 +648,14 @@ export default {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
@@ -588,6 +664,15 @@ export default {
if (this.passwordConfirm.length <= 3) return false;
return !this.passwordConfirmValid;
},
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
preOutage () {
return moment.utc().isBefore('2020-01-12');
},
},
watch: {
$route: {
@@ -653,7 +738,7 @@ export default {
return;
}
// @TODO: implement langauge and invite accepting
// @TODO: implement language and invite accepting
// var url = ApiUrl.get() + "/api/v4/user/auth/local/register";
// if (location.search && location.search.indexOf('Invite=') !== -1)
// { // matches groupInvite and partyInvite
+1 -1
View File
@@ -212,7 +212,7 @@ export default {
},
visualBuffs () {
return {
snowball: 'snowman',
snowball: `avatar_snowball_${this.member.stats.class}`,
spookySparkles: 'ghost',
shinySeed: `avatar_floral_${this.member.stats.class}`,
seafoam: 'seafoam_star',
@@ -151,7 +151,13 @@ export default {
'blackTopFrame', 'blueTopFrame', 'greenTopFrame', 'pinkTopFrame', 'redTopFrame', 'whiteTopFrame', 'yellowTopFrame',
'blackHalfMoon', 'blueHalfMoon', 'greenHalfMoon', 'pinkHalfMoon', 'redHalfMoon', 'whiteHalfMoon', 'yellowHalfMoon',
];
const options = keys.map(key => {
const noneOption = this.createGearItem(0, 'eyewear', 'base');
noneOption.none = true;
const options = [
noneOption,
];
for (const key of keys) {
const newKey = `eyewear_special_${key}`;
const option = {};
option.key = key;
@@ -164,8 +170,9 @@ export default {
return this.equip(newKey, type);
};
return option;
});
options.push(option);
}
return options;
},
freeShirts () {
@@ -179,20 +186,18 @@ export default {
},
headbands () {
const keys = ['blackHeadband', 'blueHeadband', 'greenHeadband', 'pinkHeadband', 'redHeadband', 'whiteHeadband', 'yellowHeadband'];
const options = keys.map(key => {
const newKey = `headAccessory_special_${key}`;
const option = {};
option.key = key;
option.active = this.user.preferences.costume
? this.user.items.gear.costume.headAccessory === newKey
: this.user.items.gear.equipped.headAccessory === newKey;
option.class = `headAccessory_special_${option.key} headband`;
option.click = () => {
const type = this.user.preferences.costume ? 'costume' : 'equipped';
return this.equip(newKey, type);
};
return option;
});
const noneOption = this.createGearItem(0, 'headAccessory', 'base', 'headband');
noneOption.none = true;
const options = [
noneOption,
];
for (const key of keys) {
const option = this.createGearItem(key, 'headAccessory', 'special', 'headband');
options.push(option);
}
return options;
},
chairs () {
@@ -234,7 +239,14 @@ export default {
// user purchases object, this is not recomputed. Hack for now
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
const keys = this.animalItemKeys[category];
const options = keys.map(key => {
const noneOption = this.createGearItem(0, category, 'base', category);
noneOption.none = true;
const options = [
noneOption,
];
for (const key of keys) {
const newKey = `${category}_special_${key}`;
const userPurchased = this.user.items.gear.owned[newKey];
@@ -265,8 +277,10 @@ export default {
const type = this.user.preferences.costume ? 'costume' : 'equipped';
return this.equip(newKey, type);
};
return option;
});
options.push(option);
}
return options;
},
animalItemsUnlockString (category) {
@@ -285,6 +299,44 @@ export default {
});
return own;
},
createGearItem (key, gearType, subGearType, additionalClass) {
const newKey = `${gearType}_${subGearType ? `${subGearType}_` : ''}${key}`;
const option = {};
option.key = key;
const visibleGearType = this.user.preferences.costume ? 'costume' : 'equipped';
const currentlyEquippedValue = this.user.items.gear[visibleGearType][gearType];
option.active = currentlyEquippedValue === newKey;
if (key === 0) {
// if key is the "none" option check if a property
// doesn't have a value and mark it as active
option.active = option.active || !currentlyEquippedValue;
}
option.class = `${newKey} ${additionalClass}`;
option.click = () => {
const type = this.user.preferences.costume ? 'costume' : 'equipped';
const currentlyEquipped = this.user.items.gear[type][gearType];
// no need to call api/equip-op if its already selected
if (currentlyEquipped === newKey || (key === 0 && !currentlyEquipped)) {
return;
}
let keyToEquip = newKey;
if (option.none) {
// you need to "equip" the current selected AGAIN in order to un-equip it
// the "none-key" isn't allowed to be sent
keyToEquip = currentlyEquipped;
}
this.equip(keyToEquip, type);
};
return option;
},
},
};
</script>
@@ -1,7 +1,10 @@
<template>
<div class="row">
<challenge-modal @updatedChallenge="updatedChallenge" />
<leave-challenge-modal :challenge-id="challenge._id" />
<leave-challenge-modal
:challenge-id="challenge._id"
@update-challenge="updateChallenge"
/>
<close-challenge-modal
:members="members"
:challenge-id="challenge._id"
@@ -493,12 +496,20 @@ export default {
},
async joinChallenge () {
this.user.challenges.push(this.searchId);
await this.$store.dispatch('challenges:joinChallenge', { challengeId: this.searchId });
this.challenge = await this.$store.dispatch('challenges:joinChallenge', { challengeId: this.searchId });
this.members = await this
.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true });
await this.$store.dispatch('tasks:fetchUserTasks', { forceLoad: true });
},
async leaveChallenge () {
this.$root.$emit('bv::show::modal', 'leave-challenge-modal');
},
async updateChallenge () {
this.challenge = await this.$store.dispatch('challenges:getChallenge', { challengeId: this.searchId });
this.members = await this
.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true });
},
closeChallenge () {
this.$root.$emit('bv::show::modal', 'close-challenge-modal');
},
@@ -215,7 +215,6 @@
}
.meta-info {
height: 24px;
margin-bottom: 8px;
}
@@ -295,8 +294,9 @@
justify-content: space-evenly;
background-color: $gray-700;
text-align: center;
padding: 16px;
padding: 8px;
border-radius: .25em;
flex-wrap: wrap;
> div {
.value {
@@ -327,6 +327,8 @@
}
> div.muted {
margin: 8px;
.value {
opacity: 0.5;
font-size: 20px;
@@ -595,7 +595,7 @@ export default {
this.$root.$emit('bv::hide::modal', 'challenge-modal');
this.$router.push(`/challenges/${challenge._id}`);
},
updateChallenge () {
async updateChallenge () {
const categoryKeys = this.workingChallenge.categories;
const serverCategories = [];
categoryKeys.forEach(key => {
@@ -610,10 +610,8 @@ export default {
const challengeDetails = clone(this.workingChallenge);
challengeDetails.categories = serverCategories;
this.$emit('updatedChallenge', {
challenge: challengeDetails,
});
this.$store.dispatch('challenges:updateChallenge', { challenge: challengeDetails });
const challenge = await this.$store.dispatch('challenges:updateChallenge', { challenge: challengeDetails });
this.$emit('updatedChallenge', { challenge });
this.resetWorkingChallenge();
this.$root.$emit('bv::hide::modal', 'challenge-modal');
},
@@ -68,6 +68,17 @@
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
@media only screen and (max-width: 768px) {
.header-row {
margin-bottom: 1rem;
}
.col-10.standard-page {
// full width on smaller devices
max-width: 100%;
}
}
.header-row {
h1 {
color: $purple-200;
@@ -54,6 +54,7 @@ export default {
this.close();
},
close () {
this.$emit('update-challenge');
this.$root.$emit('bv::hide::modal', 'leave-challenge-modal');
},
},
@@ -88,6 +88,17 @@
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
@media only screen and (max-width: 768px) {
.header-row {
margin-bottom: 1rem;
}
.col-10.standard-page {
// full width on smaller devices
max-width: 100%;
}
}
.header-row {
h1 {
color: $purple-200;
@@ -282,7 +282,7 @@ export default {
const promises = [];
const noProfilesLoaded = Object.keys(this.cachedProfileData).length === 0;
// @TODO: write an explination
// @TODO: write an explanation
// @TODO: Remove this after enough messages are cached
if (
!noProfilesLoaded
+21 -6
View File
@@ -6,7 +6,15 @@
:hide-header="true"
:hide-footer="true"
:modal-class="{'page-2':modalPage > 1 && !editing}"
:no-close-on-esc="!editing"
:no-close-on-backdrop="!editing"
>
<span
v-if="editing"
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<div
v-if="modalPage === 1 && !editing"
class="section row welcome-section"
@@ -113,7 +121,7 @@
v-if="editing"
class="menu-container col-2"
:class="{active: activeTopPage === 'backgrounds'}"
@click="changeTopPage('backgrounds', '2019')"
@click="changeTopPage('backgrounds', '2020')"
>
<div class="menu-item">
<div
@@ -321,7 +329,7 @@
<div class="task-option">
<div class="custom-control custom-checkbox">
<input
id="excercise"
id="exercise"
v-model="taskCategories"
class="custom-control-input"
type="checkbox"
@@ -330,7 +338,7 @@
<label
v-once
class="custom-control-label"
for="excercise"
for="exercise"
>{{ $t('exercise') }}</label>
</div>
</div>
@@ -1099,6 +1107,7 @@ import gold from '@/assets/svg/gold.svg';
import pin from '@/assets/svg/pin.svg';
import arrowRight from '@/assets/svg/arrow_right.svg';
import arrowLeft from '@/assets/svg/arrow_left.svg';
import svgClose from '@/assets/svg/close.svg';
import isPinned from '@/../../common/script/libs/isPinned';
import { avatarEditorUtilies } from '../mixins/avatarEditUtilities';
@@ -1138,6 +1147,7 @@ export default {
gold,
arrowRight,
arrowLeft,
close: svgClose,
}),
modalPage: 1,
activeTopPage: 'body',
@@ -1150,7 +1160,7 @@ export default {
},
],
bgSubMenuItems: ['2019', '2018', '2017', '2016', '2015', '2014'].map(y => ({
bgSubMenuItems: ['2020', '2019', '2018', '2017', '2016', '2015', '2014'].map(y => ({
id: y,
label: y,
})),
@@ -1158,8 +1168,6 @@ export default {
},
computed: {
...mapState({ user: 'user.data' }),
editing () {
return this.$store.state.avatarEditorOptions.editingUser;
},
@@ -1175,6 +1183,7 @@ export default {
2017: [],
2018: [],
2019: [],
2020: [],
};
// Hack to force update for now until we restructure the data
@@ -1228,6 +1237,9 @@ export default {
this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'avatar-modal');
},
purchase (type, key) {
this.$store.dispatch('shops:purchase', {
type,
@@ -1278,6 +1290,9 @@ export default {
if (this.$route.path !== '/') {
this.$router.push('/');
}
// NOTE: it's important this flag is set AFTER the onboarding default tasks
// have been created or it'll break the onboarding guide achievement for creating a task
this.$store.dispatch('user:set', {
'flags.welcomed': true,
});
@@ -479,11 +479,19 @@ export default {
if (!isEmpty(this.sortOption)) {
// Use the memberlist filtered by searchTerm
sortedMembers = orderBy(
sortedMembers,
[this.sortOption.value],
[this.sortOption.direction],
);
if (this.sortOption.value === 'profile.name') {
// If members are to be sorted by name, use localeCompare for case-
// insensitive sort
sortedMembers.sort(
(a, b) => a.profile.name.localeCompare(b.profile.name),
);
} else {
sortedMembers = orderBy(
sortedMembers,
[this.sortOption.value],
[this.sortOption.direction],
);
}
}
return sortedMembers;
@@ -491,7 +499,7 @@ export default {
},
watch: {
groupId () {
// @TOOD: We might not need this since groupId is computed now
// @TODO: We might not need this since groupId is computed now
this.getMembers();
},
challengeId () {
@@ -655,7 +663,7 @@ export default {
memberId: member._id,
groupId: this.groupId,
});
this.viewMembers();
if (this.invites.length === 0) this.viewMembers();
},
async promoteToLeader (member) {
const groupData = { ...this.group };
@@ -1,8 +1,9 @@
<template functional>
<span
class="message-count"
class="message-count d-flex align-items-center justify-content-center"
:class="{'top-count': props.top === true, 'top-count-gray': props.gray === true}"
> {{ props.count }} </span>
v-html="props.badge || props.count"
></span>
</template>
<style lang="scss">
@@ -18,6 +19,11 @@
text-align: center;
font-weight: bold;
font-size: 12px;
svg {
width: 12px;
height: 12px;
}
}
.message-count.top-count {
@@ -10,7 +10,10 @@
>
<slot name="icon"></slot>
</div>
<div class="notification-content">
<div
class="notification-content"
:class="{'has-text': hasText}"
>
<slot name="content"></slot>
</div>
<div
@@ -107,17 +110,19 @@
line-height: 1.43;
color: $gray-50;
max-width: calc(100% - 26px); // to make space for the close icon
max-width: 100%;
&.has-text {
padding-right: 12px;
}
}
.notification-remove {
// total distance from the notification top edge is 20 pixels
margin-top: 7px;
position: absolute;
width: 18px;
height: 18px;
margin-left: 12px;
padding: 4px;
right: 24px;
.svg-icon {
width: 10px;
@@ -131,7 +136,25 @@ import closeIcon from '@/assets/svg/close.svg';
import { mapActions, mapState } from '@/libs/store';
export default {
props: ['notification', 'canRemove', 'hasIcon', 'readAfterClick'],
props: {
notification: {
type: Object,
required: true,
},
canRemove: {
type: Boolean,
},
hasIcon: {
type: Boolean,
},
readAfterClick: {
type: Boolean,
},
hasText: {
type: Boolean,
default: true,
},
},
data () {
return {
icons: Object.freeze({
@@ -3,7 +3,7 @@
:can-remove="canRemove"
:has-icon="true"
:notification="notification"
:read-after-click="true"
:read-after-click="false"
@click="action"
>
<div
@@ -0,0 +1,74 @@
<template>
<base-notification
:can-remove="canRemove"
:has-icon="false"
:read-after-click="true"
:notification="notification"
:has-text="false"
@click="action"
>
<div
slot="content"
class="d-flex flex-column align-items-center onboarding-complete-content"
>
<img
class="onboarding-complete-banner d-block"
src="~@/assets/images/onboarding-complete-banner@2x.png"
>
<h3>{{ $t('congratulations') }}</h3>
<p
class="onboarding-complete-text"
v-html="$t('onboardingCompleteDesc')"
></p>
<div class="notifications-buttons">
<div
class="btn btn-small btn-primary btn-block"
>
{{ $t('viewAchievements') }}
</div>
</div>
</div>
</base-notification>
</template>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.onboarding-complete-content {
text-align: center;
}
.onboarding-complete-banner {
width: 282px;
margin-top: 12px;
margin-bottom: 12px;
}
.onboarding-complete-text {
margin-bottom: 4px;
}
.onboarding-complete-text ::v-deep .gold-amount {
color: $yellow-5;
}
.notifications-buttons {
width: 100%;
}
</style>
<script>
import BaseNotification from './base';
export default {
components: {
BaseNotification,
},
props: ['notification', 'canRemove'],
methods: {
action () {
this.$router.push({ name: 'achievements' });
},
},
};
</script>
@@ -11,10 +11,11 @@
:aria-label="$t('notifications')"
>
<message-count
v-if="notificationsCount > 0"
v-if="notificationsCount > 0 || hasSpecialBadge"
:count="notificationsCount"
:top="true"
:gray="!hasUnseenNotifications"
:gray="!hasUnseenNotifications && !hasSpecialBadge"
:badge="hasSpecialBadge ? icons.starBadge : null"
/>
<div
class="top-menu-icon svg-icon notifications"
@@ -22,7 +23,10 @@
></div>
</div>
</div>
<div slot="dropdown-content">
<div
v-if="openStatus === 1"
slot="dropdown-content"
>
<div
class="dropdown-item dropdown-separated
d-flex justify-content-between dropdown-inactive align-items-center"
@@ -41,6 +45,10 @@
>{{ $t('dismissAll') }}</a>
</div>
<world-boss />
<onboarding-guide
v-if="showOnboardingGuide"
:never-seen="hasSpecialBadge"
/>
<component
:is="notification.type"
v-for="notification in notifications"
@@ -49,7 +57,7 @@
:can-remove="!isActionable(notification)"
/>
<div
v-if="notificationsCount === 0"
v-if="notificationsCount === 0 && !showOnboardingGuide"
class="dropdown-item dropdown-separated
d-flex justify-content-center dropdown-inactive no-notifications flex-column"
>
@@ -106,10 +114,16 @@
<script>
import { mapState, mapActions } from '@/libs/store';
import * as quests from '@/../../common/script/content/quests';
import {
hasCompletedOnboarding,
hasActiveOnboarding,
} from '@/../../common/script/libs/onboarding';
import notificationsIcon from '@/assets/svg/notifications.svg';
import MenuDropdown from '../ui/customMenuDropdown';
import MessageCount from './messageCount';
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
import successImage from '@/assets/svg/success.svg';
import starBadge from '@/assets/svg/star-badge.svg';
// Notifications
import NEW_STUFF from './notifications/newStuff';
@@ -132,6 +146,8 @@ import VERIFY_USERNAME from './notifications/verifyUsername';
import ACHIEVEMENT_JUST_ADD_WATER from './notifications/justAddWater';
import ACHIEVEMENT_LOST_MASTERCLASSER from './notifications/lostMasterclasser';
import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter';
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
import OnboardingGuide from './onboardingGuide';
export default {
components: {
@@ -158,18 +174,22 @@ export default {
ACHIEVEMENT_MIND_OVER_MATTER,
WorldBoss: WORLD_BOSS,
VERIFY_USERNAME,
OnboardingGuide,
ONBOARDING_COMPLETE,
},
data () {
return {
icons: Object.freeze({
notifications: notificationsIcon,
success: successImage,
starBadge,
}),
hasSpecialBadge: false,
quests,
openStatus: undefined,
actionableNotifications: [
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK', 'GROUP_TASK_APPROVAL',
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK',
],
// A list of notifications handled by this component,
// listed in the order they should appear in the notifications panel.
@@ -177,11 +197,11 @@ export default {
handledNotifications: [
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED', 'GROUP_TASK_CLAIMED',
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
'GROUP_TASK_CLAIMED', 'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
'VERIFY_USERNAME',
'VERIFY_USERNAME', 'ONBOARDING_COMPLETE',
],
};
},
@@ -275,6 +295,20 @@ export default {
hasClass () {
return this.$store.getters['members:hasClass'](this.user);
},
showOnboardingGuide () {
return hasActiveOnboarding(this.user) && !hasCompletedOnboarding(this.user);
},
},
mounted () {
const onboardingPanelState = getLocalSetting(CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE);
if (
onboardingPanelState !== CONSTANTS.onboardingPanelValues.PANEL_OPENED
&& this.showOnboardingGuide
) {
// The first time the onboarding panel is opened a special
// badge for notifications should be used
this.hasSpecialBadge = true;
}
},
methods: {
...mapActions({
@@ -286,6 +320,18 @@ export default {
// Mark notifications as seen when the menu is opened
if (openStatus) this.markAllAsSeen();
// Reset the special notification badge as soon as it's opened
if (this.hasSpecialBadge) {
setLocalSetting(
CONSTANTS.keyConstants.ONBOARDING_PANEL_STATE,
CONSTANTS.onboardingPanelValues.PANEL_OPENED,
);
setTimeout(() => {
this.hasSpecialBadge = false;
}, 100);
}
},
markAllAsSeen () {
const idsToSee = this.notifications.map(notification => {
@@ -318,5 +364,6 @@ export default {
return this.actionableNotifications.indexOf(notification.type) !== -1;
},
},
};
</script>
@@ -0,0 +1,259 @@
<template>
<div
class="onboarding-guide-panel d-flex align-items-center flex-column p-4 dropdown-separated"
@click.stop.prevent="action"
>
<div
class="svg-icon onboarding-toggle"
:class="{'onboarding-toggle-open': open}"
@click="openPanel"
v-html="icons.down"
></div>
<div
class="svg-icon onboarding-guide-banner"
v-html="icons.onboardingGuideBanner"
></div>
<h3 class="getting-started">
{{ $t('gettingStarted') }}
</h3>
<span
class="getting-started-desc"
v-html="$t('gettingStartedDesc')"
></span>
<div
class="onboarding-progress-box d-flex flex-row justify-content-between small-text mb-2"
>
<strong>Your Progress</strong>
<span :class="{'has-progress': progress > 0}">{{ progressText }}</span>
</div>
<div class="onboarding-progress-bar mb-3">
<div
class="onboarding-progress-bar-fill"
:style="{width: `${progress}%`}"
></div>
</div>
<b-collapse
id="onboardingPanelCollapse"
v-model="open"
>
<div
v-for="(achievement, key) in onboardingAchievements"
:key="key"
:class="{
'achievement-earned': achievement.earned
}"
class="achievement-box d-flex flex-row"
>
<div class="achievement-icon-wrapper">
<div :class="`achievement-icon ${getAchievementIcon(achievement)}`"></div>
</div>
<div class="achievement-info d-flex flex-column">
<strong class="achievement-title">{{ achievement.title }}</strong>
<span class="small-text achievement-desc">{{ getAchievementText(key) }}</span>
</div>
</div>
</b-collapse>
</div>
</template>
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
.onboarding-guide-panel {
white-space: normal;
text-align: center;
}
.onboarding-toggle {
cursor: pointer;
position: absolute;
top: 75px;
right: 24px;
width: 16px;
& ::v-deep svg path {
stroke: $gray-200;
}
&:hover ::v-deep svg path {
stroke: $gray-100;
}
&-open {
transform: rotate(-180deg);
}
}
.onboarding-guide-banner {
margin-top: -8px;
margin-bottom: 12px;
width: 154px;
height: 48px;
}
.getting-started {
margin-bottom: 4px;
}
.getting-started-desc {
font-size: 14px;
padding-bottom: 12px;
}
.getting-started-desc ::v-deep .gold-amount {
color: $yellow-5;
}
.onboarding-progress-box {
width: 100%;
font-style: normal;
strong {
color: $gray-50;
}
.has-progress {
color: $yellow-5;
}
}
.onboarding-progress-bar {
width: 100%;
height: 4px;
border-radius: 2px;
background-color: $gray-600;
.onboarding-progress-bar-fill {
height: 100%;
border-radius: 2px;
background: $yellow-5;
}
}
.achievement-box {
padding-top: 11px;
padding-bottom: 12px;
border-bottom: 1px dashed $gray-500;
width: 100%;
&:last-child {
padding-bottom: 0px;
border-bottom: none;
}
}
.achievement-earned {
.achievement-icon-wrapper {
opacity: 1;
}
color: $gray-200;
.achievement-title {
text-decoration: line-through;
}
}
.achievement-icon-wrapper {
opacity: 0.5;
width: 40px;
.achievement-icon {
margin-left: -12px;
transform: scale(0.5);
}
}
.achievement-info {
width: 100%;
text-align: left;
font-size: 14px;
}
.achievement-title {
margin-bottom: 4px;
}
.achievement-desc {
font-style: normal;
}
</style>
<script>
import achievs from '@/../../common/script/libs/achievements';
import { mapState } from '@/libs/store';
import onboardingGuideBanner from '@/assets/svg/onboarding-guide-banner.svg';
import downIcon from '@/assets/svg/down.svg';
export default {
props: {
neverSeen: { // whether it's ever been seen by the user
type: Boolean,
default: false,
},
},
data () {
return {
icons: Object.freeze({
onboardingGuideBanner,
down: downIcon,
}),
open: false,
};
},
computed: {
...mapState({ user: 'user.data' }),
onboardingAchievements () {
return achievs.getAchievementsForProfile(this.user).onboarding.achievements;
},
progress () {
const keys = Object.keys(this.onboardingAchievements);
let nEarned = 0;
keys.forEach(key => {
if (this.onboardingAchievements[key].earned) nEarned += 1;
});
return (nEarned / keys.length) * 100;
},
progressText () {
if (this.progress === 0) {
return this.$t('letsGetStarted');
}
return this.$t('onboardingProgress', { percentage: this.progress });
},
},
created () {
// null means it's never been automatically toggled
if (this.neverSeen === true) {
// The first time the panel should be automatically opened
this.open = true;
}
},
methods: {
getAchievementIcon (achievement) {
if (achievement.earned) {
return `${achievement.icon}2x`;
}
return 'achievement-unearned2x';
},
getAchievementText (key) {
let stringKey = 'achievement';
stringKey += key.charAt(0).toUpperCase() + key.slice(1);
stringKey += 'ModalText';
return this.$t(stringKey);
},
openPanel (e) {
e.preventDefault();
e.stopPropagation();
this.open = !this.open;
},
action () {
// Do nothing, used to prevent closure on click.
},
},
};
</script>
@@ -341,7 +341,7 @@ export default {
const { firstRender } = viewOptions;
const { itemsInFirstPosition } = viewOptions;
// Render selected items in first postion only for the first render
// Render selected items in first position only for the first render
if (itemsInFirstPosition.indexOf(ownedItem.key) !== -1 && firstRender === false) {
gearItemsByType[type].unshift(ownedItem);
} else if (isEquipped === true && firstRender === true) {
@@ -382,7 +382,7 @@ export default {
const { firstRender } = viewOptions;
const { itemsInFirstPosition } = viewOptions;
// Render selected items in first postion only for the first render
// Render selected items in first position only for the first render
if (itemsInFirstPosition.indexOf(ownedItem.key) !== -1 && firstRender === false) {
gearItemsByClass[klass].unshift(ownedItem);
} else if (isEquipped === true && firstRender === true) {
@@ -3,6 +3,11 @@
id="hatchedPet-modal"
:hide-header="true"
>
<span
class="close-icon svg-icon inline icon-10"
@click="close()"
v-html="icons.close"
></span>
<div
v-if="pet != null"
class="content"
@@ -14,7 +19,7 @@
{{ $t('hatchedPetGeneric') }}
</div>
<div class="inner-content">
<div class="pet-background">
<div class="pet-background d-flex align-items-center">
<div :class="pet.class"></div>
</div>
<h4 class="title">
@@ -51,6 +56,10 @@
width: 330px;
}
.modal-footer {
padding-top: 0px;
}
.content {
text-align: center;
}
@@ -75,12 +84,25 @@
.dialog-header {
color: $purple-200;
margin-top: 16px;
}
.text {
margin-bottom: 24px;
min-height: 0;
&.markdown {
p {
margin-bottom: 0px;
}
}
}
}
</style>
<script>
import markdownDirective from '@/directives/markdown';
import svgClose from '@/assets/svg/close.svg';
export default {
directives: {
@@ -94,6 +116,9 @@ export default {
data () {
return {
pet: null,
icons: Object.freeze({
close: svgClose,
}),
};
},
mounted () {
@@ -26,10 +26,14 @@
<quest-completed />
<quest-invitation />
<verify-username />
<generic-achievement :data="notificationData" />
<generic-achievement
v-if="notificationData && notificationData.achievement"
:data="notificationData"
/>
<just-add-water />
<lost-masterclasser />
<mind-over-matter />
<onboarding-complete />
</div>
</template>
@@ -92,6 +96,10 @@
background-color: #4f2a93 !important;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12) !important;
}
.introjs-skipbutton.btn-primary, .introjs-donebutton.btn-primary {
color: #fff;
}
</style>
<script>
@@ -99,9 +107,11 @@ import axios from 'axios';
import moment from 'moment';
import throttle from 'lodash/throttle';
import debounce from 'lodash/debounce';
import Vue from 'vue';
import { toNextLevel } from '@/../../common/script/statHelpers';
import { shouldDo } from '@/../../common/script/cron';
import { onOnboardingComplete } from '@/../../common/script/libs/onboarding';
import { mapState } from '@/libs/store';
import notifications from '@/mixins/notifications';
import guide from '@/mixins/guide';
@@ -132,6 +142,7 @@ import justAddWater from './achievements/justAddWater';
import lostMasterclasser from './achievements/lostMasterclasser';
import mindOverMatter from './achievements/mindOverMatter';
import loginIncentives from './achievements/login-incentives';
import onboardingComplete from './achievements/onboardingComplete';
import verifyUsername from './settings/verifyUsername';
const NOTIFICATIONS = {
@@ -164,21 +175,33 @@ const NOTIFICATIONS = {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementAllYourBase')}`,
modalId: 'generic-achievement',
data: {
achievement: 'allYourBase', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_BACK_TO_BASICS: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementBackToBasics')}`,
modalId: 'generic-achievement',
data: {
achievement: 'backToBasics', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_DUST_DEVIL: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementDustDevil')}`,
modalId: 'generic-achievement',
data: {
achievement: 'dustDevil', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_ARID_AUTHORITY: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementAridAuthority')}`,
modalId: 'generic-achievement',
data: {
achievement: 'aridAuthority', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_PARTY_UP: {
achievement: true,
@@ -187,6 +210,7 @@ const NOTIFICATIONS = {
data: {
message: $t => $t('achievement'),
modalText: $t => $t('achievementPartyUp'),
achievement: 'partyUp', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_PARTY_ON: {
@@ -196,6 +220,7 @@ const NOTIFICATIONS = {
data: {
message: $t => $t('achievement'),
modalText: $t => $t('achievementPartyOn'),
achievement: 'partyOn', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_BEAST_MASTER: {
@@ -205,6 +230,7 @@ const NOTIFICATIONS = {
data: {
message: $t => $t('achievement'),
modalText: $t => $t('beastAchievement'),
achievement: 'beastMaster', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_MOUNT_MASTER: {
@@ -214,6 +240,7 @@ const NOTIFICATIONS = {
data: {
message: $t => $t('achievement'),
modalText: $t => $t('mountAchievement'),
achievement: 'mountMaster', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_TRIAD_BINGO: {
@@ -223,17 +250,49 @@ const NOTIFICATIONS = {
data: {
message: $t => $t('achievement'),
modalText: $t => $t('triadBingoAchievement'),
achievement: 'triadBingo', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_MONSTER_MAGUS: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementMonsterMagus')}`,
modalId: 'generic-achievement',
data: {
achievement: 'monsterMagus', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_UNDEAD_UNDERTAKER: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementUndeadUndertaker')}`,
modalId: 'generic-achievement',
data: {
achievement: 'undeadUndertaker', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT: { // data filled in handleUserNotifications
achievement: true,
modalId: 'generic-achievement',
label: null, // data filled in handleUserNotifications
data: {
message: $t => $t('achievement'),
modalText: null, // data filled in handleUserNotifications
},
},
ACHIEVEMENT_PRIMED_FOR_PAINTING: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementPrimedForPainting')}`,
modalId: 'generic-achievement',
data: {
achievement: 'primedForPainting', // defined manually until the server sends all the necessary data
},
},
ACHIEVEMENT_PEARLY_PRO: {
achievement: true,
label: $t => `${$t('achievement')}: ${$t('achievementPearlyPro')}`,
modalId: 'generic-achievement',
data: {
achievement: 'pearlyPro', // defined manually until the server sends all the necessary data
},
},
};
@@ -266,6 +325,7 @@ export default {
lostMasterclasser,
mindOverMatter,
justAddWater,
onboardingComplete,
},
mixins: [notifications, guide],
data () {
@@ -291,7 +351,8 @@ export default {
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'ACHIEVEMENT_ALL_YOUR_BASE', 'ACHIEVEMENT_BACK_TO_BASICS',
'GENERIC_ACHIEVEMENT', 'ACHIEVEMENT_PARTY_UP', 'ACHIEVEMENT_PARTY_ON', 'ACHIEVEMENT_BEAST_MASTER',
'ACHIEVEMENT_MOUNT_MASTER', 'ACHIEVEMENT_TRIAD_BINGO', 'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY',
'ACHIEVEMENT_MONSTER_MAGUS', 'ACHIEVEMENT_UNDEAD_UNDERTAKER', 'GENERIC_ACHIEVEMENT',
'ACHIEVEMENT_MONSTER_MAGUS', 'ACHIEVEMENT_UNDEAD_UNDERTAKER', 'ACHIEVEMENT_PRIMED_FOR_PAINTING',
'ACHIEVEMENT_PEARLY_PRO', 'ACHIEVEMENT', 'ONBOARDING_COMPLETE',
].forEach(type => {
handledNotifications[type] = true;
});
@@ -467,7 +528,7 @@ export default {
this.text(config.label(this.$t), () => {
this.notificationData = data;
this.$root.$emit('bv::show::modal', config.modalId);
}, false);
}, true, 10000);
}
},
debounceCheckUserAchievements: debounce(function debounceCheck () {
@@ -697,9 +758,23 @@ export default {
case 'ACHIEVEMENT_TRIAD_BINGO':
case 'ACHIEVEMENT_MONSTER_MAGUS':
case 'ACHIEVEMENT_UNDEAD_UNDERTAKER':
case 'ACHIEVEMENT_PRIMED_FOR_PAINTING':
case 'ACHIEVEMENT_PEARLY_PRO':
case 'GENERIC_ACHIEVEMENT':
this.showNotificationWithModal(notification);
break;
case 'ACHIEVEMENT': { // generic achievement
const { achievement } = notification.data;
const upperCaseAchievement = achievement.charAt(0).toUpperCase() + achievement.slice(1);
const achievementTitleKey = `achievement${upperCaseAchievement}`;
NOTIFICATIONS.ACHIEVEMENT.label = $t => `${$t('achievement')}: ${$t(achievementTitleKey)}`;
NOTIFICATIONS.ACHIEVEMENT.data.modalText = $t => $t(achievementTitleKey);
this.showNotificationWithModal(notification);
// Set the achievement as it's not defined in the user schema
Vue.set(this.user.achievements, achievement, true);
break;
}
case 'CRON':
if (notification.data) {
if (notification.data.hp) this.hp(notification.data.hp, 'hp');
@@ -729,6 +804,20 @@ export default {
this.$root.$emit('bv::show::modal', 'login-incentives');
}
break;
case 'ONBOARDING_COMPLETE':
// Award rewards
onOnboardingComplete(this.user);
// If the user cronned in the last 3 minutes
// Don't show too many modals on app load
// Use notification panel
if (moment().diff(this.user.lastCron, 'minutes') < 3) {
markAsRead = false;
} else {
// Otherwise use the modal
this.$root.$emit('bv::show::modal', 'onboarding-complete');
}
break;
}
if (markAsRead) notificationsToRead.push(notification.id);
@@ -28,39 +28,38 @@
<small>{{ $t('unsubscribeAllEmailsText') }}</small>
</div>
<div class="col-8">
<table class="table"></table>
<tr>
<td></td>
<th>
<span>{{ $t('email') }}</span>
</th>
<th>
<span>{{ $t('push') }}</span>
</th>
</tr>
<tr
v-for="notification in notificationsIds"
:key="notification"
>
<td>
<span>{{ $t(notification) }}</span>
</td>
<td>
<input
v-model="user.preferences.emailNotifications[notification]"
type="checkbox"
@change="set('emailNotifications', notification)"
>
</td>
<td v-if="onlyEmailsIds.indexOf(notification) === -1">
<input
v-model="user.preferences.pushNotifications[notification]"
type="checkbox"
@change="set('pushNotifications', notification)"
>
<hr>
</td>
</tr>
<table class="table">
<tr>
<td></td>
<th>
<span>{{ $t('email') }}</span>
</th>
<th>
<span>{{ $t('push') }}</span>
</th>
</tr>
<tr v-for="notification in notificationsIds" :key="notification">
<td>
<span>{{ $t(notification) }}</span>
</td>
<td>
<input
v-model="user.preferences.emailNotifications[notification]"
type="checkbox"
@change="set('emailNotifications', notification)"
>
</td>
<td v-if="onlyEmailsIds.indexOf(notification) === -1">
<input
v-model="user.preferences.pushNotifications[notification]"
type="checkbox"
@change="set('pushNotifications', notification)"
>
<td v-else>
&nbsp;
</td>
</tr>
</table>
</div>
</div>
</template>
@@ -74,6 +73,8 @@ export default {
data () {
return {
notificationsIds: Object.freeze([
'majorUpdates',
'onboarding',
'newPM',
'wonChallenge',
'giftedGems',
@@ -85,8 +86,6 @@ export default {
'invitedQuest',
'importantAnnouncements',
'weeklyRecaps',
'onboarding',
'majorUpdates',
'subscriptionReminders',
]),
// list of email-only notifications
@@ -812,7 +812,8 @@ export default {
this.localAuth.username = this.user.auth.local.username;
this.user.flags.verifiedUsername = true;
} else if (attribute === 'email') {
this.user.auth.local.email = updates[attribute];
this.user.auth.local.email = updates.newEmail;
window.alert(this.$t('emailSuccess'));
}
},
async changeDisplayName (newName) {
@@ -1,5 +1,5 @@
<template>
<div class="standard-page">
<div class="standard-page pt-0 px-0">
<h1>{{ $t('subscription') }}</h1>
<div class="row">
<div class="col-6">
@@ -242,8 +242,10 @@
</template>
<style scoped lang="scss">
@import '~@/assets/scss/colors.scss';
.badge.badge-success {
color: #fff;
color: $white;
}
.subscribe-pay {
@@ -33,6 +33,9 @@
<div class="how-many-to-buy">
<strong>{{ $t('howManyToBuy') }}</strong>
</div>
<div class="mb-3" v-if="item.addlNotes">
{{ item.addlNotes }}
</div>
<div class="box">
<input
v-model.number="selectedAmountToBuy"
+44 -25
View File
@@ -53,9 +53,9 @@
<div class="strike">
<span>{{ $t('or') }}</span>
</div>
<div
<form
class="form"
@keyup.enter="register()"
@submit.prevent.stop="register()"
>
<p class="form-text">
{{ $t('usernameLimitations') }}
@@ -63,7 +63,7 @@
<input
id="usernameInput"
v-model="username"
class="form-control"
class="form-control input-with-error"
type="text"
:placeholder="$t('username')"
:class="{'input-valid': usernameValid, 'input-invalid': usernameInvalid}"
@@ -85,32 +85,49 @@
>
<input
v-model="password"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('password')"
:class="{'input-valid': password.length > 3}"
:class="{
'input-valid': passwordValid,
'input-invalid': passwordInvalid,
}"
>
<div
v-if="passwordInvalid"
class="input-error"
>
{{ $t('minPasswordLength') }}
</div>
<input
v-model="passwordConfirm"
class="form-control"
class="form-control input-with-error"
type="password"
:placeholder="$t('confirmPassword')"
:class="{
'input-invalid': passwordConfirmInvalid,
'input-valid': passwordConfirmValid}"
>
<div
v-if="passwordConfirmInvalid"
class="input-error"
>
{{ $t('passwordConfirmationMatch') }}
</div>
<p
v-once
class="form-text"
v-html="$t('termsAndAgreement')"
></p>
<button
class="sign-up"
class="btn btn-block btn-info sign-up"
:disabled="signupFormInvalid"
type="submit"
@click="register()"
>
{{ $t('signup') }}
</button>
</div>
</form>
</div>
<div class="col-12">
<div
@@ -519,7 +536,7 @@
transition: border .5s, color .5s;
}
#usernameInput.input-invalid {
.input-invalid.input-with-error {
margin-bottom: 0.5em;
}
@@ -537,21 +554,9 @@
background-color: #36205d;
}
button.sign-up {
width: 100%;
height: 48px;
color: #fff;
border: none;
border-radius: 2px;
background-color: #2995cd;
font-size: 16px;
transition: all .5s ease;
}
.sign-up:hover {
background-color: #50b5e9;
box-shadow: 0 4px 4px 0 rgba(26, 24, 29, 0.16), 0 1px 8px 0 rgba(26, 24, 29, 0.12);
cursor: pointer;
.sign-up {
padding-top: 11px;
padding-bottom: 11px;
}
::-webkit-input-placeholder { /* Chrome/Opera/Safari */
@@ -769,7 +774,6 @@
color: #fff;
font-size: 90%;
width: 100%;
text-align: center;
margin-bottom: 1em;
}
</style>
@@ -796,6 +800,7 @@ import lifehacker from '@/assets/images/home/lifehacker.svg';
import makeuseof from '@/assets/images/home/make-use-of.svg';
import thenewyorktimes from '@/assets/images/home/the-new-york-times.svg';
import * as Analytics from '@/libs/analytics';
import { MINIMUM_PASSWORD_LENGTH } from '@/../../common/script/constants';
export default {
data () {
@@ -844,6 +849,14 @@ export default {
if (this.username.length < 1) return false;
return !this.usernameValid;
},
passwordValid () {
if (this.password.length <= 0) return false;
return this.password.length >= MINIMUM_PASSWORD_LENGTH;
},
passwordInvalid () {
if (this.password.length <= 0) return false;
return this.password.length < MINIMUM_PASSWORD_LENGTH;
},
passwordConfirmValid () {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm === this.password;
@@ -852,6 +865,12 @@ export default {
if (this.passwordConfirm.length <= 3) return false;
return this.passwordConfirm !== this.password;
},
signupFormInvalid () {
return this.usernameInvalid
|| this.emailInvalid
|| this.passwordInvalid
|| this.passwordConfirmInvalid;
},
},
watch: {
username () {

Some files were not shown because too many files have changed in this diff Show More