feat(notifications): alert user to achievements

This commit is contained in:
Sabe Jones
2019-06-11 13:06:00 -05:00
parent 103945f7c5
commit 81333a3074
14 changed files with 346 additions and 6 deletions

View File

@@ -0,0 +1,42 @@
<template lang="pug">
b-modal#generic-achievement(:title='data.message', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p(v-html='data.modalText')
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
props: ['data'],
computed: {
...mapState({user: 'user.data'}),
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'generic-achievement');
},
},
};
</script>

View File

@@ -0,0 +1,46 @@
<template lang="pug">
b-modal#just-add-water(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementJustAddWaterModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementJustAddWater')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'just-add-water');
},
},
};
</script>

View File

@@ -0,0 +1,46 @@
<template lang="pug">
b-modal#lost-masterclasser(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementLostMasterclasserModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementLostMasterclasser')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'lost-masterclasser');
},
},
};
</script>

View File

@@ -0,0 +1,46 @@
<template lang="pug">
b-modal#mind-over-matter(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementMindOverMatterModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementMindOverMatter')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'mind-over-matter');
},
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementJustAddWater')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'just-add-water');
},
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementLostMasterclasser')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'lost-masterclasser');
},
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementMindOverMatter')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'mind-over-matter');
},
},
};
</script>

View File

@@ -95,6 +95,9 @@ import NEW_INBOX_MESSAGE from './notifications/newInboxMessage';
import NEW_CHAT_MESSAGE from './notifications/newChatMessage'; import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
import WORLD_BOSS from './notifications/worldBoss'; import WORLD_BOSS from './notifications/worldBoss';
import VERIFY_USERNAME from './notifications/verifyUsername'; 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';
export default { export default {
components: { components: {
@@ -106,6 +109,7 @@ export default {
QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED, GROUP_TASK_ASSIGNED, QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED, GROUP_TASK_ASSIGNED,
UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED, UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED,
NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE, NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE,
ACHIEVEMENT_JUST_ADD_WATER, ACHIEVEMENT_LOST_MASTERCLASSER, ACHIEVEMENT_MIND_OVER_MATTER,
WorldBoss: WORLD_BOSS, WorldBoss: WORLD_BOSS,
VERIFY_USERNAME, VERIFY_USERNAME,
}, },
@@ -130,6 +134,7 @@ export default {
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED', 'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED', 'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS', 'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
'VERIFY_USERNAME', 'VERIFY_USERNAME',
], ],
}; };

View File

