mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
New client group plan (#8948)
* Added stripe payment for group plan * Began adding amazon * Added amazon payments for group * Added get group plans route * Added group plan nav * Added initial task page * Added create and edit group plans * Added initial approval header and footer * Added assignment and approved requirement * Added minor text fixes * Added inital approval flow * Added approval modal * Removed always true * Added more styles for filters * Added search * Added env vars * Fixed router issues * Added env to social login * Fixed merge conflict
This commit is contained in:
32
test/api/v3/integration/groups/GET-group-plans.test.js
Normal file
32
test/api/v3/integration/groups/GET-group-plans.test.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('GET /group-plans', () => {
|
||||||
|
let user;
|
||||||
|
let groupPlan;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
user = await generateUser({balance: 4});
|
||||||
|
groupPlan = await generateGroup(user,
|
||||||
|
{
|
||||||
|
name: 'public guild - is member',
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'public',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
purchased: {
|
||||||
|
plan: {
|
||||||
|
customerId: 'existings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns group plans for the user', async () => {
|
||||||
|
let groupPlans = await user.get('/group-plans');
|
||||||
|
|
||||||
|
expect(groupPlans[0]._id).to.eql(groupPlan._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,46 @@
|
|||||||
module.exports = {
|
const nconf = require('nconf');
|
||||||
|
const { join, resolve } = require('path');
|
||||||
|
|
||||||
|
const PATH_TO_CONFIG = join(resolve(__dirname, '../../config.json'));
|
||||||
|
let configFile = PATH_TO_CONFIG;
|
||||||
|
|
||||||
|
nconf
|
||||||
|
.argv()
|
||||||
|
.env()
|
||||||
|
.file('user', configFile);
|
||||||
|
|
||||||
|
nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production');
|
||||||
|
nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development');
|
||||||
|
nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test');
|
||||||
|
|
||||||
|
// @TODO: Check if we can import from client. Items like admin emails can be imported
|
||||||
|
// and that should be prefered
|
||||||
|
|
||||||
|
// To avoid stringifying more data then we need,
|
||||||
|
// items from `env` used on the client will have to be specified in this array
|
||||||
|
// @TODO: Do we need? const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
|
||||||
|
// 'FACEBOOK_KEY', 'GOOGLE_CLIENT_ID', 'NODE_ENV', 'BASE_URL', 'GA_ID',
|
||||||
|
// 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
|
||||||
|
// 'worldDmg', 'mods', 'IS_MOBILE', 'PUSHER:KEY', 'PUSHER:ENABLED'];
|
||||||
|
|
||||||
|
let env = {
|
||||||
NODE_ENV: '"production"',
|
NODE_ENV: '"production"',
|
||||||
|
// clientVars: CLIENT_VARS,
|
||||||
|
AMAZON_PAYMENTS: {
|
||||||
|
SELLER_ID: `"${nconf.get('AMAZON_PAYMENTS:SELLER_ID')}"`,
|
||||||
|
CLIENT_ID: `"${nconf.get('AMAZON_PAYMENTS:CLIENT_ID')}"`,
|
||||||
|
},
|
||||||
|
EMAILS: {
|
||||||
|
COMMUNITY_MANAGER_EMAIL: `"${nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL')}"`,
|
||||||
|
TECH_ASSISTANCE_EMAIL: `"${nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}"`,
|
||||||
|
PRESS_ENQUIRY_EMAIL: `"${nconf.get('EMAILS:PRESS_ENQUIRY_EMAIL')}"`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY PUSHER:KEY PUSHER:ENABLED'
|
||||||
|
.split(' ')
|
||||||
|
.forEach(key => {
|
||||||
|
env[key] = `"${nconf.get(key)}"`;
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = env;
|
||||||
|
|||||||
@@ -30,8 +30,16 @@ div
|
|||||||
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
|
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
|
||||||
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
|
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
|
||||||
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
|
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
|
||||||
router-link.nav-item.dropdown(tag="li", :to="{name: 'groupPlan'}", :class="{'active': $route.path.startsWith('/group-plan')}")
|
router-link.nav-item.dropdown(
|
||||||
|
v-if='groupPlans.length === 0',
|
||||||
|
tag="li",
|
||||||
|
:to="{name: 'groupPlan'}",
|
||||||
|
:class="{'active': $route.path.startsWith('/group-plan')}")
|
||||||
|
a.nav-link(v-once) {{ $t('group') }}
|
||||||
|
.nav-item.dropdown(v-if='groupPlans.length > 0', :class="{'active': $route.path.startsWith('/group-plans')}")
|
||||||
a.nav-link(v-once) {{ $t('group') }}
|
a.nav-link(v-once) {{ $t('group') }}
|
||||||
|
.dropdown-menu
|
||||||
|
router-link.dropdown-item(v-for='group in groupPlans', :key='group._id', :to="{name: 'groupPlanDetailTaskInformation', params: {groupId: group._id}}") {{ group.name }}
|
||||||
router-link.nav-item(tag="li", :to="{name: 'myChallenges'}", exact)
|
router-link.nav-item(tag="li", :to="{name: 'myChallenges'}", exact)
|
||||||
a.nav-link(v-once) {{ $t('challenges') }}
|
a.nav-link(v-once) {{ $t('challenges') }}
|
||||||
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}", :to="{name: 'faq'}")
|
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}", :to="{name: 'faq'}")
|
||||||
@@ -226,6 +234,7 @@ export default {
|
|||||||
user: userIcon,
|
user: userIcon,
|
||||||
logo,
|
logo,
|
||||||
}),
|
}),
|
||||||
|
groupPlans: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -234,6 +243,9 @@ export default {
|
|||||||
}),
|
}),
|
||||||
...mapState({user: 'user.data'}),
|
...mapState({user: 'user.data'}),
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
this.getUserGroupPlans();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
logout () {
|
logout () {
|
||||||
localStorage.removeItem('habit-mobile-settings');
|
localStorage.removeItem('habit-mobile-settings');
|
||||||
@@ -246,6 +258,9 @@ export default {
|
|||||||
this.$store.state.avatarEditorOptions.editingUser = true;
|
this.$store.state.avatarEditorOptions.editingUser = true;
|
||||||
this.$root.$emit('show::modal', 'avatar-modal');
|
this.$root.$emit('show::modal', 'avatar-modal');
|
||||||
},
|
},
|
||||||
|
async getUserGroupPlans () {
|
||||||
|
this.groupPlans = await this.$store.dispatch('guilds:getGroupPlans');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
hello.init({
|
hello.init({
|
||||||
facebook: '',
|
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line
|
||||||
// windows: WINDOWS_CLIENT_ID,
|
// windows: WINDOWS_CLIENT_ID,
|
||||||
google: '',
|
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
22
website/client/components/group-plans/index.vue
Normal file
22
website/client/components/group-plans/index.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.row
|
||||||
|
secondary-menu.col-12
|
||||||
|
router-link.nav-link(:to="{name: 'groupPlanDetailTaskInformation', params: {groupId}}",
|
||||||
|
exact, :class="{'active': $route.name === 'groupPlanDetailTaskInformation'}") Task Board
|
||||||
|
router-link.nav-link(:to="{name: 'groupPlanDetailInformation', params: {groupId}}",
|
||||||
|
exact, :class="{'active': $route.name === 'groupPlanDetailInformation'}") Group Information
|
||||||
|
|
||||||
|
.col-12
|
||||||
|
router-view
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SecondaryMenu from 'client/components/secondaryMenu';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['groupId'],
|
||||||
|
components: {
|
||||||
|
SecondaryMenu,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
411
website/client/components/group-plans/taskInformation.vue
Normal file
411
website/client/components/group-plans/taskInformation.vue
Normal file
@@ -0,0 +1,411 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.standard-page
|
||||||
|
.row.tasks-navigation
|
||||||
|
.col-4
|
||||||
|
h1 Group's Tasks
|
||||||
|
// @TODO: Abstract to component?
|
||||||
|
.col-4
|
||||||
|
.input-group
|
||||||
|
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||||
|
.filter-panel(v-if="isFilterPanelOpen")
|
||||||
|
.tags-category.d-flex(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key")
|
||||||
|
.tags-header
|
||||||
|
strong(v-once) {{ $t(tagsType.key) }}
|
||||||
|
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
||||||
|
.tags-list.container
|
||||||
|
.row(:class="{'no-gutters': !editingTags}")
|
||||||
|
template(v-if="editingTags && tagsType.key === 'tags'")
|
||||||
|
.col-6(v-for="(tag, tagIndex) in tagsSnap")
|
||||||
|
.inline-edit-input-group.tag-edit-item.input-group
|
||||||
|
input.tag-edit-input.inline-edit-input.form-control(type="text", :value="tag.name")
|
||||||
|
span.input-group-btn(@click="removeTag(tagIndex)")
|
||||||
|
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||||
|
.col-6
|
||||||
|
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag")
|
||||||
|
template(v-else)
|
||||||
|
.col-6(v-for="(tag, tagIndex) in tagsType.tags")
|
||||||
|
label.custom-control.custom-checkbox
|
||||||
|
input.custom-control-input(
|
||||||
|
type="checkbox",
|
||||||
|
:checked="isTagSelected(tag)",
|
||||||
|
@change="toggleTag(tag)",
|
||||||
|
)
|
||||||
|
span.custom-control-indicator
|
||||||
|
span.custom-control-description {{ tag.name }}
|
||||||
|
|
||||||
|
.filter-panel-footer.clearfix
|
||||||
|
template(v-if="editingTags === true")
|
||||||
|
.text-center
|
||||||
|
a.mr-3.btn-filters-primary(@click="saveTags()", v-once) {{ $t('saveEdits') }}
|
||||||
|
a.btn-filters-secondary(@click="cancelTagsEditing()", v-once) {{ $t('cancel') }}
|
||||||
|
template(v-else)
|
||||||
|
.float-left
|
||||||
|
a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }}
|
||||||
|
.float-right
|
||||||
|
a.mr-3.btn-filters-primary(@click="applyFilters()", v-once) {{ $t('applyFilters') }}
|
||||||
|
a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }}
|
||||||
|
span.input-group-btn
|
||||||
|
button.btn.btn-secondary.filter-button(
|
||||||
|
type="button",
|
||||||
|
@click="toggleFilterPanel()",
|
||||||
|
:class="{'filter-button-open': selectedTags.length > 0}",
|
||||||
|
)
|
||||||
|
.d-flex.align-items-center
|
||||||
|
span(v-once) {{ $t('filter') }}
|
||||||
|
.svg-icon.filter-icon(v-html="icons.filter")
|
||||||
|
#create-dropdown.col-1.offset-3
|
||||||
|
b-dropdown(:right="true", :variant="'success'")
|
||||||
|
div(slot="button-content")
|
||||||
|
.svg-icon.positive(v-html="icons.positive")
|
||||||
|
| {{ $t('create') }}
|
||||||
|
b-dropdown-item(v-for="type in columns", :key="type", @click="createTask(type)")
|
||||||
|
span.dropdown-icon-item(v-once)
|
||||||
|
span.svg-icon.inline(v-html="icons[type]")
|
||||||
|
span.text {{$t(type)}}
|
||||||
|
task-modal(
|
||||||
|
:task="workingTask",
|
||||||
|
:purpose="taskFormPurpose",
|
||||||
|
@cancel="cancelTaskModal()",
|
||||||
|
ref="taskModal",
|
||||||
|
:groupId="groupId",
|
||||||
|
v-on:taskCreated='taskCreated',
|
||||||
|
v-on:taskEdited='taskEdited',
|
||||||
|
)
|
||||||
|
.row
|
||||||
|
task-column.col-3(
|
||||||
|
v-for="column in columns",
|
||||||
|
:type="column",
|
||||||
|
:key="column",
|
||||||
|
:taskListOverride='tasksByType[column]',
|
||||||
|
v-on:editTask="editTask",
|
||||||
|
:group='group',
|
||||||
|
:searchText="searchText")
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~client/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
.user-tasks-page {
|
||||||
|
padding-top: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks-navigation {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.positive {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
color: $green-500;
|
||||||
|
margin-right: 8px;
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-icon-item .svg-icon {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.btn.btn-secondary.filter-button {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid $gray-400 !important;
|
||||||
|
|
||||||
|
&:hover, &:active, &:focus, &.open {
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: $purple-500 !important;
|
||||||
|
color: $gray-50 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.filter-button-open {
|
||||||
|
color: $purple-200 !important;
|
||||||
|
|
||||||
|
.filter-icon {
|
||||||
|
color: $purple-200 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-icon {
|
||||||
|
height: 10px;
|
||||||
|
width: 12px;
|
||||||
|
color: $gray-50;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel {
|
||||||
|
position: absolute;
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 24px;
|
||||||
|
max-width: 40vw;
|
||||||
|
z-index: 9999;
|
||||||
|
background: $white;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||||
|
top: 44px;
|
||||||
|
left: 20vw;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.43;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.tags-category {
|
||||||
|
border-bottom: 1px solid $gray-600;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-header {
|
||||||
|
flex-basis: 96px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.33;
|
||||||
|
color: $blue-10;
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
|
&:focus, &:hover, &:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-edit-input {
|
||||||
|
border-bottom: 1px solid $gray-500 !important;
|
||||||
|
|
||||||
|
&:focus, &:focus ~ .input-group-btn {
|
||||||
|
border-color: $purple-500 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-tag-item {
|
||||||
|
width: 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center left 10px;
|
||||||
|
border-bottom: 1px solid $gray-500 !important;
|
||||||
|
background-size: 10px 10px;
|
||||||
|
padding-left: 40px;
|
||||||
|
background-image: url(~client/assets/svg/for-css/positive.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-edit-item .input-group-btn {
|
||||||
|
border-bottom: 1px solid $gray-500 !important;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: $purple-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-control-description {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel-footer {
|
||||||
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
&:focus, &:hover, &:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-filters-danger {
|
||||||
|
color: $red-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-filters-primary {
|
||||||
|
color: $blue-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-filters-secondary {
|
||||||
|
color: $gray-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import taskDefaults from 'common/script/libs/taskDefaults';
|
||||||
|
import TaskColumn from '../tasks/column';
|
||||||
|
import TaskModal from '../tasks/taskModal';
|
||||||
|
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||||
|
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||||
|
|
||||||
|
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,
|
||||||
|
bDropdown,
|
||||||
|
bDropdownItem,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
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: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.group = await this.$store.dispatch('guilds:getGroup', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
let tasks = await this.$store.dispatch('tasks:getGroupTasks', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
let approvalRequests = await this.$store.dispatch('tasks:getGroupApprovals', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
});
|
||||||
|
let groupedApprovals = groupBy(approvalRequests, 'group.taskId');
|
||||||
|
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
task.approvals = groupedApprovals[task._id];
|
||||||
|
this.tasksByType[task.type].push(task);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
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('show::modal', 'task-modal');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
createTask (type) {
|
||||||
|
this.taskFormPurpose = 'create';
|
||||||
|
this.creatingTask = taskDefaults({type, text: ''});
|
||||||
|
this.workingTask = this.creatingTask;
|
||||||
|
// Necessary otherwise the first time the modal is not rendered
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
this.$root.$emit('show::modal', 'task-modal');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
taskCreated (task) {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
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>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
div
|
div
|
||||||
|
amazon-payments-modal(:amazon-payments='amazonPayments')
|
||||||
div(v-if='activePage === PAGES.BENEFITS')
|
div(v-if='activePage === PAGES.BENEFITS')
|
||||||
.header
|
.header
|
||||||
h1.text-center Need more for your Group?
|
h1.text-center Need more for your Group?
|
||||||
@@ -75,9 +76,9 @@ div
|
|||||||
input(type='checkbox', v-model='newGroup.leaderOnly.challenges')
|
input(type='checkbox', v-model='newGroup.leaderOnly.challenges')
|
||||||
| {{ $t('leaderOnlyChallenges') }}
|
| {{ $t('leaderOnlyChallenges') }}
|
||||||
.form-group(v-if='type === "party"')
|
.form-group(v-if='type === "party"')
|
||||||
input.btn.btn-default.form-control(type='submit', :value="$t('create')")
|
button.btn.btn-default.form-control(@click='pay()', :value="$t('create')")
|
||||||
.form-group
|
.form-group
|
||||||
button.btn.btn-primary.btn-lg.btn-block(@click="upgrade()", :disabled="!newGroupIsReady") {{ $t('create') }}
|
button.btn.btn-primary.btn-lg.btn-block(@click="pay()", :disabled="!newGroupIsReady") {{ $t('create') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -163,9 +164,18 @@ div
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import paymentsMixin from '../../mixins/payments';
|
||||||
|
import amazonPaymentsModal from '../payments/amazonModal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [paymentsMixin],
|
||||||
|
components: {
|
||||||
|
amazonPaymentsModal,
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
StripeCheckout: {},
|
||||||
|
amazonPayments: {},
|
||||||
PAGES: {
|
PAGES: {
|
||||||
CREATE_GROUP: 'create-group',
|
CREATE_GROUP: 'create-group',
|
||||||
UPGRADE_GROUP: 'upgrade-group',
|
UPGRADE_GROUP: 'upgrade-group',
|
||||||
@@ -191,6 +201,9 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
this.activePage = this.PAGES.BENEFITS;
|
this.activePage = this.PAGES.BENEFITS;
|
||||||
this.$store.state.hideHeader = true;
|
this.$store.state.hideHeader = true;
|
||||||
|
|
||||||
|
// @TODO: can this be in a mixin?
|
||||||
|
this.StripeCheckout = window.StripeCheckout;
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
// @TODO: going from the page back to party modal does not show
|
// @TODO: going from the page back to party modal does not show
|
||||||
@@ -210,21 +223,21 @@ export default {
|
|||||||
this.paymentMethod = paymentType;
|
this.paymentMethod = paymentType;
|
||||||
this.changePage(this.PAGES.CREATE_GROUP);
|
this.changePage(this.PAGES.CREATE_GROUP);
|
||||||
},
|
},
|
||||||
upgrade () {
|
pay () {
|
||||||
// let subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
|
let subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
|
||||||
if (this.paymentMethod === this.PAYMENTS.STRIPE) {
|
if (this.paymentMethod === this.PAYMENTS.STRIPE) {
|
||||||
// Payments.showStripe({
|
this.showStripe({
|
||||||
// subscription: subscriptionKey,
|
subscription: subscriptionKey,
|
||||||
// coupon: null,
|
coupon: null,
|
||||||
// groupToCreate: this.newGroup
|
groupToCreate: this.newGroup,
|
||||||
// });
|
});
|
||||||
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
|
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
|
||||||
// Payments.amazonPayments.init({
|
this.amazonPaymentsInit({
|
||||||
// type: 'subscription',
|
type: 'subscription',
|
||||||
// subscription: subscriptionKey,
|
subscription: subscriptionKey,
|
||||||
// coupon: null,
|
coupon: null,
|
||||||
// groupToCreate: this.newGroup
|
groupToCreate: this.newGroup,
|
||||||
// });
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,17 +1,27 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
b-modal#amazon-payment(title="Amazon", :hide-footer="true", size='lg')
|
b-modal#amazon-payment(title="Amazon", :hide-footer="true", size='lg')
|
||||||
button#AmazonPayButton
|
button#AmazonPayButton
|
||||||
|
#AmazonPayWallet(v-if="amazonPayments.loggedIn", style="width: 400px; height: 228px;")
|
||||||
|
#AmazonPayRecurring(v-if="amazonPayments.loggedIn && amazonPayments.type === 'subscription'",
|
||||||
|
style="width: 400px; height: 140px;")
|
||||||
|
.modal-footer
|
||||||
|
.btn.btn-primary(:disabled="amazonPaymentsCanCheckout() || !amazonButtonEnabled",
|
||||||
|
@click="amazonCheckOut()") {{ $t('checkout') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#AmazonPayRecurring {
|
||||||
|
height: 200px;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||||
|
|
||||||
const AMAZON_PAYMENTS = {
|
const AMAZON_PAYMENTS = process.env.AMAZON_PAYMENTS; // eslint-disable-line
|
||||||
CLIENT_ID: 'testing',
|
|
||||||
SELLER_ID: 'test-seelllide',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -22,70 +32,85 @@ export default {
|
|||||||
return {
|
return {
|
||||||
OffAmazonPayments: {},
|
OffAmazonPayments: {},
|
||||||
isAmazonReady: false,
|
isAmazonReady: false,
|
||||||
|
amazonButtonEnabled: false,
|
||||||
|
amazonPaymentsbillingAgreementId: '',
|
||||||
|
amazonPaymentspaymentSelected: false,
|
||||||
|
amazonPaymentsrecurringConsent: 'false',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
// @TODO:
|
||||||
|
// window.onAmazonLoginReady = function() {
|
||||||
|
// amazon.Login.setClientId('CLIENT-ID');
|
||||||
|
// };
|
||||||
|
// window.onAmazonPaymentsReady = function() {
|
||||||
|
// showButton();
|
||||||
|
// };
|
||||||
this.OffAmazonPayments = window.OffAmazonPayments;
|
this.OffAmazonPayments = window.OffAmazonPayments;
|
||||||
this.isAmazonReady = true;
|
this.isAmazonReady = true;
|
||||||
window.amazon.Login.setClientId(AMAZON_PAYMENTS.CLIENT_ID);
|
window.amazon.Login.setClientId(AMAZON_PAYMENTS.CLIENT_ID);
|
||||||
|
|
||||||
|
|
||||||
// @TODO: prevent modal close form clicking outside
|
// @TODO: prevent modal close form clicking outside
|
||||||
this.OffAmazonPayments.Button('AmazonPayButton', AMAZON_PAYMENTS.SELLER_ID, { // eslint-disable-line
|
let amazonButton = this.OffAmazonPayments.Button( // eslint-disable-line
|
||||||
type: 'PwA',
|
'AmazonPayButton',
|
||||||
color: 'Gold',
|
AMAZON_PAYMENTS.SELLER_ID,
|
||||||
size: 'small',
|
{
|
||||||
agreementType: 'BillingAgreement',
|
type: 'PwA',
|
||||||
|
color: 'Gold',
|
||||||
|
size: 'small',
|
||||||
|
agreementType: 'BillingAgreement',
|
||||||
|
|
||||||
onSignIn: async (contract) => {
|
onSignIn: async (contract) => {
|
||||||
this.amazonPaymentsbillingAgreementId = contract.getAmazonBillingAgreementId();
|
this.amazonPaymentsbillingAgreementId = contract.getAmazonBillingAgreementId();
|
||||||
|
|
||||||
if (this.amazonPaymentstype === 'subscription') {
|
if (this.amazonPayments.type === 'subscription') {
|
||||||
this.amazonPaymentsloggedIn = true;
|
this.amazonPayments.loggedIn = true;
|
||||||
this.amazonPaymentsinitWidgets();
|
this.amazonPaymentsinitWidgets();
|
||||||
} else {
|
} else {
|
||||||
let url = '/amazon/createOrderReferenceId';
|
let url = '/amazon/createOrderReferenceId';
|
||||||
let response = await axios.post(url, {
|
let response = await axios.post(url, {
|
||||||
billingAgreementId: this.amazonPaymentsbillingAgreementId,
|
billingAgreementId: this.amazonPaymentsbillingAgreementId,
|
||||||
});
|
|
||||||
|
|
||||||
// @TODO: Success
|
|
||||||
this.amazonPaymentsloggedIn = true;
|
|
||||||
this.amazonPaymentsorderReferenceId = response.data.orderReferenceId;
|
|
||||||
this.amazonPaymentsinitWidgets();
|
|
||||||
// @TODO: error
|
|
||||||
alert(response.message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
authorization: () => {
|
|
||||||
window.amazon.Login.authorize({
|
|
||||||
scope: 'payments:widget',
|
|
||||||
popup: true,
|
|
||||||
}, function amazonSuccess (response) {
|
|
||||||
if (response.error) return alert(response.error);
|
|
||||||
|
|
||||||
let url = '/amazon/verifyAccessToken';
|
|
||||||
axios.post(url, response)
|
|
||||||
.catch((e) => {
|
|
||||||
alert(e.message);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onError: this.amazonOnError,
|
// @TODO: Success
|
||||||
});
|
this.amazonPayments.loggedIn = true;
|
||||||
|
this.amazonPaymentsorderReferenceId = response.data.orderReferenceId;
|
||||||
|
this.OffAmazonPayments.amazonPaymentsinitWidgets();
|
||||||
|
// @TODO: error
|
||||||
|
alert(response.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
authorization: () => {
|
||||||
|
window.amazon.Login.authorize({
|
||||||
|
scope: 'payments:widget',
|
||||||
|
popup: true,
|
||||||
|
}, function amazonSuccess (response) {
|
||||||
|
if (response.error) return alert(response.error);
|
||||||
|
|
||||||
|
let url = '/amazon/verifyAccessToken';
|
||||||
|
axios.post(url, response)
|
||||||
|
.catch((e) => {
|
||||||
|
alert(e.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: this.amazonOnError,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
amazonPaymentsCanCheckout () {
|
amazonPaymentsCanCheckout () {
|
||||||
// if (this.amazonPaymentstype === 'single') {
|
if (this.amazonPayments.type === 'single') {
|
||||||
// return this.amazonPaymentspaymentSelected === true;
|
return this.amazonPaymentspaymentSelected === true;
|
||||||
// } else if(this.amazonPaymentstype === 'subscription') {
|
} else if (this.amazonPayments.type === 'subscription') {
|
||||||
// return this.amazonPaymentspaymentSelected === true &&
|
return this.amazonPaymentspaymentSelected === true &&
|
||||||
// // Mah.. one is a boolean the other a string...
|
// Mah.. one is a boolean the other a string...
|
||||||
// this.amazonPaymentsrecurringConsent === 'true';
|
this.amazonPaymentsrecurringConsent === 'true';
|
||||||
// } else {
|
} else {
|
||||||
// return false;
|
return false;
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
amazonInitWidgets () {
|
amazonInitWidgets () {
|
||||||
let walletParams = {
|
let walletParams = {
|
||||||
@@ -101,7 +126,7 @@ export default {
|
|||||||
onError: this.amazonOnError,
|
onError: this.amazonOnError,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.amazonPaymentstype === 'subscription') {
|
if (this.amazonPayments.type === 'subscription') {
|
||||||
walletParams.agreementType = 'BillingAgreement';
|
walletParams.agreementType = 'BillingAgreement';
|
||||||
|
|
||||||
walletParams.billingAgreementId = this.amazonPaymentsbillingAgreementId;
|
walletParams.billingAgreementId = this.amazonPaymentsbillingAgreementId;
|
||||||
@@ -136,7 +161,7 @@ export default {
|
|||||||
async amazonCheckOut () {
|
async amazonCheckOut () {
|
||||||
this.amazonButtonEnabled = false;
|
this.amazonButtonEnabled = false;
|
||||||
|
|
||||||
if (this.amazonPaymentstype === 'single') {
|
if (this.amazonPayments.type === 'single') {
|
||||||
let url = '/amazon/checkout';
|
let url = '/amazon/checkout';
|
||||||
let response = await axios.post(url, {
|
let response = await axios.post(url, {
|
||||||
orderReferenceId: this.amazonPaymentsorderReferenceId,
|
orderReferenceId: this.amazonPaymentsorderReferenceId,
|
||||||
@@ -150,35 +175,86 @@ export default {
|
|||||||
// Failure
|
// Failure
|
||||||
alert(response.message);
|
alert(response.message);
|
||||||
this.amazonPaymentsreset();
|
this.amazonPaymentsreset();
|
||||||
} else if (this.amazonPaymentstype === 'subscription') {
|
} else if (this.amazonPayments.type === 'subscription') {
|
||||||
let url = '/amazon/subscribe';
|
let url = '/amazon/subscribe';
|
||||||
|
|
||||||
if (this.amazonPaymentsgroupToCreate) {
|
if (this.amazonPayments.groupToCreate) {
|
||||||
url = '/api/v3/groups/create-plan';
|
url = '/api/v3/groups/create-plan';
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await axios.post(url, {
|
let response = await axios.post(url, {
|
||||||
billingAgreementId: this.amazonPaymentsbillingAgreementId,
|
billingAgreementId: this.amazonPaymentsbillingAgreementId,
|
||||||
subscription: this.amazonPaymentssubscription,
|
subscription: this.amazonPayments.subscription,
|
||||||
coupon: this.amazonPaymentscoupon,
|
coupon: this.amazonPayments.coupon,
|
||||||
groupId: this.amazonPaymentsgroupId,
|
groupId: this.amazonPayments.groupId,
|
||||||
groupToCreate: this.amazonPaymentsgroupToCreate,
|
groupToCreate: this.amazonPayments.groupToCreate,
|
||||||
paymentType: 'Amazon',
|
paymentType: 'Amazon',
|
||||||
});
|
});
|
||||||
|
|
||||||
// IF success
|
let responseStatus = response.status;
|
||||||
this.amazonPaymentsreset();
|
if (responseStatus >= 400) {
|
||||||
if (response && response.data && response.data._id) {
|
alert(`Error: ${response.message}`);
|
||||||
this.$router.push(`/groups/guilds/${response.data._id}`);
|
// @TODO: do we need this? this.amazonPaymentsreset();
|
||||||
} else {
|
return;
|
||||||
this.$router.push('/');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if fails
|
let newGroup = response.data.data;
|
||||||
alert(response.message);
|
if (newGroup && newGroup._id) {
|
||||||
|
// @TODO: Just append? or $emit?
|
||||||
|
this.$router.push(`/group-plans/${newGroup._id}/task-information`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload(true);
|
||||||
this.amazonPaymentsreset();
|
this.amazonPaymentsreset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
amazonPaymentsinitWidgets () {
|
||||||
|
let walletParams = {
|
||||||
|
sellerId: AMAZON_PAYMENTS.SELLER_ID,
|
||||||
|
design: {
|
||||||
|
designMode: 'responsive',
|
||||||
|
},
|
||||||
|
|
||||||
|
onPaymentSelect: () => {
|
||||||
|
this.amazonPayments.paymentSelected = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: this.amazonOnError,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.amazonPayments.type === 'subscription') {
|
||||||
|
walletParams.agreementType = 'BillingAgreement';
|
||||||
|
|
||||||
|
walletParams.billingAgreementId = this.amazonPayments.billingAgreementId;
|
||||||
|
walletParams.onReady = (billingAgreement) => {
|
||||||
|
this.amazonPayments.billingAgreementId = billingAgreement.getAmazonBillingAgreementId();
|
||||||
|
|
||||||
|
new this.OffAmazonPayments.Widgets.Consent({
|
||||||
|
sellerId: AMAZON_PAYMENTS.SELLER_ID,
|
||||||
|
amazonBillingAgreementId: this.amazonPayments.billingAgreementId,
|
||||||
|
design: {
|
||||||
|
designMode: 'responsive',
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady: (consent) => {
|
||||||
|
let getConsent = consent.getConsentStatus;
|
||||||
|
this.amazonPayments.recurringConsent = getConsent ? getConsent() : false;
|
||||||
|
},
|
||||||
|
|
||||||
|
onConsent: (consent) => {
|
||||||
|
this.amazonPayments.recurringConsent = consent.getConsentStatus();
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: this.amazonOnError,
|
||||||
|
}).bind('AmazonPayRecurring');
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
walletParams.amazonOrderReferenceId = this.amazonPayments.orderReferenceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
new this.OffAmazonPayments.Widgets.Wallet(walletParams).bind('AmazonPayWallet');
|
||||||
|
},
|
||||||
amazonOnError (error) {
|
amazonOnError (error) {
|
||||||
alert(error.getErrorMessage());
|
alert(error.getErrorMessage());
|
||||||
// @TODO: this.amazonPaymentsreset();
|
// @TODO: this.amazonPaymentsreset();
|
||||||
@@ -186,8 +262,8 @@ export default {
|
|||||||
reset () {
|
reset () {
|
||||||
this.amazonPaymentsmodal.close(); // @TODO: this.$root.$emit('hide::modal', 'guild-form');
|
this.amazonPaymentsmodal.close(); // @TODO: this.$root.$emit('hide::modal', 'guild-form');
|
||||||
this.amazonPaymentsmodal = null;
|
this.amazonPaymentsmodal = null;
|
||||||
this.amazonPaymentstype = null;
|
this.amazonPayments.type = null;
|
||||||
this.amazonPaymentsloggedIn = false;
|
this.amazonPayments.loggedIn = false;
|
||||||
this.amazonPaymentsgift = null;
|
this.amazonPaymentsgift = null;
|
||||||
this.amazonPaymentsbillingAgreementId = null;
|
this.amazonPaymentsbillingAgreementId = null;
|
||||||
this.amazonPaymentsorderReferenceId = null;
|
this.amazonPaymentsorderReferenceId = null;
|
||||||
|
|||||||
@@ -116,10 +116,12 @@ import { mapState } from 'client/libs/store';
|
|||||||
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
|
import subscriptionBlocks from '../../../common/script/content/subscriptionBlocks';
|
||||||
import planGemLimits from '../../../common/script/libs/planGemLimits';
|
import planGemLimits from '../../../common/script/libs/planGemLimits';
|
||||||
import amazonPaymentsModal from '../payments/amazonModal';
|
import amazonPaymentsModal from '../payments/amazonModal';
|
||||||
|
import paymentsMixin from '../../mixins/payments';
|
||||||
|
|
||||||
const STRIPE_PUB_KEY = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh';
|
const STRIPE_PUB_KEY = 'pk_test_6pRNASCoBOKtIshFeQd4XMUh';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [paymentsMixin],
|
||||||
components: {
|
components: {
|
||||||
amazonPaymentsModal,
|
amazonPaymentsModal,
|
||||||
},
|
},
|
||||||
@@ -250,58 +252,6 @@ export default {
|
|||||||
subs.basic_6mo.discount = true;
|
subs.basic_6mo.discount = true;
|
||||||
subs.google_6mo.discount = false;
|
subs.google_6mo.discount = false;
|
||||||
},
|
},
|
||||||
showStripe (data) {
|
|
||||||
if (!this.checkGemAmount(data)) return;
|
|
||||||
|
|
||||||
let sub = false;
|
|
||||||
|
|
||||||
if (data.subscription) {
|
|
||||||
sub = data.subscription;
|
|
||||||
} else if (data.gift && data.gift.type === 'subscription') {
|
|
||||||
sub = data.gift.subscription.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub = sub && subscriptionBlocks[sub];
|
|
||||||
|
|
||||||
let amount = 500;// 500 = $5
|
|
||||||
if (sub) amount = sub.price * 100;
|
|
||||||
if (data.gift && data.gift.type === 'gems') amount = data.gift.gems.amount / 4 * 100;
|
|
||||||
if (data.group) amount = (sub.price + 3 * (data.group.memberCount - 1)) * 100;
|
|
||||||
|
|
||||||
this.StripeCheckout.open({
|
|
||||||
key: STRIPE_PUB_KEY,
|
|
||||||
address: false,
|
|
||||||
amount,
|
|
||||||
name: 'Habitica',
|
|
||||||
description: sub ? this.$t('subscribe') : this.$t('checkout'),
|
|
||||||
image: '/apple-touch-icon-144-precomposed.png',
|
|
||||||
panelLabel: sub ? this.$t('subscribe') : this.$t('checkout'),
|
|
||||||
token: async (res) => {
|
|
||||||
let url = '/stripe/checkout?a=a'; // just so I can concat &x=x below
|
|
||||||
|
|
||||||
if (data.groupToCreate) {
|
|
||||||
url = '/api/v3/groups/create-plan?a=a';
|
|
||||||
res.groupToCreate = data.groupToCreate;
|
|
||||||
res.paymentType = 'Stripe';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.gift) url += `&gift=${this.encodeGift(data.uuid, data.gift)}`;
|
|
||||||
if (data.subscription) url += `&sub=${sub.key}`;
|
|
||||||
if (data.coupon) url += `&coupon=${data.coupon}`;
|
|
||||||
if (data.groupId) url += `&groupId=${data.groupId}`;
|
|
||||||
|
|
||||||
let response = await axios.post(url, res);
|
|
||||||
// Success
|
|
||||||
if (response && response.data && response.data._id) {
|
|
||||||
this.$router.push(`/#/options/groups/guilds/${response.data._id}`);
|
|
||||||
} else {
|
|
||||||
window.location.reload(true);
|
|
||||||
}
|
|
||||||
// Error
|
|
||||||
alert(response.message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
showStripeEdit (config) {
|
showStripeEdit (config) {
|
||||||
let groupId;
|
let groupId;
|
||||||
if (config && config.groupId) {
|
if (config && config.groupId) {
|
||||||
@@ -373,34 +323,6 @@ export default {
|
|||||||
getCancelSubInfo () {
|
getCancelSubInfo () {
|
||||||
return this.$t(`cancelSubInfo${this.user.purchased.plan.paymentMethod}`);
|
return this.$t(`cancelSubInfo${this.user.purchased.plan.paymentMethod}`);
|
||||||
},
|
},
|
||||||
amazonPaymentsInit (data) {
|
|
||||||
if (!this.isAmazonReady) return;
|
|
||||||
if (!this.checkGemAmount(data)) return;
|
|
||||||
if (data.type !== 'single' && data.type !== 'subscription') return;
|
|
||||||
|
|
||||||
if (data.gift) {
|
|
||||||
if (data.gift.gems && data.gift.gems.amount && data.gift.gems.amount <= 0) return;
|
|
||||||
data.gift.uuid = data.giftedTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.subscription) {
|
|
||||||
this.amazonPayments.subscription = data.subscription;
|
|
||||||
this.amazonPayments.coupon = data.coupon;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.groupId) {
|
|
||||||
this.amazonPayments.groupId = data.groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.groupToCreate) {
|
|
||||||
this.amazonPayments.groupToCreate = data.groupToCreate;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.amazonPayments.gift = data.gift;
|
|
||||||
this.amazonPayments.type = data.type;
|
|
||||||
|
|
||||||
this.$root.$emit('show::modal', 'amazon-payment');
|
|
||||||
},
|
|
||||||
payPalPayment (data) {
|
payPalPayment (data) {
|
||||||
if (!this.checkGemAmount(data)) return;
|
if (!this.checkGemAmount(data)) return;
|
||||||
|
|
||||||
@@ -413,15 +335,6 @@ export default {
|
|||||||
let encodedString = JSON.stringify(gift);
|
let encodedString = JSON.stringify(gift);
|
||||||
return encodeURIComponent(encodedString);
|
return encodeURIComponent(encodedString);
|
||||||
},
|
},
|
||||||
checkGemAmount (data) {
|
|
||||||
let isGem = data && data.gift && data.gift.type === 'gems';
|
|
||||||
let notEnoughGem = isGem && (!data.gift.gems.amount || data.gift.gems.amount === 0);
|
|
||||||
if (notEnoughGem) {
|
|
||||||
Notification.error(this.$t('badAmountOfGemsToPurchase'), true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
99
website/client/components/tasks/approvalFooter.vue
Normal file
99
website/client/components/tasks/approvalFooter.vue
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
div
|
||||||
|
approval-modal(:task='task')
|
||||||
|
.claim-bottom-message.col-12
|
||||||
|
.task-unclaimed(v-if='!approvalRequested && !multipleApprovalsRequested')
|
||||||
|
| {{ message }}
|
||||||
|
a(@click='claim()', v-if='!userIsAssigned') Claim
|
||||||
|
a(@click='unassign()', v-if='userIsAssigned') Remove Claim
|
||||||
|
.row.task-single-approval(v-if='approvalRequested')
|
||||||
|
.col-6.text-center
|
||||||
|
a(@click='approve()') Approve Task
|
||||||
|
.col-6.text-center
|
||||||
|
a Needs work
|
||||||
|
.text-center.task-multi-approval(v-if='multipleApprovalsRequested')
|
||||||
|
a(@click='showRequests()') View Requests
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.task-unclaimed a {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
import approvalModal from './approvalModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['task', 'group'],
|
||||||
|
components: {
|
||||||
|
approvalModal,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
userIsAssigned () {
|
||||||
|
return this.task.group.assignedUsers && this.task.group.assignedUsers.indexOf(this.user._id) !== -1;
|
||||||
|
},
|
||||||
|
message () {
|
||||||
|
let assignedUsers = this.task.group.assignedUsers;
|
||||||
|
let assignedUsersLength = assignedUsers.length;
|
||||||
|
|
||||||
|
if (assignedUsersLength === 1 && !this.userIsAssigned) {
|
||||||
|
return `Assigned to ${assignedUsers}`;
|
||||||
|
} else if (assignedUsersLength > 1 && !this.userIsAssigned) {
|
||||||
|
return `Assigned to ${assignedUsersLength} members`;
|
||||||
|
} else if (assignedUsersLength > 1 && this.userIsAssigned) {
|
||||||
|
return `Assigned to you and ${assignedUsersLength} members`;
|
||||||
|
} else if (this.userIsAssigned) {
|
||||||
|
return 'You are assigned to this task';
|
||||||
|
} else if (assignedUsersLength === 0) {
|
||||||
|
return 'This task is unassigned';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
approvalRequested () {
|
||||||
|
if (this.task.approvals && this.task.approvals.length === 1) return true;
|
||||||
|
},
|
||||||
|
multipleApprovalsRequested () {
|
||||||
|
if (this.task.approvals && this.task.approvals.length > 1) return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
claim () {
|
||||||
|
if (!confirm('Are you sure you want to claim this task?')) return;
|
||||||
|
this.$store.dispatch('tasks:assignTask', {
|
||||||
|
taskId: this.task._id,
|
||||||
|
userId: this.user._id,
|
||||||
|
});
|
||||||
|
this.task.group.assignedUsers.push(this.user._id);
|
||||||
|
// @TODO: Reload user tasks?
|
||||||
|
},
|
||||||
|
unassign () {
|
||||||
|
if (!confirm('Are you sure you want to unclaim this task?')) return;
|
||||||
|
this.$store.dispatch('tasks:unassignTask', {
|
||||||
|
taskId: this.task._id,
|
||||||
|
userId: this.user._id,
|
||||||
|
});
|
||||||
|
let index = this.task.group.assignedUsers.indexOf(this.user._id);
|
||||||
|
this.task.group.assignedUsers.splice(index, 1);
|
||||||
|
// @TODO: Reload user tasks?
|
||||||
|
},
|
||||||
|
approve () {
|
||||||
|
if (!confirm('Are you sure you want to approve this task?')) return;
|
||||||
|
let userIdToApprove = this.task.group.assignedUsers[0];
|
||||||
|
this.$store.dispatch('tasks:unassignTask', {
|
||||||
|
taskId: this.task._id,
|
||||||
|
userId: userIdToApprove,
|
||||||
|
});
|
||||||
|
this.task.group.assignedUsers.splice(0, 1);
|
||||||
|
this.task.approvals.splice(0, 1);
|
||||||
|
},
|
||||||
|
reject () {
|
||||||
|
|
||||||
|
},
|
||||||
|
showRequests () {
|
||||||
|
this.$root.$emit('show::modal', 'approval-modal');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
38
website/client/components/tasks/approvalHeader.vue
Normal file
38
website/client/components/tasks/approvalHeader.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.claim-bottom-message.col-12.text-center(v-if='task.approvals && task.approvals.length > 0', :class="{approval: userIsAdmin}")
|
||||||
|
.task-unclaimed
|
||||||
|
| {{ message }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.approval {
|
||||||
|
background: #24cc8f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
export default {
|
||||||
|
props: ['task', 'group'],
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
message () {
|
||||||
|
let approvals = this.task.approvals;
|
||||||
|
let approvalsLength = approvals.length;
|
||||||
|
let userIsRequesting = this.task.group.approvals && this.task.group.approvals.indexOf(this.user._id) !== -1;
|
||||||
|
|
||||||
|
if (approvalsLength === 1 && !userIsRequesting) {
|
||||||
|
return `${approvals[0].userId.profile.name} requests approval`;
|
||||||
|
} else if (approvalsLength > 1 && !userIsRequesting) {
|
||||||
|
return `${approvalsLength} request approval`;
|
||||||
|
} else if (approvalsLength === 1 && userIsRequesting) {
|
||||||
|
return 'You are requesting approval';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userIsAdmin () {
|
||||||
|
return this.group.leader.id === this.user._id;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
44
website/client/components/tasks/approvalModal.vue
Normal file
44
website/client/components/tasks/approvalModal.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
b-modal#approval-modal(title="Approve Task", size='md', :hide-footer="true")
|
||||||
|
.modal-body
|
||||||
|
.row.approval(v-for='(approval, index) in task.approvals')
|
||||||
|
.col-8
|
||||||
|
strong {{approval.userId.profile.name}}
|
||||||
|
.col-2
|
||||||
|
button.btn.btn-primary(@click='approve(index)') Approve
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-secondary(@click='close()') {{$t('close')}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.row.approval {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['task'],
|
||||||
|
components: {
|
||||||
|
bModal,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
approve (index) {
|
||||||
|
if (!confirm('Are you sure you want to approve this task?')) return;
|
||||||
|
let userIdToApprove = this.task.group.assignedUsers[index];
|
||||||
|
this.$store.dispatch('tasks:unassignTask', {
|
||||||
|
taskId: this.task._id,
|
||||||
|
userId: userIdToApprove,
|
||||||
|
});
|
||||||
|
this.task.group.assignedUsers.splice(index, 1);
|
||||||
|
this.task.approvals.splice(index, 1);
|
||||||
|
},
|
||||||
|
close () {
|
||||||
|
this.$root.$emit('hide::modal', 'approval-modal');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
v-if="filterTask(task)",
|
v-if="filterTask(task)",
|
||||||
:isUser="isUser",
|
:isUser="isUser",
|
||||||
@editTask="editTask",
|
@editTask="editTask",
|
||||||
|
:group='group',
|
||||||
)
|
)
|
||||||
template(v-if="isUser === true && type === 'reward' && activeFilter.label !== 'custom'")
|
template(v-if="isUser === true && type === 'reward' && activeFilter.label !== 'custom'")
|
||||||
.reward-items
|
.reward-items
|
||||||
@@ -169,7 +170,7 @@ export default {
|
|||||||
bModal,
|
bModal,
|
||||||
shopItem,
|
shopItem,
|
||||||
},
|
},
|
||||||
props: ['type', 'isUser', 'searchText', 'selectedTags', 'taskListOverride'],
|
props: ['type', 'isUser', 'searchText', 'selectedTags', 'taskListOverride', 'group'], // @TODO: maybe we should store the group on state?
|
||||||
data () {
|
data () {
|
||||||
const types = Object.freeze({
|
const types = Object.freeze({
|
||||||
habit: {
|
habit: {
|
||||||
@@ -227,6 +228,7 @@ export default {
|
|||||||
userPreferences: 'user.data.preferences',
|
userPreferences: 'user.data.preferences',
|
||||||
}),
|
}),
|
||||||
taskList () {
|
taskList () {
|
||||||
|
// @TODO: This should not default to user's tasks. It should require that you pass options in
|
||||||
if (this.taskListOverride) return this.taskListOverride;
|
if (this.taskListOverride) return this.taskListOverride;
|
||||||
return this.tasks[`${this.type}s`];
|
return this.tasks[`${this.type}s`];
|
||||||
},
|
},
|
||||||
@@ -265,6 +267,8 @@ export default {
|
|||||||
combinedTasksHeights += el.offsetHeight;
|
combinedTasksHeights += el.offsetHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!this.$refs.columnBackground) return;
|
||||||
|
|
||||||
const rewardsList = taskListEl.getElementsByClassName('reward-items')[0];
|
const rewardsList = taskListEl.getElementsByClassName('reward-items')[0];
|
||||||
if (rewardsList) {
|
if (rewardsList) {
|
||||||
combinedTasksHeights += rewardsList.offsetHeight;
|
combinedTasksHeights += rewardsList.offsetHeight;
|
||||||
|
|||||||
@@ -1,277 +1,281 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.task.d-flex(:class="{'task-not-scoreable': isUser !== true}")
|
.task
|
||||||
// Habits left side control
|
approval-header(:task='task', v-if='this.task.group.id', :group='group')
|
||||||
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up")
|
.d-flex(:class="{'task-not-scoreable': isUser !== true}")
|
||||||
.task-control.habit-control(:class="controlClass.up + '-control-habit'", @click="isUser ? score('up') : null")
|
// Habits left side control
|
||||||
.svg-icon.positive(v-html="icons.positive")
|
.left-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.up")
|
||||||
// Dailies and todos left side control
|
.task-control.habit-control(:class="controlClass.up + '-control-habit'", @click="isUser ? score('up') : null")
|
||||||
.left-control.d-flex.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass")
|
.svg-icon.positive(v-html="icons.positive")
|
||||||
.task-control.daily-todo-control(:class="controlClass + '-control-daily-todo'", @click="isUser ? score(task.completed ? 'down' : 'up') : null")
|
// Dailies and todos left side control
|
||||||
.svg-icon.check(v-html="icons.check", :class="{'display-check-icon': task.completed}")
|
.left-control.d-flex.justify-content-center(v-if="task.type === 'daily' || task.type === 'todo'", :class="controlClass")
|
||||||
// Task title, description and icons
|
.task-control.daily-todo-control(:class="controlClass + '-control-daily-todo'", @click="isUser ? score(task.completed ? 'down' : 'up') : null")
|
||||||
.task-content(:class="contentClass")
|
.svg-icon.check(v-html="icons.check", :class="{'display-check-icon': task.completed}")
|
||||||
.task-clickable-area(@click="edit($event, task)")
|
// Task title, description and icons
|
||||||
h3.task-title(:class="{ 'has-notes': task.notes }", v-markdown="task.text")
|
.task-content(:class="contentClass")
|
||||||
.task-notes.small-text(v-markdown="task.notes")
|
.task-clickable-area(@click="edit($event, task)")
|
||||||
.checklist(v-if="task.checklist && task.checklist.length > 0")
|
h3.task-title(:class="{ 'has-notes': task.notes }", v-markdown="task.text")
|
||||||
label.custom-control.custom-checkbox.checklist-item(
|
.task-notes.small-text(v-markdown="task.notes")
|
||||||
v-for="item in task.checklist", :class="{'checklist-item-done': item.completed}",
|
.checklist(v-if="task.checklist && task.checklist.length > 0")
|
||||||
)
|
label.custom-control.custom-checkbox.checklist-item(
|
||||||
input.custom-control-input(type="checkbox", :checked="item.completed", @change="toggleChecklistItem(item)")
|
v-for="item in task.checklist", :class="{'checklist-item-done': item.completed}",
|
||||||
span.custom-control-indicator
|
|
||||||
span.custom-control-description {{ item.text }}
|
|
||||||
.icons.small-text.d-flex.align-items-center
|
|
||||||
.d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}")
|
|
||||||
.svg-icon.calendar(v-html="icons.calendar")
|
|
||||||
span {{dueIn}}
|
|
||||||
.icons-right.d-flex.justify-content-end
|
|
||||||
.d-flex.align-items-center(v-if="showStreak")
|
|
||||||
.svg-icon.streak(v-html="icons.streak")
|
|
||||||
span(v-if="task.type === 'daily'") {{task.streak}}
|
|
||||||
span(v-if="task.type === 'habit'")
|
|
||||||
span.m-0(v-if="task.up") +{{task.counterUp}}
|
|
||||||
span.m-0(v-if="task.up && task.down") |
|
|
||||||
span.m-0(v-if="task.down") -{{task.counterDown}}
|
|
||||||
.d-flex.align-items-center(v-if="task.challenge && task.challenge.id")
|
|
||||||
.svg-icon.challenge(v-html="icons.challenge")
|
|
||||||
b-popover.tags-popover.no-span-margin(
|
|
||||||
:triggers="['hover']",
|
|
||||||
:placement="'bottom'",
|
|
||||||
:popover-style="{'max-width': '1000px'}",
|
|
||||||
)
|
)
|
||||||
.d-flex.align-items-center(slot="content")
|
input.custom-control-input(type="checkbox", :checked="item.completed", @change="toggleChecklistItem(item)")
|
||||||
.tags-popover-title(v-once) {{ `${$t('tags')}:` }}
|
span.custom-control-indicator
|
||||||
.tag-label(v-for="tag in getTagsFor(task)") {{tag}}
|
span.custom-control-description {{ item.text }}
|
||||||
.d-flex.align-items-center(v-if="task.tags && task.tags.length > 0")
|
.icons.small-text.d-flex.align-items-center
|
||||||
.svg-icon.tags(v-html="icons.tags")
|
.d-flex.align-items-center(v-if="task.type === 'todo' && task.date", :class="{'due-overdue': isDueOverdue}")
|
||||||
|
.svg-icon.calendar(v-html="icons.calendar")
|
||||||
|
span {{dueIn}}
|
||||||
|
.icons-right.d-flex.justify-content-end
|
||||||
|
.d-flex.align-items-center(v-if="showStreak")
|
||||||
|
.svg-icon.streak(v-html="icons.streak")
|
||||||
|
span(v-if="task.type === 'daily'") {{task.streak}}
|
||||||
|
span(v-if="task.type === 'habit'")
|
||||||
|
span.m-0(v-if="task.up") +{{task.counterUp}}
|
||||||
|
span.m-0(v-if="task.up && task.down") |
|
||||||
|
span.m-0(v-if="task.down") -{{task.counterDown}}
|
||||||
|
.d-flex.align-items-center(v-if="task.challenge && task.challenge.id")
|
||||||
|
.svg-icon.challenge(v-html="icons.challenge")
|
||||||
|
b-popover.tags-popover.no-span-margin(
|
||||||
|
:triggers="['hover']",
|
||||||
|
:placement="'bottom'",
|
||||||
|
:popover-style="{'max-width': '1000px'}",
|
||||||
|
)
|
||||||
|
.d-flex.align-items-center(slot="content")
|
||||||
|
.tags-popover-title(v-once) {{ `${$t('tags')}:` }}
|
||||||
|
.tag-label(v-for="tag in getTagsFor(task)") {{tag}}
|
||||||
|
.d-flex.align-items-center(v-if="task.tags && task.tags.length > 0")
|
||||||
|
.svg-icon.tags(v-html="icons.tags")
|
||||||
|
|
||||||
// Habits right side control
|
// Habits right side control
|
||||||
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
|
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
|
||||||
.task-control.habit-control(:class="controlClass.down + '-control-habit'", @click="isUser ? score('down') : null")
|
.task-control.habit-control(:class="controlClass.down + '-control-habit'", @click="isUser ? score('down') : null")
|
||||||
.svg-icon.negative(v-html="icons.negative")
|
.svg-icon.negative(v-html="icons.negative")
|
||||||
// Rewards right side control
|
// Rewards right side control
|
||||||
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass", @click="isUser ? score('down') : null")
|
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass", @click="isUser ? score('down') : null")
|
||||||
.svg-icon(v-html="icons.gold")
|
.svg-icon(v-html="icons.gold")
|
||||||
.small-text {{task.value}}
|
.small-text {{task.value}}
|
||||||
|
approval-footer(:task='task', v-if='this.task.group.id', :group='group')
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '~client/assets/scss/colors.scss';
|
@import '~client/assets/scss/colors.scss';
|
||||||
|
|
||||||
.task {
|
.task {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 1px 8px 0 rgba($black, 0.12), 0 4px 4px 0 rgba($black, 0.16);
|
box-shadow: 0 1px 8px 0 rgba($black, 0.12), 0 4px 4px 0 rgba($black, 0.16);
|
||||||
|
|
||||||
.left-control, .right-control, .task-content {
|
.left-control, .right-control, .task-content {
|
||||||
border-color: $purple-500;
|
border-color: $purple-500;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.task-title {
|
.task-title {
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
color: $gray-10;
|
color: $gray-10;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
|
|
||||||
&.has-notes {
|
&.has-notes {
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.task-notes {
|
.task-notes {
|
||||||
color: $gray-100;
|
color: $gray-100;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
|
||||||
|
|
||||||
.task-content {
|
|
||||||
padding: 8px;
|
|
||||||
flex-grow: 1;
|
|
||||||
cursor: pointer;
|
|
||||||
background: $white;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
|
|
||||||
&.no-right-border {
|
|
||||||
border-right: none !important;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.checklist {
|
.task-content {
|
||||||
margin-bottom: 2px;
|
padding: 8px;
|
||||||
margin-top: 8px;
|
flex-grow: 1;
|
||||||
}
|
cursor: pointer;
|
||||||
|
background: $white;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
.checklist-item {
|
&.no-right-border {
|
||||||
color: $gray-50;
|
border-right: none !important;
|
||||||
font-size: 14px;
|
}
|
||||||
line-height: 1.43;
|
}
|
||||||
margin-bottom: 10px;
|
|
||||||
min-height: 0px;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&-done {
|
.checklist {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist-item {
|
||||||
|
color: $gray-50;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.43;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
min-height: 0px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&-done {
|
||||||
|
color: $gray-300;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-control-indicator {
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-control-description {
|
||||||
|
margin-left: 6px;
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
margin-top: 4px;
|
||||||
color: $gray-300;
|
color: $gray-300;
|
||||||
text-decoration: line-through;
|
font-style: normal;
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-control-indicator {
|
.icons-right .svg-icon {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons span {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-span-margin span {
|
||||||
|
margin-left: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon.streak {
|
||||||
|
width: 11.6px;
|
||||||
|
height: 7.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags.svg-icon, .calendar.svg-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags:hover {
|
||||||
|
color: $purple-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.due-overdue {
|
||||||
|
color: $red-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar.svg-icon {
|
||||||
|
margin-right: 2px;
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-control-description {
|
.challenge.svg-icon {
|
||||||
margin-left: 6px;
|
width: 14px;
|
||||||
padding-top: 0px;
|
height: 12px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.icons {
|
.check.svg-icon {
|
||||||
margin-top: 4px;
|
width: 12.3px;
|
||||||
color: $gray-300;
|
height: 9.8px;
|
||||||
font-style: normal;
|
margin: 9px 8px;
|
||||||
|
|
||||||
&-right {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.icons-right .svg-icon {
|
.left-control, .right-control {
|
||||||
margin-left: 8px;
|
width: 40px;
|
||||||
}
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.icons span {
|
.left-control {
|
||||||
margin-left: 4px;
|
border-top-left-radius: 2px;
|
||||||
}
|
border-bottom-left-radius: 2px;
|
||||||
|
min-height: 60px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-right: none;
|
||||||
|
|
||||||
.no-span-margin span {
|
& + .task-content {
|
||||||
margin-left: 0px !important;
|
border-left: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.svg-icon.streak {
|
.right-control {
|
||||||
width: 11.6px;
|
border-top-right-radius: 2px;
|
||||||
height: 7.1px;
|
border-bottom-right-radius: 2px;
|
||||||
}
|
min-height: 56px;
|
||||||
|
border: 1px solid transparent;
|
||||||
.tags.svg-icon, .calendar.svg-icon {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags:hover {
|
|
||||||
color: $purple-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.due-overdue {
|
|
||||||
color: $red-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar.svg-icon {
|
|
||||||
margin-right: 2px;
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.challenge.svg-icon {
|
|
||||||
width: 14px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check.svg-icon {
|
|
||||||
width: 12.3px;
|
|
||||||
height: 9.8px;
|
|
||||||
margin: 9px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-control, .right-control {
|
|
||||||
width: 40px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-control {
|
|
||||||
border-top-left-radius: 2px;
|
|
||||||
border-bottom-left-radius: 2px;
|
|
||||||
min-height: 60px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-right: none;
|
|
||||||
|
|
||||||
& + .task-content {
|
|
||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.right-control {
|
|
||||||
border-top-right-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
min-height: 56px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-control, .reward-control {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-not-scoreable {
|
|
||||||
.task-control, .reward-control {
|
.task-control, .reward-control {
|
||||||
cursor: default !important;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-icon.check {
|
.task-not-scoreable {
|
||||||
display: none !important;
|
.task-control, .reward-control {
|
||||||
}
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.daily-todo-control {
|
.svg-icon.check {
|
||||||
margin-top: 16px;
|
display: none !important;
|
||||||
border-radius: 2px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.reward-control {
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-text {
|
.daily-todo-control {
|
||||||
margin-top: 4px;
|
margin-top: 16px;
|
||||||
color: $yellow-10;
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reward-control {
|
||||||
|
flex-direction: column;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-text {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: $yellow-10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss"> // not working as scoped css
|
<style lang="scss">
|
||||||
@import '~client/assets/scss/colors.scss';
|
// not working as scoped css
|
||||||
|
@import '~client/assets/scss/colors.scss';
|
||||||
|
|
||||||
.tags-popover {
|
.tags-popover {
|
||||||
// TODO fix padding, see https://github.com/bootstrap-vue/bootstrap-vue/issues/559#issuecomment-311150335
|
// TODO fix padding, see https://github.com/bootstrap-vue/bootstrap-vue/issues/559#issuecomment-311150335
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-popover-title {
|
.tags-popover-title {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-label {
|
.tag-label {
|
||||||
display: block;
|
display: block;
|
||||||
float: left;
|
float: left;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
background-color: $gray-50;
|
background-color: $gray-50;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
color: $gray-300;
|
color: $gray-300;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -292,16 +296,20 @@ import checkIcon from 'assets/svg/check.svg';
|
|||||||
import bPopover from 'bootstrap-vue/lib/components/popover';
|
import bPopover from 'bootstrap-vue/lib/components/popover';
|
||||||
import markdownDirective from 'client/directives/markdown';
|
import markdownDirective from 'client/directives/markdown';
|
||||||
import notifications from 'client/mixins/notifications';
|
import notifications from 'client/mixins/notifications';
|
||||||
|
import approvalHeader from './approvalHeader';
|
||||||
|
import approvalFooter from './approvalFooter';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [notifications],
|
mixins: [notifications],
|
||||||
components: {
|
components: {
|
||||||
bPopover,
|
bPopover,
|
||||||
|
approvalFooter,
|
||||||
|
approvalHeader,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
markdown: markdownDirective,
|
markdown: markdownDirective,
|
||||||
},
|
},
|
||||||
props: ['task', 'isUser'],
|
props: ['task', 'isUser', 'group'], // @TODO: maybe we should store the group on state?
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
|
|||||||
@@ -1,126 +1,149 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
form(
|
form(
|
||||||
v-if="task",
|
v-if="task",
|
||||||
@submit.stop.prevent="submit()",
|
@submit.stop.prevent="submit()",
|
||||||
)
|
|
||||||
b-modal#task-modal(
|
|
||||||
size="sm",
|
|
||||||
@hidden="cancel()",
|
|
||||||
)
|
)
|
||||||
.task-modal-header(
|
b-modal#task-modal(
|
||||||
slot="modal-header",
|
size="sm",
|
||||||
:class="[cssClass]",
|
@hidden="cancel()",
|
||||||
)
|
)
|
||||||
h1 {{ title }}
|
.task-modal-header(
|
||||||
.form-group
|
slot="modal-header",
|
||||||
label(v-once) {{ `${$t('title')}*` }}
|
:class="[cssClass]",
|
||||||
input.form-control(type='text', :class="[`${cssClass}-modal-input`]", required, v-model="task.text")
|
)
|
||||||
.form-group
|
h1 {{ title }}
|
||||||
label(v-once) {{ $t('notes') }}
|
.form-group
|
||||||
textarea.form-control(:class="[`${cssClass}-modal-input`]", v-model="task.notes", rows="3")
|
label(v-once) {{ `${$t('title')}*` }}
|
||||||
.task-modal-content
|
input.form-control(type='text', :class="[`${cssClass}-modal-input`]", required, v-model="task.text")
|
||||||
.option(v-if="task.type === 'reward'")
|
.form-group
|
||||||
label(v-once) {{ $t('cost') }}
|
label(v-once) {{ $t('notes') }}
|
||||||
input(type="number", v-model="task.value", required, min="0")
|
textarea.form-control(:class="[`${cssClass}-modal-input`]", v-model="task.notes", rows="3")
|
||||||
.option(v-if="['daily', 'todo'].indexOf(task.type) > -1")
|
.task-modal-content
|
||||||
label(v-once) {{ $t('checklist') }}
|
.option(v-if="task.type === 'reward'")
|
||||||
br
|
label(v-once) {{ $t('cost') }}
|
||||||
.inline-edit-input-group.checklist-group.input-group(v-for="(item, $index) in task.checklist")
|
input(type="number", v-model="task.value", required, min="0")
|
||||||
input.inline-edit-input.checklist-item.form-control(type="text", :value="item.text")
|
.option(v-if="['daily', 'todo'].indexOf(task.type) > -1")
|
||||||
span.input-group-btn(@click="removeChecklistItem($index)")
|
label(v-once) {{ $t('checklist') }}
|
||||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
br
|
||||||
input.inline-edit-input.checklist-item.form-control(type="text", :placeholder="$t('newChecklistItem')", @keydown.enter="addChecklistItem($event)", v-model="newChecklistItem")
|
.inline-edit-input-group.checklist-group.input-group(v-for="(item, $index) in task.checklist")
|
||||||
.d-flex.justify-content-center(v-if="task.type === 'habit'")
|
input.inline-edit-input.checklist-item.form-control(type="text", :value="item.text")
|
||||||
.option-item(:class="optionClass(task.up === true)", @click="task.up = !task.up")
|
span.input-group-btn(@click="removeChecklistItem($index)")
|
||||||
.option-item-box
|
.svg-icon.destroy-icon(v-html="icons.destroy")
|
||||||
.task-control.habit-control(:class="controlClass.up + '-control-habit'")
|
input.inline-edit-input.checklist-item.form-control(type="text", :placeholder="$t('newChecklistItem')", @keydown.enter="addChecklistItem($event)", v-model="newChecklistItem")
|
||||||
.svg-icon.positive(v-html="icons.positive")
|
.d-flex.justify-content-center(v-if="task.type === 'habit'")
|
||||||
.option-item-label(v-once) {{ $t('positive') }}
|
.option-item(:class="optionClass(task.up === true)", @click="task.up = !task.up")
|
||||||
.option-item(:class="optionClass(task.down === true)", @click="task.down = !task.down")
|
|
||||||
.option-item-box
|
|
||||||
.task-control.habit-control(:class="controlClass.down + '-control-habit'")
|
|
||||||
.svg-icon.negative(v-html="icons.negative")
|
|
||||||
.option-item-label(v-once) {{ $t('negative') }}
|
|
||||||
template(v-if="task.type !== 'reward'")
|
|
||||||
label(v-once)
|
|
||||||
span.float-left {{ $t('difficulty') }}
|
|
||||||
.svg-icon.info-icon(v-html="icons.information")
|
|
||||||
.d-flex.justify-content-center
|
|
||||||
.option-item(:class="optionClass(task.priority === 0.1)", @click="task.priority = 0.1")
|
|
||||||
.option-item-box
|
.option-item-box
|
||||||
.svg-icon.difficulty-trivial-icon(v-html="icons.difficultyTrivial")
|
.task-control.habit-control(:class="controlClass.up + '-control-habit'")
|
||||||
.option-item-label(v-once) {{ $t('trivial') }}
|
.svg-icon.positive(v-html="icons.positive")
|
||||||
.option-item(:class="optionClass(task.priority === 1)", @click="task.priority = 1")
|
.option-item-label(v-once) {{ $t('positive') }}
|
||||||
|
.option-item(:class="optionClass(task.down === true)", @click="task.down = !task.down")
|
||||||
.option-item-box
|
.option-item-box
|
||||||
.svg-icon.difficulty-normal-icon(v-html="icons.difficultyNormal")
|
.task-control.habit-control(:class="controlClass.down + '-control-habit'")
|
||||||
.option-item-label(v-once) {{ $t('easy') }}
|
.svg-icon.negative(v-html="icons.negative")
|
||||||
.option-item(:class="optionClass(task.priority === 1.5)", @click="task.priority = 1.5")
|
.option-item-label(v-once) {{ $t('negative') }}
|
||||||
.option-item-box
|
template(v-if="task.type !== 'reward'")
|
||||||
.svg-icon.difficulty-medium-icon(v-html="icons.difficultyMedium")
|
label(v-once)
|
||||||
.option-item-label(v-once) {{ $t('medium') }}
|
span.float-left {{ $t('difficulty') }}
|
||||||
.option-item(:class="optionClass(task.priority === 2)", @click="task.priority = 2")
|
.svg-icon.info-icon(v-html="icons.information")
|
||||||
.option-item-box
|
.d-flex.justify-content-center
|
||||||
.svg-icon.difficulty-hard-icon(v-html="icons.difficultyHard")
|
.option-item(:class="optionClass(task.priority === 0.1)", @click="task.priority = 0.1")
|
||||||
.option-item-label(v-once) {{ $t('hard') }}
|
.option-item-box
|
||||||
.option(v-if="task.type === 'todo'")
|
.svg-icon.difficulty-trivial-icon(v-html="icons.difficultyTrivial")
|
||||||
label(v-once) {{ $t('dueDate') }}
|
.option-item-label(v-once) {{ $t('trivial') }}
|
||||||
datepicker(v-model="task.date")
|
.option-item(:class="optionClass(task.priority === 1)", @click="task.priority = 1")
|
||||||
.option(v-if="task.type === 'daily'")
|
.option-item-box
|
||||||
label(v-once) {{ $t('startDate') }}
|
.svg-icon.difficulty-normal-icon(v-html="icons.difficultyNormal")
|
||||||
datepicker(v-model="task.startDate")
|
.option-item-label(v-once) {{ $t('easy') }}
|
||||||
.option(v-if="task.type === 'daily'")
|
.option-item(:class="optionClass(task.priority === 1.5)", @click="task.priority = 1.5")
|
||||||
label(v-once) {{ $t('repeats') }}
|
.option-item-box
|
||||||
b-dropdown(:text="$t(task.frequency)")
|
.svg-icon.difficulty-medium-icon(v-html="icons.difficultyMedium")
|
||||||
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly', 'yearly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
|
.option-item-label(v-once) {{ $t('medium') }}
|
||||||
| {{ $t(frequency) }}
|
.option-item(:class="optionClass(task.priority === 2)", @click="task.priority = 2")
|
||||||
label(v-once) {{ $t('repeatEvery') }}
|
.option-item-box
|
||||||
input.form-control(type="number", v-model="task.everyX", min="0", required)
|
.svg-icon.difficulty-hard-icon(v-html="icons.difficultyHard")
|
||||||
| {{ repeatSuffix }}
|
.option-item-label(v-once) {{ $t('hard') }}
|
||||||
br
|
.option(v-if="task.type === 'todo'")
|
||||||
template(v-if="task.frequency === 'weekly'")
|
label(v-once) {{ $t('dueDate') }}
|
||||||
.form-check-inline.weekday-check(
|
datepicker(v-model="task.date")
|
||||||
v-for="(day, dayNumber) in ['su','m','t','w','th','f','s']",
|
.option(v-if="task.type === 'daily'")
|
||||||
:key="dayNumber",
|
label(v-once) {{ $t('startDate') }}
|
||||||
)
|
datepicker(v-model="task.startDate")
|
||||||
label.custom-control.custom-checkbox
|
.option(v-if="task.type === 'daily'")
|
||||||
input.custom-control-input(type="checkbox", v-model="task.repeat[day]")
|
label(v-once) {{ $t('repeats') }}
|
||||||
|
b-dropdown(:text="$t(task.frequency)")
|
||||||
|
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly', 'yearly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
|
||||||
|
| {{ $t(frequency) }}
|
||||||
|
label(v-once) {{ $t('repeatEvery') }}
|
||||||
|
input.form-control(type="number", v-model="task.everyX", min="0", required)
|
||||||
|
| {{ repeatSuffix }}
|
||||||
|
br
|
||||||
|
template(v-if="task.frequency === 'weekly'")
|
||||||
|
.form-check-inline.weekday-check(
|
||||||
|
v-for="(day, dayNumber) in ['su','m','t','w','th','f','s']",
|
||||||
|
:key="dayNumber",
|
||||||
|
)
|
||||||
|
label.custom-control.custom-checkbox
|
||||||
|
input.custom-control-input(type="checkbox", v-model="task.repeat[day]")
|
||||||
|
span.custom-control-indicator
|
||||||
|
span.custom-control-description(v-once) {{ weekdaysMin(dayNumber) }}
|
||||||
|
template(v-if="task.frequency === 'monthly'")
|
||||||
|
label.custom-control.custom-radio
|
||||||
|
input.custom-control-input(type='radio', v-model="repeatsOn", value="dayOfMonth")
|
||||||
span.custom-control-indicator
|
span.custom-control-indicator
|
||||||
span.custom-control-description(v-once) {{ weekdaysMin(dayNumber) }}
|
span.custom-control-description {{ $t('dayOfMonth') }}
|
||||||
template(v-if="task.frequency === 'monthly'")
|
label.custom-control.custom-radio
|
||||||
label.custom-control.custom-radio
|
input.custom-control-input(type='radio', v-model="repeatsOn", value="dayOfWeek")
|
||||||
input.custom-control-input(type='radio', v-model="repeatsOn", value="dayOfMonth")
|
|
||||||
span.custom-control-indicator
|
|
||||||
span.custom-control-description {{ $t('dayOfMonth') }}
|
|
||||||
label.custom-control.custom-radio
|
|
||||||
input.custom-control-input(type='radio', v-model="repeatsOn", value="dayOfWeek")
|
|
||||||
span.custom-control-indicator
|
|
||||||
span.custom-control-description {{ $t('dayOfWeek') }}
|
|
||||||
|
|
||||||
.option
|
|
||||||
label(v-once) {{ $t('tags') }}
|
|
||||||
.category-wrap(@click="showTagsSelect = !showTagsSelect")
|
|
||||||
span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}}
|
|
||||||
span.category-select(v-else) {{getTagsFor(task)[0]}}
|
|
||||||
.category-box(v-if="showTagsSelect")
|
|
||||||
.form-check(
|
|
||||||
v-for="tag in user.tags",
|
|
||||||
:key="tag.id",
|
|
||||||
)
|
|
||||||
label.custom-control.custom-checkbox
|
|
||||||
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
|
|
||||||
span.custom-control-indicator
|
span.custom-control-indicator
|
||||||
span.custom-control-description(v-once) {{ tag.name }}
|
span.custom-control-description {{ $t('dayOfWeek') }}
|
||||||
button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}}
|
|
||||||
.option(v-if="task.type === 'habit'")
|
|
||||||
label(v-once) {{ $t('resetStreak') }}
|
|
||||||
b-dropdown(:text="$t(task.frequency)")
|
|
||||||
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
|
|
||||||
| {{ $t(frequency) }}
|
|
||||||
|
|
||||||
.task-modal-footer(slot="modal-footer")
|
.option
|
||||||
button.btn.btn-primary(type="submit", v-once) {{ $t('save') }}
|
label(v-once) {{ $t('tags') }}
|
||||||
span.cancel-task-btn(v-once, v-if="purpose === 'create'", @click="cancel()") {{ $t('cancel') }}
|
.category-wrap(@click="showTagsSelect = !showTagsSelect")
|
||||||
span.delete-task-btn(v-once, v-else, @click="destroy()") {{ $t('delete') }}
|
span.category-select(v-if='task.tags && task.tags.length === 0') {{$t('none')}}
|
||||||
|
span.category-select(v-else) {{getTagsFor(task)[0]}}
|
||||||
|
.category-box(v-if="showTagsSelect")
|
||||||
|
.form-check(
|
||||||
|
v-for="tag in user.tags",
|
||||||
|
:key="tag.id",
|
||||||
|
)
|
||||||
|
label.custom-control.custom-checkbox
|
||||||
|
input.custom-control-input(type="checkbox", :value="tag.id", v-model="task.tags")
|
||||||
|
span.custom-control-indicator
|
||||||
|
span.custom-control-description(v-once) {{ tag.name }}
|
||||||
|
button.btn.btn-primary(@click="showTagsSelect = !showTagsSelect") {{$t('close')}}
|
||||||
|
.option(v-if="task.type === 'habit'")
|
||||||
|
label(v-once) {{ $t('resetStreak') }}
|
||||||
|
b-dropdown(:text="$t(task.frequency)")
|
||||||
|
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
|
||||||
|
| {{ $t(frequency) }}
|
||||||
|
|
||||||
|
.option.group-options(v-if='groupId')
|
||||||
|
label(v-once) Assigned To
|
||||||
|
.category-wrap(@click="showAssignedSelect = !showAssignedSelect")
|
||||||
|
span.category-select(v-if='assignedMembers && assignedMembers.length === 0') {{$t('none')}}
|
||||||
|
span.category-select(v-else)
|
||||||
|
span(v-for='memberId in assignedMembers') {{memberNamesById[memberId]}}
|
||||||
|
.category-box(v-if="showAssignedSelect")
|
||||||
|
.form-check(
|
||||||
|
v-for="member in members",
|
||||||
|
:key="member._id",
|
||||||
|
)
|
||||||
|
label.custom-control.custom-checkbox
|
||||||
|
input.custom-control-input(type="checkbox", :value="member._id", v-model="assignedMembers", @change='toggleAssignment(member._id)')
|
||||||
|
span.custom-control-indicator
|
||||||
|
span.custom-control-description(v-once) {{ member.profile.name }}
|
||||||
|
button.btn.btn-primary(@click="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||||
|
|
||||||
|
.option.group-options(v-if='groupId')
|
||||||
|
label(v-once) Needs Approval
|
||||||
|
toggle-switch(:label='""',
|
||||||
|
:checked="requiresApproval",
|
||||||
|
@change="updateRequiresApproval")
|
||||||
|
|
||||||
|
.task-modal-footer(slot="modal-footer")
|
||||||
|
button.btn.btn-primary(type="submit", v-once) {{ $t('save') }}
|
||||||
|
span.cancel-task-btn(v-once, v-if="purpose === 'create'", @click="cancel()") {{ $t('cancel') }}
|
||||||
|
span.delete-task-btn(v-once, v-else, @click="destroy()") {{ $t('delete') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -325,6 +348,7 @@ import bModal from 'bootstrap-vue/lib/components/modal';
|
|||||||
import { mapGetters, mapActions, mapState } from 'client/libs/store';
|
import { mapGetters, mapActions, mapState } from 'client/libs/store';
|
||||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||||
|
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||||
import Datepicker from 'vuejs-datepicker';
|
import Datepicker from 'vuejs-datepicker';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import uuid from 'uuid';
|
import uuid from 'uuid';
|
||||||
@@ -344,11 +368,13 @@ export default {
|
|||||||
bDropdown,
|
bDropdown,
|
||||||
bDropdownItem,
|
bDropdownItem,
|
||||||
Datepicker,
|
Datepicker,
|
||||||
|
toggleSwitch,
|
||||||
},
|
},
|
||||||
props: ['task', 'purpose', 'challengeId'], // purpose is either create or edit, task is the task created or edited
|
props: ['task', 'purpose', 'challengeId', 'groupId'], // purpose is either create or edit, task is the task created or edited
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
showTagsSelect: false,
|
showTagsSelect: false,
|
||||||
|
showAssignedSelect: false,
|
||||||
newChecklistItem: null,
|
newChecklistItem: null,
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
information: informationIcon,
|
information: informationIcon,
|
||||||
@@ -360,8 +386,31 @@ export default {
|
|||||||
positive: positiveIcon,
|
positive: positiveIcon,
|
||||||
destroy: deleteIcon,
|
destroy: deleteIcon,
|
||||||
}),
|
}),
|
||||||
|
requiresApproval: false, // We can't set task.group fields so we use this field to toggle
|
||||||
|
members: [],
|
||||||
|
memberNamesById: {},
|
||||||
|
assignedMembers: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
async task () {
|
||||||
|
if (this.groupId && this.task.group && this.task.group.approval.required) {
|
||||||
|
this.requiresApproval = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.groupId) {
|
||||||
|
let members = await this.$store.dispatch('members:getGroupMembers', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
includeAllPublicFields: true,
|
||||||
|
});
|
||||||
|
this.members = members;
|
||||||
|
this.members.forEach(member => {
|
||||||
|
this.memberNamesById[member._id] = member.profile.name;
|
||||||
|
});
|
||||||
|
this.assignedMembers = this.task.group.assignedUsers;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
getTaskClasses: 'tasks:getTaskClasses',
|
getTaskClasses: 'tasks:getTaskClasses',
|
||||||
@@ -457,10 +506,21 @@ export default {
|
|||||||
tasks: [this.task],
|
tasks: [this.task],
|
||||||
});
|
});
|
||||||
this.$emit('taskCreated', this.task);
|
this.$emit('taskCreated', this.task);
|
||||||
|
} else if (this.groupId) {
|
||||||
|
this.$store.dispatch('tasks:createGroupTasks', {
|
||||||
|
groupId: this.groupId,
|
||||||
|
tasks: [this.task],
|
||||||
|
});
|
||||||
|
this.$emit('taskCreated', this.task);
|
||||||
} else {
|
} else {
|
||||||
this.createTask(this.task);
|
this.createTask(this.task);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (this.groupId) {
|
||||||
|
this.task.group.assignedUsers = this.assignedMembers;
|
||||||
|
this.task.requiresApproval = this.requiresApproval;
|
||||||
|
}
|
||||||
|
|
||||||
this.saveTask(this.task);
|
this.saveTask(this.task);
|
||||||
this.$emit('taskEdited', this.task);
|
this.$emit('taskEdited', this.task);
|
||||||
}
|
}
|
||||||
@@ -474,6 +534,24 @@ export default {
|
|||||||
this.showTagsSelect = false;
|
this.showTagsSelect = false;
|
||||||
this.$emit('cancel');
|
this.$emit('cancel');
|
||||||
},
|
},
|
||||||
|
updateRequiresApproval (newValue) {
|
||||||
|
let truthy = true;
|
||||||
|
if (!newValue) truthy = false; // This return undefined instad of false
|
||||||
|
this.requiresApproval = truthy;
|
||||||
|
},
|
||||||
|
async toggleAssignment (memberId) {
|
||||||
|
if (this.assignedMembers.indexOf(memberId) === -1) {
|
||||||
|
await this.$store.dispatch('tasks:unassignTask', {
|
||||||
|
taskId: this.task._id,
|
||||||
|
userId: this.user._id,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.$store.dispatch('tasks:assignTask', {
|
||||||
|
taskId: this.task._id,
|
||||||
|
userId: this.user._id,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
105
website/client/mixins/payments.js
Normal file
105
website/client/mixins/payments.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const STRIPE_PUB_KEY = process.env.STRIPE_PUB_KEY; // eslint-disable-line
|
||||||
|
import subscriptionBlocks from '../../common/script/content/subscriptionBlocks';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
showStripe (data) {
|
||||||
|
if (!this.checkGemAmount(data)) return;
|
||||||
|
|
||||||
|
let sub = false;
|
||||||
|
|
||||||
|
if (data.subscription) {
|
||||||
|
sub = data.subscription;
|
||||||
|
} else if (data.gift && data.gift.type === 'subscription') {
|
||||||
|
sub = data.gift.subscription.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub = sub && subscriptionBlocks[sub];
|
||||||
|
|
||||||
|
let amount = 500;// 500 = $5
|
||||||
|
if (sub) amount = sub.price * 100;
|
||||||
|
if (data.gift && data.gift.type === 'gems') amount = data.gift.gems.amount / 4 * 100;
|
||||||
|
if (data.group) amount = (sub.price + 3 * (data.group.memberCount - 1)) * 100;
|
||||||
|
|
||||||
|
this.StripeCheckout.open({
|
||||||
|
key: STRIPE_PUB_KEY,
|
||||||
|
address: false,
|
||||||
|
amount,
|
||||||
|
name: 'Habitica',
|
||||||
|
description: sub ? this.$t('subscribe') : this.$t('checkout'),
|
||||||
|
image: '/apple-touch-icon-144-precomposed.png',
|
||||||
|
panelLabel: sub ? this.$t('subscribe') : this.$t('checkout'),
|
||||||
|
token: async (res) => {
|
||||||
|
let url = '/stripe/checkout?a=a'; // just so I can concat &x=x below
|
||||||
|
|
||||||
|
if (data.groupToCreate) {
|
||||||
|
url = '/api/v3/groups/create-plan?a=a';
|
||||||
|
res.groupToCreate = data.groupToCreate;
|
||||||
|
res.paymentType = 'Stripe';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.gift) url += `&gift=${this.encodeGift(data.uuid, data.gift)}`;
|
||||||
|
if (data.subscription) url += `&sub=${sub.key}`;
|
||||||
|
if (data.coupon) url += `&coupon=${data.coupon}`;
|
||||||
|
if (data.groupId) url += `&groupId=${data.groupId}`;
|
||||||
|
|
||||||
|
let response = await axios.post(url, res);
|
||||||
|
|
||||||
|
let responseStatus = response.status;
|
||||||
|
if (responseStatus >= 400) {
|
||||||
|
alert(`Error: ${response.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newGroup = response.data.data;
|
||||||
|
if (newGroup && newGroup._id) {
|
||||||
|
// @TODO: Just append? or $emit?
|
||||||
|
this.$router.push(`/group-plans/${newGroup._id}/task-information`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload(true);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkGemAmount (data) {
|
||||||
|
let isGem = data && data.gift && data.gift.type === 'gems';
|
||||||
|
let notEnoughGem = isGem && (!data.gift.gems.amount || data.gift.gems.amount === 0);
|
||||||
|
if (notEnoughGem) {
|
||||||
|
Notification.error(this.$t('badAmountOfGemsToPurchase'), true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
amazonPaymentsInit (data) {
|
||||||
|
// @TODO: Do we need this? if (!this.isAmazonReady) return;
|
||||||
|
if (!this.checkGemAmount(data)) return;
|
||||||
|
if (data.type !== 'single' && data.type !== 'subscription') return;
|
||||||
|
|
||||||
|
if (data.gift) {
|
||||||
|
if (data.gift.gems && data.gift.gems.amount && data.gift.gems.amount <= 0) return;
|
||||||
|
data.gift.uuid = data.giftedTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.subscription) {
|
||||||
|
this.amazonPayments.subscription = data.subscription;
|
||||||
|
this.amazonPayments.coupon = data.coupon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.groupId) {
|
||||||
|
this.amazonPayments.groupId = data.groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.groupToCreate) {
|
||||||
|
this.amazonPayments.groupToCreate = data.groupToCreate;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.amazonPayments.gift = data.gift;
|
||||||
|
this.amazonPayments.type = data.type;
|
||||||
|
|
||||||
|
this.$root.$emit('show::modal', 'amazon-payment');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -71,6 +71,10 @@ const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './com
|
|||||||
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/guild');
|
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/guild');
|
||||||
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/groupPlan');
|
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/groupPlan');
|
||||||
|
|
||||||
|
// Group Plans
|
||||||
|
const GroupPlanIndex = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/index');
|
||||||
|
const GroupPlanTaskInformation = () => import(/* webpackChunkName: "group-plans" */ './components/group-plans/taskInformation');
|
||||||
|
|
||||||
// Challenges
|
// Challenges
|
||||||
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index');
|
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index');
|
||||||
const MyChallenges = () => import(/* webpackChunkName: "challenges" */ './components/challenges/myChallenges');
|
const MyChallenges = () => import(/* webpackChunkName: "challenges" */ './components/challenges/myChallenges');
|
||||||
@@ -122,6 +126,26 @@ const router = new VueRouter({
|
|||||||
},
|
},
|
||||||
{ name: 'party', path: '/party', component: GuildPage },
|
{ name: 'party', path: '/party', component: GuildPage },
|
||||||
{ name: 'groupPlan', path: '/group-plans', component: GroupPlansAppPage },
|
{ name: 'groupPlan', path: '/group-plans', component: GroupPlansAppPage },
|
||||||
|
{
|
||||||
|
name: 'groupPlanDetail',
|
||||||
|
path: '/group-plans/:groupId',
|
||||||
|
component: GroupPlanIndex,
|
||||||
|
props: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'groupPlanDetailTaskInformation',
|
||||||
|
path: '/group-plans/:groupId/task-information',
|
||||||
|
component: GroupPlanTaskInformation,
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'groupPlanDetailInformation',
|
||||||
|
path: '/group-plans/:groupId/information',
|
||||||
|
component: GuildPage,
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/groups',
|
path: '/groups',
|
||||||
component: GuildIndex,
|
component: GuildIndex,
|
||||||
|
|||||||
@@ -165,3 +165,8 @@ export async function removeManager (store, payload) {
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getGroupPlans () {
|
||||||
|
let response = await axios.get('/api/v3/group-plans');
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|||||||
@@ -143,3 +143,33 @@ export async function createChallengeTasks (store, payload) {
|
|||||||
let response = await axios.post(`/api/v3/tasks/challenge/${payload.challengeId}`, payload.tasks);
|
let response = await axios.post(`/api/v3/tasks/challenge/${payload.challengeId}`, payload.tasks);
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getGroupTasks (store, payload) {
|
||||||
|
let response = await axios.get(`/api/v3/tasks/group/${payload.groupId}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createGroupTasks (store, payload) {
|
||||||
|
let response = await axios.post(`/api/v3/tasks/group/${payload.groupId}`, payload.tasks);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assignTask (store, payload) {
|
||||||
|
let response = await axios.post(`/api/v3/tasks/${payload.taskId}/assign/${payload.userId}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unassignTask (store, payload) {
|
||||||
|
let response = await axios.post(`/api/v3/tasks/${payload.taskId}/unassign/${payload.userId}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGroupApprovals (store, payload) {
|
||||||
|
let response = await axios.get(`/api/v3/approvals/group/${payload.groupId}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approve (store, payload) {
|
||||||
|
let response = await axios.post(`/api/v3/tasks/${payload.taskId}/approve/${payload.userId}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1248,4 +1248,41 @@ api.removeGroupManager = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /api/v3/group-plans Get group plans for a user
|
||||||
|
* @apiName GetGroupPlans
|
||||||
|
* @apiGroup Group
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object[]} data An array of the requested groups with a group plan (See <a href="https://github.com/HabitRPG/habitica/blob/develop/website/server/models/group.js" target="_blank">/website/server/models/group.js</a>)
|
||||||
|
*
|
||||||
|
* @apiSuccessExample {json} Groups the user is in with a group plan:
|
||||||
|
* HTTP/1.1 200 OK
|
||||||
|
* [
|
||||||
|
* {groupPlans}
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
api.getGroupPlans = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/group-plans',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
const userGroups = user.getGroups();
|
||||||
|
|
||||||
|
const groups = await Group
|
||||||
|
.find({
|
||||||
|
_id: {$in: userGroups},
|
||||||
|
})
|
||||||
|
.select('leaderOnly leader purchased name')
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
let groupPlans = groups.filter(group => {
|
||||||
|
return group.isSubscribed();
|
||||||
|
});
|
||||||
|
|
||||||
|
res.respond(200, groupPlans);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ api.updateTask = {
|
|||||||
// repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject()
|
// repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject()
|
||||||
// see https://github.com/Automattic/mongoose/issues/2749
|
// see https://github.com/Automattic/mongoose/issues/2749
|
||||||
|
|
||||||
|
task.group.approval.required = false;
|
||||||
if (sanitizedObj.requiresApproval) {
|
if (sanitizedObj.requiresApproval) {
|
||||||
task.group.approval.required = true;
|
task.group.approval.required = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user