diff --git a/website/client/assets/creator/arrow.png b/website/client/assets/creator/arrow.png
deleted file mode 100644
index 410be81a77..0000000000
Binary files a/website/client/assets/creator/arrow.png and /dev/null differ
diff --git a/website/client/assets/creator/arrow@2x.png b/website/client/assets/creator/arrow@2x.png
deleted file mode 100644
index 003d4b98c2..0000000000
Binary files a/website/client/assets/creator/arrow@2x.png and /dev/null differ
diff --git a/website/client/assets/creator/arrow@3x.png b/website/client/assets/creator/arrow@3x.png
deleted file mode 100644
index 938172fa18..0000000000
Binary files a/website/client/assets/creator/arrow@3x.png and /dev/null differ
diff --git a/website/client/assets/creator/prev.png b/website/client/assets/creator/prev.png
deleted file mode 100644
index 6ad81b7d3c..0000000000
Binary files a/website/client/assets/creator/prev.png and /dev/null differ
diff --git a/website/client/assets/creator/prev@2x.png b/website/client/assets/creator/prev@2x.png
deleted file mode 100644
index f5f24ed3bf..0000000000
Binary files a/website/client/assets/creator/prev@2x.png and /dev/null differ
diff --git a/website/client/assets/creator/prev@3x.png b/website/client/assets/creator/prev@3x.png
deleted file mode 100644
index 780145a87e..0000000000
Binary files a/website/client/assets/creator/prev@3x.png and /dev/null differ
diff --git a/website/client/assets/scss/colors.scss b/website/client/assets/scss/colors.scss
index ee3864f7c0..9ced003cc1 100644
--- a/website/client/assets/scss/colors.scss
+++ b/website/client/assets/scss/colors.scss
@@ -78,3 +78,5 @@ $wizard-color: #2995CD;
$gems-color: #24CC8F;
$gold-color: #FFA624;
$hourglass-color: #2995CD;
+
+$purple-task: #925cf3;
diff --git a/website/client/assets/scss/task.scss b/website/client/assets/scss/task.scss
index 46d17aba95..f7fc28751a 100644
--- a/website/client/assets/scss/task.scss
+++ b/website/client/assets/scss/task.scss
@@ -231,6 +231,19 @@
}
&-purple { // purple, only used in modals
+ &-control {
+ &-bg {
+ background: $purple-task !important;
+ &:hover {
+ .habit-control { background: rgba(26, 24, 29, 0.48) !important; }
+ .daily-todo-control { background: rgba(255, 255, 255, 0.72) !important; }
+ }
+ }
+ &-inner-habit { background: rgba(26, 24, 29, 0.24) !important; }
+ &-inner-daily-todo { background: #ffffff80 !important; }
+ &-checkbox { color: $purple-task !important; }
+ }
+
&-modal {
&-bg { background: $purple-300 !important; }
&-icon { color: $purple-300 !important; }
diff --git a/website/client/assets/svg/arrow_left.svg b/website/client/assets/svg/arrow_left.svg
new file mode 100644
index 0000000000..51be9229ea
--- /dev/null
+++ b/website/client/assets/svg/arrow_left.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/client/assets/svg/arrow_right.svg b/website/client/assets/svg/arrow_right.svg
new file mode 100644
index 0000000000..88947c9bdd
--- /dev/null
+++ b/website/client/assets/svg/arrow_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/website/client/components/avatarModal/body-settings.vue b/website/client/components/avatarModal/body-settings.vue
new file mode 100644
index 0000000000..cea1b2c7dd
--- /dev/null
+++ b/website/client/components/avatarModal/body-settings.vue
@@ -0,0 +1,92 @@
+
+ #body.section.customize-section
+ sub-menu.text-center(:items="items", :activeSubPage="activeSubPage", @changeSubPage="changeSubPage($event)")
+ div(v-if='activeSubPage === "size"')
+ customize-options(
+ :items="sizes",
+ :currentValue="user.preferences.size"
+ )
+ div(v-if='activeSubPage === "shirt"')
+ customize-options(
+ :items="freeShirts",
+ :currentValue="user.preferences.shirt"
+ )
+ customize-options(
+ v-if='editing',
+ :items='specialShirts',
+ :currentValue="user.preferences.shirt",
+ :fullSet='!userOwnsSet("shirt", specialShirtKeys)',
+ @unlock='unlock(`shirt.${specialShirtKeys.join(",shirt.")}`)'
+ )
+
+
+
+
+
diff --git a/website/client/components/avatarModal/customize-options.vue b/website/client/components/avatarModal/customize-options.vue
new file mode 100644
index 0000000000..27eb6e2e0b
--- /dev/null
+++ b/website/client/components/avatarModal/customize-options.vue
@@ -0,0 +1,304 @@
+
+ .customize-options(:class="{'background-set': fullSet}")
+ .outer-option-background(
+ v-for='option in items',
+ :key='option.key',
+ @click='option.click(option)',
+ :class='{locked: option.gemLocked || option.goldLocked, premium: Boolean(option.gem), active: option.active || currentValue === option.key, none: option.none, hide: option.hide }'
+ )
+ .option
+ .sprite.customize-option(:class='option.class')
+ .redline-outer(v-if="option.none")
+ .redline
+ .gem-lock(v-if='option.gemLocked')
+ .svg-icon.gem(v-html='icons.gem')
+ span {{ option.gem }}
+ .gold-lock(v-if='option.goldLocked')
+ .svg-icon.gold(v-html='icons.gold')
+ span {{ option.gold }}
+ .purchase-set(v-if='fullSet', @click='unlock()')
+ span.label {{ $t('purchaseAll') }}
+ .svg-icon.gem(v-html='icons.gem')
+ span.price 5
+
+
+
+
+
diff --git a/website/client/components/avatarModal/extra-settings.vue b/website/client/components/avatarModal/extra-settings.vue
new file mode 100644
index 0000000000..406771ebfd
--- /dev/null
+++ b/website/client/components/avatarModal/extra-settings.vue
@@ -0,0 +1,272 @@
+
+ #extra.section.container.customize-section
+ sub-menu.text-center(:items="extraSubMenuItems", :activeSubPage="activeSubPage", @changeSubPage="changeSubPage($event)")
+
+ #hair-color(v-if='activeSubPage === "glasses"')
+ customize-options(
+ :items="eyewear"
+ )
+
+ #animal-ears(v-if='activeSubPage === "ears"')
+ customize-options(
+ :items="animalItems('headAccessory')",
+ :fullSet='!animalItemsOwned("headAccessory")',
+ @unlock='unlock(animalItemsUnlockString("headAccessory"))'
+ )
+
+ #animal-tails(v-if='activeSubPage === "tails"')
+ customize-options(
+ :items="animalItems('back')",
+ :fullSet='!animalItemsOwned("back")',
+ @unlock='unlock(animalItemsUnlockString("back"))'
+ )
+ #headband(v-if='activeSubPage === "headband"')
+ customize-options(
+ :items="headbands",
+ )
+
+ #wheelchairs(v-if='activeSubPage === "wheelchair"')
+ customize-options(
+ :items="chairs",
+ )
+ #flowers(v-if='activeSubPage === "flower"')
+ customize-options(
+ :items="flowers",
+ )
+
+
+
+
+
diff --git a/website/client/components/avatarModal/hair-settings.vue b/website/client/components/avatarModal/hair-settings.vue
new file mode 100644
index 0000000000..d86f608aba
--- /dev/null
+++ b/website/client/components/avatarModal/hair-settings.vue
@@ -0,0 +1,341 @@
+
+ #hair.section.customize-section
+ sub-menu.text-center(:items="hairSubMenuItems", :activeSubPage="activeSubPage", @changeSubPage="changeSubPage($event)")
+
+ #hair-color(v-if='activeSubPage === "color"')
+ customize-options(
+ :items="freeHairColors",
+ :currentValue="user.preferences.hair.color"
+ )
+
+ div(v-if='editing && set.key !== "undefined"', v-for='set in seasonalHairColors')
+ customize-options(
+ :items='set.options',
+ :currentValue="user.preferences.skin",
+ :fullSet='!hideSet(set) && !userOwnsSet("hair", set.keys, "color")',
+ @unlock='unlock(`hair.color.${set.keys.join(",hair.color.")}`)'
+ )
+
+ #style(v-if='activeSubPage === "style"')
+ div(v-for='set in styleSets')
+ customize-options(
+ :items='set.options',
+ :fullSet='set.fullSet',
+ @unlock='set.unlock()'
+ )
+ #bangs(v-if='activeSubPage === "bangs"')
+ customize-options(
+ :items='hairBangs',
+ :currentValue="user.preferences.hair.bangs"
+ )
+ #facialhair(v-if='activeSubPage === "facialhair"')
+ customize-options(
+ v-if='editing',
+ :items='mustacheList'
+ )
+ customize-options(
+ v-if='editing',
+ :items='beardList',
+ :fullSet='isPurchaseAllNeeded("hair", ["baseHair5", "baseHair6"], ["mustache", "beard"])',
+ @unlock='unlock(`hair.mustache.${baseHair5Keys.join(",hair.mustache.")},hair.beard.${baseHair6Keys.join(",hair.beard.")}`)'
+ )
+
+
+
+
+
diff --git a/website/client/components/avatarModal/skin-settings.vue b/website/client/components/avatarModal/skin-settings.vue
new file mode 100644
index 0000000000..f180b47a3f
--- /dev/null
+++ b/website/client/components/avatarModal/skin-settings.vue
@@ -0,0 +1,114 @@
+
+ #skin.section.customize-section
+ sub-menu.text-center(:items="skinSubMenuItems", :activeSubPage="activeSubPage", @changeSubPage="changeSubPage($event)")
+ customize-options(
+ :items="freeSkins",
+ :currentValue="user.preferences.skin"
+ )
+
+ div(v-if='editing && set.key !== "undefined"', v-for='set in seasonalSkins')
+ customize-options(
+ :items='set.options',
+ :currentValue="user.preferences.skin",
+ :fullSet='!hideSet(set) && !userOwnsSet("skin", set.keys)',
+ @unlock='unlock(`skin.${set.keys.join(",skin.")}`)'
+ )
+
+
+
+
+
diff --git a/website/client/components/avatarModal/sub-menu.vue b/website/client/components/avatarModal/sub-menu.vue
new file mode 100644
index 0000000000..3d24bc0994
--- /dev/null
+++ b/website/client/components/avatarModal/sub-menu.vue
@@ -0,0 +1,52 @@
+
+ .sub-menu.text-center
+ .sub-menu-item(
+ v-for="item of items",
+ :key="item.id",
+ @click='$emit("changeSubPage", item.id)',
+ :class='{active: activeSubPage === item.id}'
+ )
+ strong(v-once) {{ item.label }}
+
+
+
+
+
diff --git a/website/client/components/creatorIntro.vue b/website/client/components/creatorIntro.vue
index a62477bd46..e049d0348b 100644
--- a/website/client/components/creatorIntro.vue
+++ b/website/client/components/creatorIntro.vue
@@ -1,5 +1,5 @@
-b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true', :hide-footer='true', :class='{"page-2": modalPage > 1 && !editing}')
+b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true', :hide-footer='true', :modal-class="{'page-2':modalPage > 1 && !editing}")
.section.row.welcome-section(v-if='modalPage === 1 && !editing')
.col-6.offset-3.text-center
h3(v-once) {{$t('welcomeTo')}}
@@ -20,268 +20,48 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
.menu-item
.svg-icon(v-html='icons.bodyIcon')
strong(v-once) {{$t('bodyBody')}}
+ .indicator
.menu-container(@click='changeTopPage("skin", "color")', :class='{"col-3": !editing, "col-2": editing, active: activeTopPage === "skin"}')
.menu-item
.svg-icon(v-html='icons.skinIcon')
strong(v-once) {{$t('skin')}}
+ .indicator
.menu-container(@click='changeTopPage("hair", "color")', :class='{"col-3": !editing, "col-2": editing, active: activeTopPage === "hair"}')
.menu-item
.svg-icon(v-html='icons.hairIcon')
strong(v-once) {{$t('hair')}}
+ .indicator
.menu-container(@click='changeTopPage("extra", "glasses")', :class='{"col-3": !editing, "col-2": editing, active: activeTopPage === "extra"}')
.menu-item
.svg-icon(v-html='icons.accessoriesIcon')
strong(v-once) {{$t('extra')}}
+ .indicator
.menu-container.col-2(@click='changeTopPage("backgrounds", "2019")', v-if='editing', :class='{active: activeTopPage === "backgrounds"}')
.menu-item
.svg-icon(v-html='icons.backgroundsIcon')
strong(v-once) {{$t('backgrounds')}}
- #body.section.customize-section(v-if='activeTopPage === "body"')
- .row.sub-menu.text-center
- .col-3.offset-3.sub-menu-item(@click='changeSubPage("size")', :class='{active: activeSubPage === "size"}')
- strong(v-once) {{$t('size')}}
- .col-3.sub-menu-item(@click='changeSubPage("shirt")', :class='{active: activeSubPage === "shirt"}')
- strong(v-once) {{$t('shirt')}}
- .row(v-if='activeSubPage === "size"')
- .col-12.customize-options.size-options
- .option(v-for='option in ["slim", "broad"]',
- :class='{active: user.preferences.size === option}',
- @click='set({"preferences.size": option})')
- .sprite.customize-option(:class="`${option}_shirt_black`")
- .row(v-if='activeSubPage === "shirt"')
- .col-12.customize-options
- .option(v-for='option in ["black", "blue", "green", "pink", "white", "yellow"]',
- :class='{active: user.preferences.shirt === option}',
- @click='set({"preferences.shirt": option})')
- .sprite.customize-option(:class="`slim_shirt_${option}`")
- .col-12.customize-options(v-if='editing')
- .option(v-for='item in specialShirts',
- :class='{active: item.active, locked: item.locked}',
- @click='item.click')
- .sprite.customize-option(:class="`broad_shirt_${item.key}`")
- .gem-lock(v-if='item.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if='!userOwnsSet("shirt", specialShirtKeys)')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`shirt.${specialShirtKeys.join(",shirt.")}`)') {{ $t('purchaseAll') }}
- #skin.section.customize-section(v-if='activeTopPage === "skin"')
- .row.sub-menu.col-6.offset-3.text-center
- .col-6.offset-3.text-center.sub-menu-item(:class='{active: activeSubPage === "color"}')
- strong(v-once) {{$t('color')}}
- .row
- .col-12.customize-options
- .option(v-for='option in ["ddc994", "f5a76e", "ea8349", "c06534", "98461a", "915533", "c3e1dc", "6bd049"]',
- :class='{active: user.preferences.skin === option}',
- @click='set({"preferences.skin": option})')
- .skin.sprite.customize-option(:class="`skin_${option}`")
- .row(v-if='editing && set.key !== "undefined"', v-for='set in seasonalSkins')
- .col-12.customize-options
- .option(v-for='option in set.options',
- :class='{active: option.active, locked: option.locked, hide: option.hide}',
- @click='option.click')
- .skin.sprite.customize-option(:class="`skin_${option.key}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if='!hideSet(set) && !userOwnsSet("skin", set.keys)')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`skin.${set.keys.join(",skin.")}`)') {{ $t('purchaseAll') }}
- #hair.section.customize-section(v-if='activeTopPage === "hair"')
- .row.col-12.sub-menu.text-center
- .col-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color", "offset-2": !editing}')
- strong(v-once) {{$t('color')}}
- .col-3.text-center.sub-menu-item(@click='changeSubPage("bangs")', :class='{active: activeSubPage === "bangs"}')
- strong(v-once) {{$t('bangs')}}
- .col-3.text-center.sub-menu-item(@click='changeSubPage("style")', :class='{active: activeSubPage === "style"}')
- strong(v-once) {{$t('style')}}
- .col-3.text-center.sub-menu-item(@click='changeSubPage("facialhair")', :class='{active: activeSubPage === "facialhair"}', v-if='editing')
- strong(v-once) {{$t('facialhair')}}
- #hair-color.row(v-if='activeSubPage === "color"')
- .col-12.customize-options
- .option(v-for='option in ["white", "brown", "blond", "red", "black"]',
- :class='{active: user.preferences.hair.color === option}',
- @click='set({"preferences.hair.color": option})')
- .color-bangs.sprite.customize-option(:class="`hair_bangs_1_${option}`")
- .col-12.customize-options(v-if='editing && set.key !== "undefined"', v-for='set in seasonalHairColors')
- .option(v-for='option in set.options',
- :class='{active: option.active, locked: option.locked, hide: option.hide}',
- @click='option.click')
- .skin.sprite.customize-option(:class="`hair_bangs_1_${option.key}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if='!hideSet(set) && !userOwnsSet("hair", set.keys, "color")')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`hair.color.${set.keys.join(",hair.color.")}`)') {{ $t('purchaseAll') }}
- #style.row(v-if='activeSubPage === "style"')
- .col-12.customize-options(v-if='editing')
- .head_0.option(@click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
- .option(v-for='option in baseHair3',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .base.sprite.customize-option(:class="`hair_base_${option.key}_${user.preferences.hair.color}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if='!userOwnsSet("hair", baseHair3Keys, "base")')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair3Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
- .col-12.customize-options(v-if='editing')
- .option(v-for='option in baseHair4',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .base.sprite.customize-option(:class="`hair_base_${option.key}_${user.preferences.hair.color}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if='!userOwnsSet("hair", baseHair4Keys, "base")')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair4Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
- .col-12.customize-options
- .head_0.option(v-if="!editing", @click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
- .option(v-for='option in baseHair1',
- :class='{active: user.preferences.hair.base === option}',
- @click='set({"preferences.hair.base": option})')
- .base.sprite.customize-option(:class="`hair_base_${option}_${user.preferences.hair.color}`")
- .col-12.customize-options(v-if='editing')
- .option(v-for='option in baseHair2',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .base.sprite.customize-option(:class="`hair_base_${option.key}_${user.preferences.hair.color}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if='!userOwnsSet("hair", baseHair2Keys, "base")')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair2Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
- #bangs.row(v-if='activeSubPage === "bangs"')
- .col-12.customize-options
- .head_0.option(@click='set({"preferences.hair.bangs": 0})',
- :class="[{ active: user.preferences.hair.bangs === 0 }, 'hair_bangs_0_' + user.preferences.hair.color]")
- .option(v-for='option in [1, 2, 3, 4]',
- :class='{active: user.preferences.hair.bangs === option}',
- @click='set({"preferences.hair.bangs": option})')
- .bangs.sprite.customize-option(:class="`hair_bangs_${option}_${user.preferences.hair.color}`")
- #facialhair.row(v-if='activeSubPage === "facialhair"')
- .col-12.customize-options(v-if='editing')
- .head_0.option(@click='set({"preferences.hair.mustache": 0})', :class="[{ active: user.preferences.hair.mustache === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
- .option(v-for='option in baseHair5',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .base.sprite.customize-option(:class="`hair_mustache_${option.key}_${user.preferences.hair.color}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.customize-options(v-if='editing')
- .head_0.option(@click='set({"preferences.hair.beard": 0})', :class="[{ active: user.preferences.hair.beard === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
- .option(v-for='option in baseHair6',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .base.sprite.customize-option(:class="`hair_beard_${option.key}_${user.preferences.hair.color}`")
- .gem-lock(v-if='option.locked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .col-12.text-center(v-if="isPurchaseAllNeeded('hair', ['baseHair5', 'baseHair6'], ['mustache', 'beard'])")
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(`hair.mustache.${baseHair5Keys.join(",hair.mustache.")},hair.beard.${baseHair6Keys.join(",hair.beard.")}`)') {{ $t('purchaseAll') }}
- #extra.section.container.customize-section(v-if='activeTopPage === "extra"')
- .row.sub-menu
- .col-3.offset-1.text-center.sub-menu-item(@click='changeSubPage("glasses")', :class='{active: activeSubPage === "glasses"}')
- strong(v-once) {{ $t('glasses') }}
- .col-4.text-center.sub-menu-item(@click='changeSubPage("wheelchair")', :class='{active: activeSubPage === "wheelchair"}')
- strong(v-once) {{ $t('wheelchair') }}
- .col-3.text-center.sub-menu-item(@click='changeSubPage("flower")', :class='{active: activeSubPage === "flower"}')
- strong(v-once) {{ $t('accent') }}
- .row.sub-menu(v-if='editing')
- .col-4.text-center.sub-menu-item(@click='changeSubPage("ears")' :class='{active: activeSubPage === "ears"}')
- strong(v-once) {{ $t('animalEars') }}
- .col-4.text-center.sub-menu-item(@click='changeSubPage("tails")' :class='{active: activeSubPage === "tails"}')
- strong(v-once) {{ $t('animalTails') }}
- .col-4.text-center.sub-menu-item(@click='changeSubPage("headband")' :class='{active: activeSubPage === "headband"}')
- strong(v-once) {{ $t('headband') }}
- #glasses.row(v-if='activeSubPage === "glasses"')
- .col-12.customize-options
- .option(v-for='option in eyewear',
- :class='{active: option.active}',
- @click='option.click')
- .sprite.customize-option(:class="`eyewear_special_${option.key}`")
- #animal-ears.row(v-if='activeSubPage === "ears"')
- .section.col-12.customize-options
- .option(v-for='option in animalItems("headAccessory")',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .sprite.customize-option(:class="`headAccessory_special_${option.key}`")
- .gem-lock(v-if='option.gemLocked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .gold-lock(v-if='option.goldLocked')
- .svg-icon.gold(v-html='icons.gold')
- span 20
- .col-12.text-center(v-if='!animalItemsOwned("headAccessory")')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(animalItemsUnlockString("headAccessory"))') {{ $t('purchaseAll') }}
- #animal-tails.row(v-if='activeSubPage === "tails"')
- .section.col-12.customize-options
- .option(v-for='option in animalItems("back")',
- :class='{active: option.active, locked: option.locked}',
- @click='option.click')
- .sprite.customize-option(:class="`icon_back_special_${option.key}`")
- .gem-lock(v-if='option.gemLocked')
- .svg-icon.gem(v-html='icons.gem')
- span 2
- .gold-lock(v-if='option.goldLocked')
- .svg-icon.gold(v-html='icons.gold')
- span 20
- .col-12.text-center(v-if='!animalItemsOwned("back")')
- .gem-lock
- .svg-icon.gem(v-html='icons.gem')
- span 5
- button.btn.btn-secondary.purchase-all(@click='unlock(animalItemsUnlockString("back"))') {{ $t('purchaseAll') }}
- #headband.row(v-if='activeSubPage === "headband"')
- .col-12.customize-options
- .option(v-for='option in headbands',
- :class='{active: option.active}',
- @click='option.click')
- .sprite.customize-option(:class="`headAccessory_special_${option.key}`")
- #wheelchairs.row(v-if='activeSubPage === "wheelchair"')
- .col-12.customize-options
- .option(@click='set({"preferences.chair": "none"})', :class='{active: user.preferences.chair === "none"}')
- | None
- .option(v-for='option in chairKeys',
- :class='{active: user.preferences.chair === option}',
- @click='set({"preferences.chair": option})')
- .chair.sprite.customize-option(:class="`button_chair_${option}`")
- #flowers.row(v-if='activeSubPage === "flower"')
- .col-12.customize-options
- .head_0.option(@click='set({"preferences.hair.flower":0})', :class='{active: user.preferences.hair.flower === 0}')
- .option(v-for='option in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]',
- :class='{active: user.preferences.hair.flower === option}',
- @click='set({"preferences.hair.flower": option})')
- .sprite.customize-option(:class="`hair_flower_${option}`")
- .row(v-if='activeSubPage === "flower"')
- .col-12.customize-options
- // button.customize-option(ng-repeat='item in ::getGearArray("animal")', class='{{::item.key}}',
- ng-class="{locked: user.items.gear.owned[item.key] == undefined, selectableInventory: user.preferences.costume ? user.items.gear.costume.headAccessory == item.key : user.items.gear.equipped.headAccessory == item.key}",
- popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter',
- popover-placement='right', popover-append-to-body='true',
- ng-click='user.items.gear.owned[item.key] ? equip(item.key) : purchase(item.type,item)')
+ .indicator
+
+ body-settings(
+ v-if='activeTopPage === "body"',
+ :editing="editing",
+ )
+
+ skin-settings(
+ v-if='activeTopPage === "skin"',
+ :editing="editing",
+ )
+
+ hairSettings(
+ v-if='activeTopPage === "hair"',
+ :editing="editing"
+ )
+
+ extraSettings(
+ v-if='activeTopPage === "extra"',
+ :editing="editing"
+ )
+
#backgrounds.section.container.customize-section(v-if='activeTopPage === "backgrounds"')
.row.title-row
toggle-switch.backgroundFilterToggle(:label="'Hide locked backgrounds'", v-model='filterBackgrounds')
@@ -296,19 +76,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
popover-trigger='mouseenter')
.incentive-background(:class='[`background_${bg.key}`]')
.small-rectangle
- .row.sub-menu.col-10.offset-1(v-if='!filterBackgrounds')
- .col-2.text-center.sub-menu-item(@click='changeSubPage("2019")', :class='{active: activeSubPage === "2019"}')
- strong(v-once) 2019
- .col-2.text-center.sub-menu-item(@click='changeSubPage("2018")', :class='{active: activeSubPage === "2018"}')
- strong(v-once) 2018
- .col-2.text-center.sub-menu-item(@click='changeSubPage("2017")', :class='{active: activeSubPage === "2017"}')
- strong(v-once) 2017
- .col-2.text-center.sub-menu-item(@click='changeSubPage("2016")', :class='{active: activeSubPage === "2016"}')
- strong(v-once) 2016
- .col-2.text-center.sub-menu-item(@click='changeSubPage("2015")', :class='{active: activeSubPage === "2015"}')
- strong(v-once) 2015
- .col-2.text-center.sub-menu-item(@click='changeSubPage("2014")', :class='{active: activeSubPage === "2014"}')
- strong(v-once) 2014
+ sub-menu.text-center(:items="bgSubMenuItems", :activeSubPage="activeSubPage", @changeSubPage="changeSubPage($event)")
.row.customize-menu(v-if='!filterBackgrounds' v-for='(sets, key) in backgroundShopSetsByYear')
.row.background-set(v-for='set in sets', v-if='activeSubPage === key')
.col-8.offset-2.text-center.set-title
@@ -378,7 +146,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
input.custom-control-input#self_care(type="checkbox", value='self_care', v-model='taskCategories')
label.custom-control-label(v-once, for="self_care") {{ $t('self_care') }}
- .section.d-flex.justify-content-center(:class='{top: modalPage > 1}', v-if='!editing')
+ .section.d-flex.justify-content-center.justin-outer-section(:class='{top: modalPage > 1}', v-if='!editing')
.justin-section.d-flex.align-items-center
.featured-label
span.rectangle
@@ -397,537 +165,563 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
div(v-if='modalPage === 3')
p(v-once) {{ $t('justinIntroMessage3') }}
.npc-justin-textbox
- .section.mr-5.ml-5(v-if='modalPage === 1')
+ .section.mr-5.ml-5.first-page-footer(v-if='modalPage === 1')
username-form(@usernameConfirmed='modalPage += 1', :avatarIntro='true')
.small.text-center(v-html="$t('usernameTOSRequirements')")
.section.container.footer(v-if='!editing && !(modalPage === 1)')
- .row
- .col-3.offset-1.text-center
- div(v-if='modalPage > 1', @click='prev()')
- .prev-arrow
- .prev(v-once) {{ $t('prev') }}
- .col-4.text-center.circles
- .circle(:class="{active: modalPage === 1}")
- .circle(:class="{active: modalPage === 2}")
- .circle(:class="{active: modalPage === 3}")
- .col-3.text-center
- div(v-if='modalPage < 3', @click='next()')
- .next(v-once) {{$t('next')}}
- .next-arrow
- div(v-if='modalPage === 3', @click='done()')
- button.btn.btn-primary.next(v-once, v-if='!loading') {{$t('done')}}
+ .footer-left
+ div.prev-outer(v-if='modalPage > 1', @click='prev()')
+ .prev-arrow.svg-icon(v-html='icons.arrowLeft')
+ .prev(v-once) {{ $t('prev') }}
+ .footer-center.text-center.circles
+ .circle(:class="{active: modalPage === 1}")
+ .circle(:class="{active: modalPage === 2}")
+ .circle(:class="{active: modalPage === 3}")
+ .footer-right
+ div.next-outer(v-if='modalPage < 3', @click='next()')
+ .next(v-once) {{$t('next')}}
+ .next-arrow.svg-icon(v-html='icons.arrowRight')
+ div.next-outer(v-if='modalPage === 3 && !loading', @click='done()', :class="{disabled: taskCategories.length === 0}")
+ .next(v-once) {{$t('finish')}}
+ .next-arrow.svg-icon(v-html='icons.arrowRight')
-
-
-
diff --git a/website/client/components/tasks/column.vue b/website/client/components/tasks/column.vue
index f5a69506bb..1d73d8d5ed 100644
--- a/website/client/components/tasks/column.vue
+++ b/website/client/components/tasks/column.vue
@@ -154,7 +154,6 @@
text-align: center;
overflow-y: hidden;
- max-height: 65px; // approximate max height
}
.quick-add-tip-slide-enter-active {
diff --git a/website/client/components/tasks/task.vue b/website/client/components/tasks/task.vue
index 70e3d04619..34351b8006 100644
--- a/website/client/components/tasks/task.vue
+++ b/website/client/components/tasks/task.vue
@@ -1,6 +1,6 @@
.task-wrapper
- .task(@click='castEnd($event, task)')
+ .task(@click='castEnd($event, task)', :class="`type_${task.type}`")
approval-header(:task='task', v-if='this.task.group.id', :group='group')
.d-flex(:class="{'task-not-scoreable': isUser !== true}")
// Habits left side control
@@ -418,7 +418,6 @@
transition-property: border-color, background, color;
transition-timing-function: ease-in;
}
-
.left-control {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
@@ -428,8 +427,14 @@
& + .task-content {
border-left: none;
- border-top-right-radius: 2px;
- border-bottom-right-radius: 2px;
+ }
+ }
+ .task:not(.type_habit) {
+ .left-control {
+ & + .task-content {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
}
}
diff --git a/website/client/mixins/avatarEditUtilities.js b/website/client/mixins/avatarEditUtilities.js
new file mode 100644
index 0000000000..00fe66168c
--- /dev/null
+++ b/website/client/mixins/avatarEditUtilities.js
@@ -0,0 +1,178 @@
+import moment from 'moment';
+import axios from 'axios';
+
+import unlock from '../../common/script/ops/unlock';
+import buy from '../../common/script/ops/buy/buy';
+
+import get from 'lodash/get';
+
+import appearanceSets from 'common/script/content/appearance/sets';
+
+
+import {userStateMixin} from './userState';
+
+export const avatarEditorUtilies = {
+ mixins: [userStateMixin],
+ data () {
+ return {
+ backgroundUpdate: new Date(),
+ };
+ },
+ methods: {
+ hideSet (set) {
+ return moment(appearanceSets[set.key].availableUntil).isBefore(moment());
+ },
+ mapKeysToFreeOption (key, type, subType) {
+ const userPreference = subType ? this.user.preferences[type][subType] : this.user.preferences[type];
+ const pathKey = subType ? `${type}.${subType}` : `${type}`;
+
+ const option = {};
+ option.key = key;
+ option.pathKey = pathKey;
+ option.active = userPreference === key;
+ option.class = this.createClass(type, subType, key);
+ option.click = (optionParam) => {
+ return option.gemLocked ? this.unlock(`${optionParam.pathKey}.${key}`) : this.set({[`preferences.${optionParam.pathKey}`]: optionParam.key});
+ };
+ return option;
+ },
+ mapKeysToOption (key, type, subType, set) {
+ const option = this.mapKeysToFreeOption(key, type, subType);
+
+ let userPurchased = subType ? this.user.purchased[type][subType] : this.user.purchased[type];
+ let locked = !userPurchased || !userPurchased[key];
+ let hide = false;
+
+ if (set && appearanceSets[set]) {
+ if (locked) hide = moment(appearanceSets[set].availableUntil).isBefore(moment());
+ }
+
+ option.gemLocked = locked;
+ option.hide = hide;
+ if (locked) {
+ option.gem = 2;
+ }
+
+ return option;
+ },
+ createClass (type, subType, key) {
+ let str = `${type} ${subType} `;
+
+ switch (type) {
+ case 'shirt': {
+ str += `${this.user.preferences.size}_shirt_${key}`;
+ break;
+ }
+ case 'size': {
+ str += `${key}_shirt_black`;
+ break;
+ }
+ case 'hair': {
+ if (subType === 'color') {
+ str += `hair_bangs_1_${key}`; // todo get current hair-bang setting
+ } else {
+ str += `hair_${subType}_${key}_${this.user.preferences.hair.color}`;
+ }
+ break;
+ }
+ case 'skin': {
+ str += `skin skin_${key}`;
+ break;
+ }
+ default: {
+ // `hair_base_${option.key}_${user.preferences.hair.color}`
+ // console.warn('unknown type', type, key);
+ }
+ }
+
+ return str;
+ },
+ userOwnsSet (type, setKeys, subType) {
+ let owns = true;
+
+ setKeys.forEach(key => {
+ if (subType) {
+ if (!this.user.purchased[type] || !this.user.purchased[type][subType] || !this.user.purchased[type][subType][key]) owns = false;
+ return;
+ }
+ if (!this.user.purchased[type][key]) owns = false;
+ });
+
+ return owns;
+ },
+ set (settings) {
+ this.$store.dispatch('user:set', settings);
+ },
+ equip (key, type) {
+ this.$store.dispatch('common:equip', {key, type});
+ },
+ /**
+ * For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase
+ * @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc.
+ * Pass in this paramater as "skin.abc". Alternatively, pass as an array ["skin.abc", "skin.xyz"] to unlock sets
+ */
+ async unlock (path) {
+ let fullSet = path.indexOf(',') !== -1;
+ let isBackground = path.indexOf('background.') !== -1;
+
+ let cost;
+
+ if (isBackground) {
+ cost = fullSet ? 3.75 : 1.75; // (Backgrounds) 15G per set, 7G per individual
+ } else {
+ cost = fullSet ? 1.25 : 0.5; // (Hair, skin, etc) 5G per set, 2G per individual
+ }
+
+ let loginIncentives = [
+ 'background.blue',
+ 'background.green',
+ 'background.red',
+ 'background.purple',
+ 'background.yellow',
+ 'background.violet',
+ ];
+
+ if (loginIncentives.indexOf(path) === -1) {
+ if (fullSet) {
+ if (confirm(this.$t('purchaseFor', {cost: cost * 4})) !== true) return;
+ // @TODO: implement gem modal
+ // if (this.user.balance < cost) return $rootScope.openModal('buyGems');
+ } else if (!get(this.user, `purchased.${path}`)) {
+ if (confirm(this.$t('purchaseFor', {cost: cost * 4})) !== true) return;
+ // @TODO: implement gem modal
+ // if (this.user.balance < cost) return $rootScope.openModal('buyGems');
+ }
+ }
+
+ await axios.post(`/api/v4/user/unlock?path=${path}`);
+ try {
+ unlock(this.user, {
+ query: {
+ path,
+ },
+ });
+ this.backgroundUpdate = new Date();
+ } catch (e) {
+ alert(e.message);
+ }
+ },
+ async buy (item) {
+ const options = {
+ currency: 'gold',
+ key: item,
+ type: 'marketGear',
+ quantity: 1,
+ pinType: 'marketGear',
+ };
+ await axios.post(`/api/v4/user/buy/${item}`, options);
+ try {
+ buy(this.user, {
+ params: options,
+ });
+ this.backgroundUpdate = new Date();
+ } catch (e) {
+ alert(e.message);
+ }
+ },
+ },
+};
diff --git a/website/client/mixins/subPage.js b/website/client/mixins/subPage.js
new file mode 100644
index 0000000000..bdae59faee
--- /dev/null
+++ b/website/client/mixins/subPage.js
@@ -0,0 +1,12 @@
+export const subPageMixin = {
+ data () {
+ return {
+ activeSubPage: '',
+ };
+ },
+ methods: {
+ changeSubPage (page) {
+ this.activeSubPage = page;
+ },
+ },
+};
diff --git a/website/client/mixins/userState.js b/website/client/mixins/userState.js
new file mode 100644
index 0000000000..d52d6e2be2
--- /dev/null
+++ b/website/client/mixins/userState.js
@@ -0,0 +1,7 @@
+import { mapState } from 'client/libs/store';
+
+export const userStateMixin = {
+ computed: {
+ ...mapState({user: 'user.data'}),
+ },
+};
diff --git a/website/client/store/getters/tasks.js b/website/client/store/getters/tasks.js
index 2c453a0423..ff5d751428 100644
--- a/website/client/store/getters/tasks.js
+++ b/website/client/store/getters/tasks.js
@@ -16,7 +16,7 @@ export function getTagsFor (store) {
}
function getTaskColor (task) {
- if (task.type === 'reward') return 'purple';
+ if (task.type === 'reward' || task.byHabitica) return 'purple';
const value = task.value;
diff --git a/website/common/locales/en/defaultTasks.json b/website/common/locales/en/defaultTasks.json
index 7dcacb0b35..dca538c5fc 100644
--- a/website/common/locales/en/defaultTasks.json
+++ b/website/common/locales/en/defaultTasks.json
@@ -34,5 +34,51 @@
"defaultTag4": "School",
"defaultTag5": "Teams",
"defaultTag6": "Chores",
- "defaultTag7": "Creativity"
+ "defaultTag7": "Creativity",
+
+ "workHabitMail": "Process email",
+ "workDailyImportantTask": "Most important task >> Worked on today’s most important task",
+ "workDailyImportantTaskNotes": "Tap to specify your most important task",
+ "workTodoProject": "Work project >> Complete work project",
+ "workTodoProjectNotes": "Tap to specify the name of your current project + set a due date!",
+
+ "exerciseHabit": "10 min cardio >> + 10 minutes cardio",
+ "exerciseDailyText": "Stretching >> Daily workout routine",
+ "exerciseDailyNotes": "Tap to choose your schedule and specify exercises!",
+ "exerciseTodoText": "Set up workout schedule",
+ "exerciseTodoNotes": "Tap to add a checklist!",
+
+ "healthHabit": "Eat Health/Junk Food",
+ "healthDailyText": "Floss",
+ "healthDailyNotes": "Tap to make any changes!",
+
+ "healthTodoText": "Schedule check-up >> Brainstorm a healthy change",
+ "healthTodoNotes": "Tap to add checklists!",
+
+ "schoolHabit": "Study/Procrastinate",
+ "schoolDailyText": "Finish homework",
+ "schoolDailyNotes": "Tap to choose your homework schedule!",
+ "schoolTodoText": "Finish assignment for class",
+ "schoolTodoNotes": "Tap to name the assignment and choose a due date!]",
+
+ "selfCareHabit": "Take a short break",
+ "selfCareDailyText": "5 minutes of quiet breathing",
+ "selfCareDailyNotes": "Tap to choose your schedule!",
+ "selfCareTodoText": "Engage in a fun activity",
+ "selfCareTodoNotes": "Tap to specify what you plan to do!",
+
+ "choresHabit": "10 minutes cleaning",
+ "choresDailyText": "Wash dishes",
+ "choresDailyNotes": "Tap to choose your schedule!",
+ "choresTodoText": "Organize closet >> Organize clutter",
+ "choresTodoNotes": "Tap to specify the cluttered area!",
+
+ "creativityHabit": "Study a master of the craft >> + Practiced a new creative technique",
+ "creativityDailyText": "Work on creative project",
+ "creativityDailyNotes": "Tap to specify the name of your current project + set the schedule!",
+ "creativityTodoText": "Finish creative project",
+ "creativityTodoNotes": "Tap to specify the name of your project",
+
+ "defaultHabitText": "Click here to edit this into a bad habit you'd like to quit",
+ "defaultHabitNotes": "Or delete from the edit screen"
}
diff --git a/website/common/locales/en/generic.json b/website/common/locales/en/generic.json
index c5744715b6..e5b7c3f485 100644
--- a/website/common/locales/en/generic.json
+++ b/website/common/locales/en/generic.json
@@ -7,6 +7,7 @@
"onward": "Onward!",
"done": "Done",
+ "finish": "Finish",
"gotIt": "Got it!",
"titleTasks": "Tasks",
diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js
index eedb5c8cd9..177fe2536b 100644
--- a/website/common/script/content/index.js
+++ b/website/common/script/content/index.js
@@ -2,6 +2,7 @@ import defaults from 'lodash/defaults';
import each from 'lodash/each';
import moment from 'moment';
import t from './translation';
+import {tasksByCategory} from './tasks';
import {
CLASSES,
@@ -898,6 +899,7 @@ api.userDefaults = {
},
],
};
+api.tasksByCategory = tasksByCategory;
api.userDefaultsMobile = {
habits: [],
diff --git a/website/common/script/content/tasks.js b/website/common/script/content/tasks.js
new file mode 100644
index 0000000000..1b14a99fd0
--- /dev/null
+++ b/website/common/script/content/tasks.js
@@ -0,0 +1,158 @@
+import t from './translation';
+
+export const tasksByCategory = {
+ work: [
+ {
+ type: 'habit',
+ text: t('workHabitMail'),
+ up: true,
+ down: false,
+ },
+ {
+ type: 'daily',
+ text: t('workDailyImportantTask'),
+ notes: t('workDailyImportantTaskNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('workTodoProject'),
+ notes: t('workTodoProjectNotes'),
+ },
+ ],
+ exercise: [
+ {
+ type: 'habit',
+ text: t('exerciseHabit'),
+ up: true,
+ down: false,
+ },
+ {
+ type: 'daily',
+ text: t('exerciseDailyText'),
+ notes: t('exerciseDailyNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('exerciseTodoText'),
+ notes: t('exerciseTodoNotes'),
+ },
+ ],
+ health_wellness: [ // eslint-disable-line
+ {
+ type: 'habit',
+ text: t('healthHabit'),
+ up: true,
+ down: true,
+ },
+ {
+ type: 'daily',
+ text: t('healthDailyText'),
+ notes: t('healthDailyNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('healthTodoText'),
+ notes: t('healthTodoNotes'),
+ },
+ ],
+ school: [
+ {
+ type: 'habit',
+ text: t('schoolHabit'),
+ up: true,
+ down: true,
+ },
+ {
+ type: 'daily',
+ text: t('schoolDailyText'),
+ notes: t('schoolDailyNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('schoolTodoText'),
+ notes: t('schoolTodoNotes'),
+ },
+ ],
+ self_care: [ // eslint-disable-line
+ {
+ type: 'habit',
+ text: t('selfCareHabit'),
+ up: true,
+ down: false,
+ },
+ {
+ type: 'daily',
+ text: t('selfCareDailyText'),
+ notes: t('selfCareDailyNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('selfCareTodoText'),
+ notes: t('selfCareTodoNotes'),
+ },
+ ],
+ chores: [
+ {
+ type: 'habit',
+ text: t('choresHabit'),
+ up: true,
+ down: false,
+ },
+ {
+ type: 'daily',
+ text: t('choresDailyText'),
+ notes: t('choresDailyNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('choresTodoText'),
+ notes: t('choresTodoNotes'),
+ },
+ ],
+ creativity: [
+ {
+ type: 'habit',
+ text: t('creativityHabit'),
+ up: true,
+ down: false,
+ },
+ {
+ type: 'daily',
+ text: t('creativityDailyText'),
+ notes: t('creativityDailyNotes'),
+ },
+ {
+ type: 'todo',
+ text: t('creativityTodoText'),
+ notes: t('creativityTodoNotes'),
+ },
+ ],
+ defaults: [
+ {
+ type: 'habit',
+ text: t('defaultHabit4Text'),
+ notes: t('defaultHabit4Notes'),
+ up: true,
+ down: false,
+ },
+ {
+ type: 'habit',
+ text: t('defaultHabitText'),
+ notes: t('defaultHabitNotes'),
+ up: false,
+ down: true,
+ },
+ {
+ type: 'todo',
+ text: t('defaultTodo1Text'),
+ notes: t('defaultTodoNotes'),
+ byHabitica: true,
+ },
+ {
+ type: 'reward',
+ text: t('defaultReward2Text'),
+ notes: t('defaultReward2Notes'),
+ value: 10,
+ },
+ ],
+};
diff --git a/website/server/models/task.js b/website/server/models/task.js
index aa64de38e8..24c73da6fd 100644
--- a/website/server/models/task.js
+++ b/website/server/models/task.js
@@ -128,6 +128,8 @@ export let TaskSchema = new Schema({
},
reminders: [reminderSchema],
+
+ byHabitica: {$type: Boolean, default: false}, // Flag of Tasks that were created by Habitica
}, _.defaults({
minimize: false, // So empty objects are returned
strict: true,