@@ -26,6 +26,10 @@ div
quest-completed quest-completed
quest-invitation quest-invitation
verify-username verify-username
generic-achievement(:data='notificationData')
just-add-water
lost-masterclasser
mind-over-matter
</template> </template>
<style lang='scss'> <style lang='scss'>
@@ -118,6 +122,10 @@ import rebirth from './achievements/rebirth';
import streak from './achievements/streak'; import streak from './achievements/streak';
import ultimateGear from './achievements/ultimateGear'; import ultimateGear from './achievements/ultimateGear';
import wonChallenge from './achievements/wonChallenge'; import wonChallenge from './achievements/wonChallenge';
import genericAchievement from './achievements/genericAchievement';
import justAddWater from './achievements/justAddWater';
import lostMasterclasser from './achievements/lostMasterclasser';
import mindOverMatter from './achievements/mindOverMatter';
import loginIncentives from './achievements/login-incentives'; import loginIncentives from './achievements/login-incentives';
import verifyUsername from './settings/verifyUsername'; import verifyUsername from './settings/verifyUsername';
@@ -147,6 +155,16 @@ const NOTIFICATIONS = {
label: ($t) => $t('modalContribAchievement'), label: ($t) => $t('modalContribAchievement'),
modalId: 'contributor', modalId: 'contributor',
}, },
ACHIEVEMENT_ALL_YOUR_BASE: {
achievement: true,
label: ($t) => `${$t('achievement')}: ${$t('achievementAllYourBase')}`,
modalId: 'generic-achievement',
},
ACHIEVEMENT_BACK_TO_BASICS: {
achievement: true,
label: ($t) => `${$t('achievement')}: ${$t('achievementBackToBasics')}`,
modalId: 'generic-achievement',
},
}; };
export default { export default {
@@ -176,6 +194,10 @@ export default {
contributor, contributor,
loginIncentives, loginIncentives,
verifyUsername, verifyUsername,
genericAchievement,
lostMasterclasser,
mindOverMatter,
justAddWater,
}, },
data () { data () {
// Levels that already display modals and should not trigger generic Level Up // Levels that already display modals and should not trigger generic Level Up
@@ -197,7 +219,8 @@ export default {
'GUILD_PROMPT', 'DROPS_ENABLED', 'REBIRTH_ENABLED', 'WON_CHALLENGE', 'STREAK_ACHIEVEMENT', 'GUILD_PROMPT', 'DROPS_ENABLED', 'REBIRTH_ENABLED', 'WON_CHALLENGE', 'STREAK_ACHIEVEMENT',
'ULTIMATE_GEAR_ACHIEVEMENT', 'REBIRTH_ACHIEVEMENT', 'GUILD_JOINED_ACHIEVEMENT', 'ULTIMATE_GEAR_ACHIEVEMENT', 'REBIRTH_ACHIEVEMENT', 'GUILD_JOINED_ACHIEVEMENT',
'CHALLENGE_JOINED_ACHIEVEMENT', 'INVITED_FRIEND_ACHIEVEMENT', 'NEW_CONTRIBUTOR_LEVEL', 'CHALLENGE_JOINED_ACHIEVEMENT', 'INVITED_FRIEND_ACHIEVEMENT', 'NEW_CONTRIBUTOR_LEVEL',
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'ACHIEVEMENT_ALL_YOUR_BASE', 'ACHIEVEMENT_BACK_TO_BASICS',
'GENERIC_ACHIEVEMENT',
].forEach(type => { ].forEach(type => {
handledNotifications[type] = true; handledNotifications[type] = true;
}); });
@@ -342,8 +365,8 @@ export default {
this.playSound('Death'); this.playSound('Death');
this.$root.$emit('bv::show::modal', 'death'); this.$root.$emit('bv::show::modal', 'death');
}, },
showNotificationWithModal (type, forceToModal) { showNotificationWithModal (notification, forceToModal) {
const config = NOTIFICATIONS[type]; const config = NOTIFICATIONS[notification.type];
if (!config) { if (!config) {
return; return;
@@ -355,6 +378,10 @@ export default {
this.playSound(config.sound); this.playSound(config.sound);
} }
if (notification.data) {
this.notificationData = notification.data;
}
if (forceToModal) { if (forceToModal) {
this.$root.$emit('bv::show::modal', config.modalId); this.$root.$emit('bv::show::modal', config.modalId);
} else { } else {
@@ -566,7 +593,10 @@ export default {
case 'CHALLENGE_JOINED_ACHIEVEMENT': case 'CHALLENGE_JOINED_ACHIEVEMENT':
case 'INVITED_FRIEND_ACHIEVEMENT': case 'INVITED_FRIEND_ACHIEVEMENT':
case 'NEW_CONTRIBUTOR_LEVEL': case 'NEW_CONTRIBUTOR_LEVEL':
this.showNotificationWithModal(notification.type); case 'ACHIEVEMENT_ALL_YOUR_BASE':
case 'ACHIEVEMENT_BACK_TO_BASICS':
case 'GENERIC_ACHIEVEMENT':
this.showNotificationWithModal(notification);
break; break;
case 'CRON': case 'CRON':
if (notification.data) { if (notification.data) {

View File

@@ -6,12 +6,17 @@
"reachedLevel": "You Reached Level <%= level %>", "reachedLevel": "You Reached Level <%= level %>",
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series", "achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!", "achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
"achievementLostMasterclasserModalText": "You completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
"achievementMindOverMatter": "Mind Over Matter", "achievementMindOverMatter": "Mind Over Matter",
"achievementMindOverMatterText": "Has completed Rock, Slime, and Yarn pet quests.", "achievementMindOverMatterText": "Has completed Rock, Slime, and Yarn pet quests.",
"achievementMindOverMatterModalText": "You completed the Rock, Slime, and Yarn pet quests!",
"achievementJustAddWater": "Just Add Water", "achievementJustAddWater": "Just Add Water",
"achievementJustAddWaterText": "Has completed Octopus, Seahorse, Cuttlefish, Whale, Turtle, Nudibranch, Sea Serpent, and Dolphin pet quests.", "achievementJustAddWaterText": "Has completed Octopus, Seahorse, Cuttlefish, Whale, Turtle, Nudibranch, Sea Serpent, and Dolphin pet quests.",
"achievementJustAddWaterModalText": "You completed the Octopus, Seahorse, Cuttlefish, Whale, Turtle, Nudibranch, Sea Serpent, and Dolphin pet quests!",
"achievementBackToBasics": "Back to Basics", "achievementBackToBasics": "Back to Basics",
"achievementBackToBasicsText": "Has collected all Base Pets.", "achievementBackToBasicsText": "Has collected all Base Pets.",
"achievementBackToBasicsModalText": "You collected all the Base Pets!",
"achievementAllYourBase": "All Your Base", "achievementAllYourBase": "All Your Base",
"achievementAllYourBaseText": "Has tamed all Base Mounts." "achievementAllYourBaseText": "Has tamed all Base Mounts.",
"achievementAllYourBaseModalText": "You tamed all the Base Mounts!"
} }

View File

@@ -96,6 +96,13 @@ module.exports = function feed (user, req = {}) {
}); });
if (mountIndex === -1) { if (mountIndex === -1) {
user.achievements.allYourBase = true; user.achievements.allYourBase = true;
if (user.addNotification) {
user.addNotification('ACHIEVEMENT_ALL_YOUR_BASE', {
achievement: 'allYourBase',
message: `${i18n.t('modalAchievement')} ${i18n.t('achievementAllYourBase')}`,
modalText: i18n.t('achievementAllYourBaseModalText'),
});
}
} }
} }

View File

@@ -46,6 +46,13 @@ module.exports = function hatch (user, req = {}) {
}); });
if (petIndex === -1) { if (petIndex === -1) {
user.achievements.backToBasics = true; user.achievements.backToBasics = true;
if (user.addNotification) {
user.addNotification('ACHIEVEMENT_BACK_TO_BASICS', {
achievement: 'backToBasics',
message: `${i18n.t('modalAchievement')} ${i18n.t('achievementBackToBasics')}`,
modalText: i18n.t('achievementBackToBasicsModalText'),
});
}
} }
} }

