old client structure

This commit is contained in:
Matteo Pagliazzi
2019-10-01 11:44:38 +02:00
parent 5b61f65711
commit b44fbdcb14
906 changed files with 44 additions and 44 deletions
@@ -0,0 +1,93 @@
<template lang="pug">
.row.standard-page(v-if='groupIsSubscribed && isLeader')
.col-12.col-md-6.offset-md-3
table.table.alert.alert-info
tr(v-if='group.purchased.plan.dateTerminated')
td.alert.alert-warning
span.noninteractive-button.btn-danger {{ $t('canceledGroupPlan') }}
i.glyphicon.glyphicon-time {{ $t('groupPlanCanceled') }}
strong {{dateTerminated}}
tr(v-if='!group.purchased.plan.dateTerminated')
td
h3 {{ $t('paymentDetails') }}
p(v-if='group.purchased.plan.planId') {{ $t('groupSubscriptionPrice') }}
tr(v-if='group.purchased.plan.extraMonths')
td
span.glyphicon.glyphicon-credit-card
| {{ $t('purchasedGroupPlanPlanExtraMonths', purchasedGroupPlanPlanExtraMonths) }}
tr(v-if='group.purchased.plan.consecutive.count || group.purchased.plan.consecutive.offset')
td
span.glyphicon.glyphicon-forward
| {{ $t('consecutiveSubscription') }}
ul.list-unstyled
li {{ $t('consecutiveMonths') }} {{group.purchased.plan.consecutive.count + group.purchased.plan.consecutive.offset}}
li {{ $t('gemCapExtra') }} {{group.purchased.plan.consecutive.gemCapExtra}}
li {{ $t('mysticHourglasses') }} {{group.purchased.plan.consecutive.trinkets}}
.col-12.col-md-6.offset-md-3
button.btn.btn-success(class='btn-success', v-if='group.purchased.plan.dateTerminated', @click='upgradeGroup()')
| {{ $t('upgrade') }}
.btn.btn-primary(v-if='!group.purchased.plan.dateTerminated && group.purchased.plan.paymentMethod === "Stripe"',
@click='showStripeEdit({groupId: group.id})') {{ $t('subUpdateCard') }}
.btn.btn-sm.btn-danger(v-if='!group.purchased.plan.dateTerminated',
@click='cancelSubscriptionConfirm({group: group})') {{ $t('cancelGroupSub') }}
</template>
<script>
import moment from 'moment';
import { mapState } from 'client/libs/store';
import paymentsMixin from 'client/mixins/payments';
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
export default {
mixins: [paymentsMixin],
props: ['groupId'],
data () {
return {
group: {},
};
},
async mounted () {
await this.loadGroup();
let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
if (appState) {
appState = JSON.parse(appState);
if (appState.groupPlanCanceled) {
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
this.$root.$emit('habitica:subscription-canceled', {
dateTerminated: this.dateTerminated,
isGroup: true,
});
}
}
},
computed: {
...mapState({user: 'user.data'}),
isLeader () {
return this.user._id === this.group.leader._id;
},
groupIsSubscribed () {
return this.group.purchased && this.group.purchased.plan && this.group.purchased.plan.customerId;
},
dateTerminated () {
if (!this.user.preferences || !this.user.preferences.dateFormat) return moment(this.group.purchased.plan.dateTerminated);
return moment(this.group.purchased.plan.dateTerminated).format(this.user.preferences.dateFormat.toUpperCase());
},
purchasedGroupPlanPlanExtraMonths () {
return {
months: parseFloat(this.group.purchased.plan.extraMonths).toFixed(2),
};
},
},
methods: {
async loadGroup () {
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
this.group = Object.assign({}, group);
},
upgradeGroup () {
this.$store.state.upgradingGroup = this.group;
this.$router.push('/group-plans');
},
},
};
</script>
@@ -0,0 +1,156 @@
<template lang="pug">
.create-group-modal-pages
.col-12(v-if='activePage === PAGES.CREATE_GROUP')
h2 {{ $t('nameYourGroup') }}
.form-group
label.control-label(for='new-group-name') {{ $t('name') }}
input.form-control#new-group-name.input-medium.option-content(required, type='text', :placeholder="$t('exampleGroupName')", v-model='newGroup.name')
.form-group
label(for='new-group-description') {{ $t('description') }}
textarea.form-control#new-group-description.option-content(cols='3', :placeholder="$t('exampleGroupDesc')", v-model='newGroup.description')
.form-group.text-left(v-if='newGroup.type === "guild"')
.custom-control.custom-radio
input.custom-control-input(type='radio', name='new-group-privacy', value='private', v-model='newGroup.privacy')
label.custom-control-label {{ $t('thisGroupInviteOnly') }}
.form-group.text-left
.custom-control.custom-checkbox
input.custom-control-input(type='checkbox', v-model='newGroup.leaderOnly.challenges' id='create-group-leaderOnlyChallenges-checkbox')
label.custom-control-label(for='create-group-leaderOnlyChallenges-checkbox') {{ $t('leaderOnlyChallenges') }}
.form-group(v-if='newGroup.type === "party"')
button.btn.btn-secondary.form-control(@click='createGroup()', :value="$t('createGroupPlan')")
.form-group
button.btn.btn-primary.btn-lg.btn-block(@click="createGroup()", :disabled="!newGroupIsReady") {{ $t('createGroupPlan') }}
.col-12(v-if='activePage === PAGES.PAY')
h2 {{ $t('choosePaymentMethod') }}
.payments-column
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
| {{ $t('card') }}
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
</template>
<style lang="scss" scoped>
h2 {
font-family: 'Varela Round', sans-serif;
font-weight: normal;
font-size: 29px;
color: #34313a;
margin-top: 1em;
}
.box {
border-radius: 2px;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
padding: 2em;
text-align: center;
vertical-align: bottom;
height: 100px;
width: 306px;
margin: 0 auto;
margin-bottom: 1em;
}
.box .svg-icon {
margin: 0 auto;
}
.form-group {
text-align: left;
font-weight: bold;
}
.box:hover {
cursor: pointer;
opacity: 0.7;
}
.btn-block {
margin-bottom: 1em;
}
</style>
<script>
import * as Analytics from 'client/libs/analytics';
import { mapState } from 'client/libs/store';
import paymentsMixin from '../../mixins/payments';
import amazonButton from 'client/components/payments/amazonButton';
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
export default {
mixins: [paymentsMixin],
components: {
amazonButton,
},
data () {
return {
amazonPayments: {},
icons: Object.freeze({
creditCardIcon,
}),
PAGES: {
CREATE_GROUP: 'create-group',
UPGRADE_GROUP: 'upgrade-group',
PAY: 'pay',
},
PAYMENTS: {
AMAZON: 'amazon',
STRIPE: 'stripe',
},
activePage: 'create-group',
newGroup: {
type: 'guild',
privacy: 'private',
name: '',
leaderOnly: {
challenges: false,
},
},
};
},
computed: {
...mapState({user: 'user.data'}),
newGroupIsReady () {
return Boolean(this.newGroup.name);
},
},
methods: {
changePage (page) {
Analytics.track({
hitType: 'event',
eventCategory: 'group-plans-static',
eventAction: 'view',
eventLabel: page,
});
this.activePage = page;
window.scrollTo(0, 0);
},
createGroup () {
this.changePage(this.PAGES.PAY);
},
pay (paymentMethod) {
const subscriptionKey = 'group_monthly';
let paymentData = {
subscription: subscriptionKey,
coupon: null,
};
if (this.upgradingGroup && this.upgradingGroup._id) {
paymentData.groupId = this.upgradingGroup._id;
paymentData.group = this.upgradingGroup;
} else {
paymentData.groupToCreate = this.newGroup;
}
this.paymentMethod = paymentMethod;
if (this.paymentMethod === this.PAYMENTS.STRIPE) {
this.showStripe(paymentData);
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
paymentData.type = 'subscription';
return paymentData;
}
},
},
};
</script>
@@ -0,0 +1,235 @@
<template lang="pug">
b-modal#group-plan-overview(title="Empty", size='lg', hide-footer=true, @shown='shown()')
.header-wrap.text-center(slot="modal-header")
h2(v-once) {{ $t('gettingStarted') }}
p(v-once) {{ $t('congratsOnGroupPlan') }}
.row
.col-12
.card(:class='{expanded: expandedQuestions.question1}')
.question-head
.q Q.
.title {{ $t('whatsIncludedGroup') }}
.arrow.float-right(@click='toggle("question1")')
.svg-icon(v-html="icons.upIcon", v-if='expandedQuestions.question1')
.svg-icon(v-html="icons.downIcon", v-else)
.question-body(v-if='expandedQuestions.question1')
p {{ $t('whatsIncludedGroupDesc') }}
.col-12
.card(:class='{expanded: expandedQuestions.question2}')
.question-head
.q Q.
.title {{ $t('howDoesBillingWork') }}
.arrow.float-right(@click='toggle("question2")')
.svg-icon(v-html="icons.upIcon", v-if='expandedQuestions.question2')
.svg-icon(v-html="icons.downIcon", v-else)
.question-body(v-if='expandedQuestions.question2')
p {{ $t('howDoesBillingWorkDesc') }}
.col-12
.card(:class='{expanded: expandedQuestions.question3}')
.question-head
.q Q.
.title {{ $t('howToAssignTask') }}
.arrow.float-right(@click='toggle("question3")')
.svg-icon(v-html="icons.upIcon", v-if='expandedQuestions.question3')
.svg-icon(v-html="icons.downIcon", v-else)
.question-body(v-if='expandedQuestions.question3')
p {{ $t('howToAssignTaskDesc') }}
.assign-tasks.image-example
.col-12
.card(:class='{expanded: expandedQuestions.question4}')
.question-head
.q Q.
.title {{ $t('howToRequireApproval') }}
.arrow.float-right(@click='toggle("question4")')
.svg-icon(v-html="icons.upIcon", v-if='expandedQuestions.question4')
.svg-icon(v-html="icons.downIcon", v-else)
.question-body(v-if='expandedQuestions.question4')
p {{ $t('howToRequireApprovalDesc') }}
.requires-approval.image-example
p {{ $t('howToRequireApprovalDesc2') }}
.approval-requested.image-example
.col-12
.card(:class='{expanded: expandedQuestions.question5}')
.question-head
.q Q.
.title {{ $t('whatIsGroupManager') }}
.arrow.float-right(@click='toggle("question5")')
.svg-icon(v-html="icons.upIcon", v-if='expandedQuestions.question5')
.svg-icon(v-html="icons.downIcon", v-else)
.question-body(v-if='expandedQuestions.question5')
p {{ $t('whatIsGroupManagerDesc') }}
.promote-leader.image-example
.col-12.text-center
button.btn.btn-primary.close-button(@click='close()') {{ $t('goToTaskBoard') }}
</template>
<style>
#group-plan-overview___BV_modal_header_ {
border-bottom: none;
}
</style>
<style lang="scss" scoped>
@import url('https://fonts.googleapis.com/css?family=Varela+Round');
.header-wrap {
padding-left: 4em;
padding-right: 4em;
h2 {
font-size: 32px;
font-weight: bold;
margin-top: 1em;
}
p {
color: #878190;
font-size: 16px;
}
}
.row {
margin-bottom: 2em;
.col-12 {
margin-bottom: .5em;
}
}
.card.expanded {
padding-bottom: 1em;
.title {
color: #4f2a93;
}
}
.card {
min-height: 60px;
border-radius: 4px;
background-color: #ffffff;
border: none;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
.question-head {
.q {
font-family: 'Varela Round', sans-serif;
font-size: 20px;
color: #a5a1ac;
margin: 1em;
}
.title {
font-weight: normal;
}
div {
display: inline-block;
}
.arrow {
margin: 1em;
padding-top: .9em;
.svg-icon {
width: 26px;
height: 16px;
}
}
.arrow:hover {
cursor: pointer;
}
}
.question-body {
padding-left: 4.4em;
padding-right: 4em;
p {
color: #4e4a57;
}
}
}
.image-example {
background-repeat: no-repeat;
margin: 0 auto;
background-position: center;
background-size: contain;
}
.assign-tasks {
background-image: url('~assets/images/group-plans/assign-task@3x.png');
width: 400px;
height: 150px;
}
.requires-approval {
background-image: url('~assets/images/group-plans/requires-approval@3x.png');
width: 402px;
height: 20px;
margin-bottom: 1em;
}
.approval-requested {
background-image: url('~assets/images/group-plans/approval-requested@3x.png');
width: 471px;
height: 204px;
}
.promote-leader {
background-image: url('~assets/images/group-plans/promote-leader@3x.png');
width: 423px;
height: 185px;
}
.close-button {
margin-top: 1em;
}
</style>
<script>
import * as Analytics from 'client/libs/analytics';
import { mapState } from 'client/libs/store';
import upIcon from 'assets/svg/up.svg';
import downIcon from 'assets/svg/down.svg';
export default {
data () {
return {
icons: Object.freeze({
upIcon,
downIcon,
}),
expandedQuestions: {
question1: false,
question2: false,
question3: false,
question4: false,
question5: false,
},
};
},
computed: {
...mapState({user: 'user.data'}),
},
methods: {
shown () {
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'viewed-group-plan-overview',
});
},
toggle (question) {
this.expandedQuestions[question] = !this.expandedQuestions[question];
},
close () {
this.$root.$emit('bv::hide::modal', 'group-plan-overview');
},
},
};
</script>
@@ -0,0 +1,48 @@
<template lang="pug">
.row
group-form-modal
secondary-menu.col-12
router-link.nav-link(:to="{name: 'groupPlanDetailTaskInformation', params: {groupId}}",
exact, :class="{'active': $route.name === 'groupPlanDetailTaskInformation'}") {{ $t('groupTaskBoard') }}
router-link.nav-link(:to="{name: 'groupPlanDetailInformation', params: {groupId}}",
exact, :class="{'active': $route.name === 'groupPlanDetailInformation'}") {{ $t('groupInformation') }}
router-link.nav-link(
v-if='isLeader',
:to="{name: 'groupPlanBilling', params: {groupId}}",
exact,
:class="{'active': $route.name === 'groupPlanBilling'}") {{ $t('groupBilling') }}
.col-12
router-view
</template>
<script>
import groupFormModal from 'client/components/groups/groupFormModal';
import SecondaryMenu from 'client/components/secondaryMenu';
import { mapState } from 'client/libs/store';
export default {
props: ['groupId'],
components: {
SecondaryMenu,
groupFormModal,
},
computed: {
...mapState({
user: 'user.data',
groupPlans: 'groupPlans',
}),
currentGroup () {
let groupFound = this.groupPlans.find(group => {
return group._id === this.groupId;
});
return groupFound;
},
isLeader () {
if (!this.currentGroup) return false;
return this.currentGroup.leader === this.user._id;
},
},
};
</script>
@@ -0,0 +1,312 @@
<template lang="pug">
.standard-page
group-plan-overview-modal
task-modal(
:task="workingTask",
:purpose="taskFormPurpose",
@cancel="cancelTaskModal()",
ref="taskModal",
:groupId="groupId",
v-on:taskCreated='taskCreated',
v-on:taskEdited='taskEdited',
v-on:taskDestroyed='taskDestroyed'
)
.row.tasks-navigation
.col-12.col-md-4
h1 {{ $t('groupTasksTitle') }}
// @TODO: Abstract to component!
.col-12.col-md-4
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
.create-task-area.d-flex(v-if='canCreateTasks')
transition(name="slide-tasks-btns")
.d-flex(v-if="openCreateBtn")
.create-task-btn.diamond-btn(
v-for="type in columns",
:key="type",
@click="createTask(type)",
v-b-tooltip.hover.bottom="$t(type)",
)
.svg-icon(v-html="icons[type]", :class='`icon-${type}`')
#create-task-btn.create-btn.diamond-btn.btn.btn-success(
@click="openCreateBtn = !openCreateBtn",
:class="{open: openCreateBtn}",
)
.svg-icon(v-html="icons.positive")
b-tooltip(target="create-task-btn", placement="bottom", v-if="!openCreateBtn") {{ $t('addTaskToGroupPlan') }}
.row
task-column.col-12.col-md-3(
v-for="column in columns",
:type="column",
:key="column",
:taskListOverride='tasksByType[column]',
:showOptions="showOptions"
v-on:editTask="editTask",
v-on:loadGroupCompletedTodos="loadGroupCompletedTodos",
v-on:taskDestroyed="taskDestroyed",
:group='group',
:searchText="searchText")
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/create-task.scss';
.tasks-navigation {
margin-bottom: 40px;
}
.positive {
display: inline-block;
width: 10px;
color: $green-500;
margin-right: 8px;
padding-top: 6px;
}
</style>
<script>
import taskDefaults from 'common/script/libs/taskDefaults';
import TaskColumn from '../tasks/column';
import TaskModal from '../tasks/taskModal';
import GroupPlanOverviewModal from './groupPlanOverviewModal';
import positiveIcon from 'assets/svg/positive.svg';
import filterIcon from 'assets/svg/filter.svg';
import deleteIcon from 'assets/svg/delete.svg';
import habitIcon from 'assets/svg/habit.svg';
import dailyIcon from 'assets/svg/daily.svg';
import todoIcon from 'assets/svg/todo.svg';
import rewardIcon from 'assets/svg/reward.svg';
import Vue from 'vue';
import cloneDeep from 'lodash/cloneDeep';
import findIndex from 'lodash/findIndex';
import groupBy from 'lodash/groupBy';
import { mapState } from 'client/libs/store';
export default {
props: ['groupId'],
components: {
TaskColumn,
TaskModal,
GroupPlanOverviewModal,
},
data () {
return {
openCreateBtn: false,
searchId: '',
columns: ['habit', 'daily', 'todo', 'reward'],
tasksByType: {
habit: [],
daily: [],
todo: [],
reward: [],
},
editingTask: {},
creatingTask: {},
workingTask: {},
taskFormPurpose: 'create',
// @TODO: Separate component?
searchText: '',
selectedTags: [],
temporarilySelectedTags: [],
isFilterPanelOpen: false,
icons: Object.freeze({
positive: positiveIcon,
filter: filterIcon,
destroy: deleteIcon,
habit: habitIcon,
daily: dailyIcon,
todo: todoIcon,
reward: rewardIcon,
}),
editingTags: false,
group: {},
};
},
watch: {
// call again the method if the route changes (when this route is already active)
$route: 'load',
},
beforeRouteUpdate (to, from, next) {
this.$set(this, 'searchId', to.params.groupId);
next();
},
mounted () {
if (!this.searchId) this.searchId = this.groupId;
this.load();
if (this.$route.query.showGroupOverview) {
this.$root.$emit('bv::show::modal', 'group-plan-overview');
}
},
computed: {
...mapState({user: 'user.data'}),
tagsByType () {
const userTags = this.user.tags;
const tagsByType = {
challenges: {
key: 'challenges',
tags: [],
},
groups: {
key: 'groups',
tags: [],
},
user: {
key: 'tags',
tags: [],
},
};
userTags.forEach(t => {
if (t.group) {
tagsByType.groups.tags.push(t);
} else if (t.challenge) {
tagsByType.challenges.tags.push(t);
} else {
tagsByType.user.tags.push(t);
}
});
return tagsByType;
},
canCreateTasks () {
if (!this.group) return false;
return this.group.leader && this.group.leader._id === this.user._id || this.group.managers && Boolean(this.group.managers[this.user._id]);
},
showOptions () {
return this.canCreateTasks;
},
},
methods: {
async load () {
this.tasksByType = {
habit: [],
daily: [],
todo: [],
reward: [],
};
this.group = await this.$store.dispatch('guilds:getGroup', {
groupId: this.searchId,
});
let members = await this.$store.dispatch('members:getGroupMembers', {groupId: this.searchId});
this.group.members = members;
let tasks = await this.$store.dispatch('tasks:getGroupTasks', {
groupId: this.searchId,
});
let groupedApprovals = await this.loadApprovals();
tasks.forEach((task) => {
if (groupedApprovals[task._id] && groupedApprovals[task._id].length > 0) task.approvals = groupedApprovals[task._id];
this.tasksByType[task.type].push(task);
});
},
async loadApprovals () {
let approvalRequests = await this.$store.dispatch('tasks:getGroupApprovals', {
groupId: this.searchId,
});
return groupBy(approvalRequests, 'group.taskId');
},
editTask (task) {
this.taskFormPurpose = 'edit';
this.editingTask = cloneDeep(task);
this.workingTask = this.editingTask;
// Necessary otherwise the first time the modal is not rendered
Vue.nextTick(() => {
this.$root.$emit('bv::show::modal', 'task-modal');
});
},
async loadGroupCompletedTodos () {
const completedTodos = await this.$store.dispatch('tasks:getCompletedGroupTasks', {
groupId: this.searchId,
});
completedTodos.forEach((task) => {
const existingTaskIndex = findIndex(this.tasksByType.todo, (todo) => {
return todo._id === task._id;
});
if (existingTaskIndex === -1) {
this.tasksByType.todo.push(task);
}
});
},
createTask (type) {
this.openCreateBtn = false;
this.taskFormPurpose = 'create';
this.creatingTask = taskDefaults({type, text: ''}, this.user);
this.workingTask = this.creatingTask;
// Necessary otherwise the first time the modal is not rendered
Vue.nextTick(() => {
this.$root.$emit('bv::show::modal', 'task-modal');
});
},
taskCreated (task) {
task.group.id = this.group._id;
this.tasksByType[task.type].push(task);
},
taskEdited (task) {
let index = findIndex(this.tasksByType[task.type], (taskItem) => {
return taskItem._id === task._id;
});
this.tasksByType[task.type].splice(index, 1, task);
},
taskDestroyed (task) {
let index = findIndex(this.tasksByType[task.type], (taskItem) => {
return taskItem._id === task._id;
});
this.tasksByType[task.type].splice(index, 1);
},
cancelTaskModal () {
this.editingTask = null;
this.creatingTask = null;
this.workingTask = {};
},
toggleFilterPanel () {
if (this.isFilterPanelOpen === true) {
this.closeFilterPanel();
} else {
this.openFilterPanel();
}
},
openFilterPanel () {
this.isFilterPanelOpen = true;
this.temporarilySelectedTags = this.selectedTags.slice();
},
closeFilterPanel () {
this.temporarilySelectedTags = [];
this.isFilterPanelOpen = false;
},
resetFilters () {
this.selectedTags = [];
this.closeFilterPanel();
},
applyFilters () {
const temporarilySelectedTags = this.temporarilySelectedTags;
this.selectedTags = temporarilySelectedTags.slice();
this.closeFilterPanel();
},
toggleTag (tag) {
const temporarilySelectedTags = this.temporarilySelectedTags;
const tagI = temporarilySelectedTags.indexOf(tag.id);
if (tagI === -1) {
temporarilySelectedTags.push(tag.id);
} else {
temporarilySelectedTags.splice(tagI, 1);
}
},
isTagSelected (tag) {
const tagId = tag.id;
if (this.temporarilySelectedTags.indexOf(tagId) !== -1) return true;
return false;
},
},
};
</script>