View File

@@ -886,8 +886,20 @@ schema.methods.finishQuest = async function finishQuest (quest) {
} }
}); });
let questAchievementUpdate = {$set: {}}; let questAchievementUpdate = {$set: {}, $push: {}};
questAchievementUpdate.$set[`achievements.${achievement}`] = true; questAchievementUpdate.$set[`achievements.${achievement}`] = true;
const achievementTitleCase = `${achievement.slice(0, 1).toUpperCase()}${achievement.slice(1, achievement.length)}`;
const achievementSnakeCase = `ACHIEVEMENT_${_.snakeCase(achievement).toUpperCase()}`;
questAchievementUpdate.$push = {
notifications: new UserNotification({
type: achievementSnakeCase,
data: {
achievement,
message: `${shared.i18n.t('modalAchievement')} ${shared.i18n.t(`achievement${achievementTitleCase}`)}`,
modalText: shared.i18n.t(`achievement${achievementTitleCase}ModalText`),
},
}).toObject(),
};
promises.push(participants.map(userId => { promises.push(participants.map(userId => {
return _updateUserWithRetries(userId, questAchievementUpdate, null, questAchievementQuery); return _updateUserWithRetries(userId, questAchievementUpdate, null, questAchievementQuery);

View File

@@ -32,6 +32,10 @@ const NOTIFICATION_TYPES = [
'NEW_STUFF', 'NEW_STUFF',
'NEW_CHAT_MESSAGE', 'NEW_CHAT_MESSAGE',
'LEVELED_UP', 'LEVELED_UP',
'ACHIEVEMENT_ALL_YOUR_BASE',
'ACHIEVEMENT_BACK_TO_BASICS',
'ACHIEVEMENT_JUST_ADD_WATER',
'ACHIEVEMENT_MIND_OVER_MATTER',
]; ];
const Schema = mongoose.Schema; const Schema = mongoose.Schema;