Merge branch 'develop' into sabrecat/teams-rebase
This commit is contained in:
@@ -7,7 +7,7 @@ import { setup as setupPayments } from '@/libs/payments';
|
||||
|
||||
setupPayments();
|
||||
|
||||
storiesOf('Payments Buttons', module)
|
||||
storiesOf('Subscriptions/Payments Buttons', module)
|
||||
.add('simple', () => ({
|
||||
components: { PaymentsButtonsList },
|
||||
template: `
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<h5>{{ $t('dayStartAdjustment') }}</h5>
|
||||
<div class="mb-4">
|
||||
{{ $t('customDayStartInfo1') }}
|
||||
</div>
|
||||
<h3 v-once>{{ $t('adjustment') }}</h3>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="">
|
||||
<select
|
||||
v-model="newDayStart"
|
||||
class="form-control"
|
||||
>
|
||||
<option
|
||||
v-for="option in dayStartOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary full-width mt-3"
|
||||
:disabled="newDayStart === user.preferences.dayStart"
|
||||
@click="openDayStartModal()"
|
||||
>
|
||||
{{ $t('save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<small>
|
||||
<p v-html="$t('timezoneUTC', {utc: timezoneOffsetToUtc})"></p>
|
||||
<p v-html="$t('timezoneInfo')"></p>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import moment from 'moment';
|
||||
import getUtcOffset from '../../../../common/script/fns/getUtcOffset';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
export default {
|
||||
name: 'dayStartAdjustment',
|
||||
data () {
|
||||
const dayStartOptions = [];
|
||||
for (let number = 0; number <= 12; number += 1) {
|
||||
const meridian = number < 12 ? 'AM' : 'PM';
|
||||
const hour = number % 12;
|
||||
const timeWithMeridian = `(${hour || 12}:00 ${meridian})`;
|
||||
const option = {
|
||||
value: number,
|
||||
name: `+${number} hours ${timeWithMeridian}`,
|
||||
};
|
||||
|
||||
if (number === 0) {
|
||||
option.name = `Default ${timeWithMeridian}`;
|
||||
}
|
||||
|
||||
dayStartOptions.push(option);
|
||||
}
|
||||
|
||||
return {
|
||||
newDayStart: 0,
|
||||
dayStartOptions,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.newDayStart = this.user.preferences.dayStart;
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
timezoneOffsetToUtc () {
|
||||
const offsetString = moment().utcOffset(getUtcOffset(this.user)).format('Z');
|
||||
return `UTC${offsetString}`;
|
||||
},
|
||||
dayStart () {
|
||||
return this.user.preferences.dayStart;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async saveDayStart () {
|
||||
this.user.preferences.dayStart = this.newDayStart;
|
||||
await axios.post('/api/v4/user/custom-day-start', {
|
||||
dayStart: this.newDayStart,
|
||||
});
|
||||
// @TODO
|
||||
// Notification.text(response.data.data.message);
|
||||
},
|
||||
openDayStartModal () {
|
||||
const nextCron = this.calculateNextCron();
|
||||
// @TODO: Add generic modal
|
||||
if (!window.confirm(this.$t('sureChangeCustomDayStartTime', { time: nextCron }))) return; // eslint-disable-line no-alert
|
||||
this.saveDayStart();
|
||||
// $rootScope.openModal('change-day-start', { scope: $scope });
|
||||
},
|
||||
calculateNextCron () {
|
||||
let nextCron = moment()
|
||||
.hours(this.newDayStart)
|
||||
.minutes(0)
|
||||
.seconds(0)
|
||||
.milliseconds(0);
|
||||
|
||||
const currentHour = moment().format('H');
|
||||
if (currentHour >= this.newDayStart) {
|
||||
nextCron = nextCron.add(1, 'day');
|
||||
}
|
||||
|
||||
return nextCron.format(`${this.user.preferences.dateFormat.toUpperCase()} @ h:mm a`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -213,49 +213,7 @@
|
||||
{{ $t('enableClass') }}
|
||||
</button>
|
||||
<hr>
|
||||
<div>
|
||||
<h5>{{ $t('customDayStart') }}</h5>
|
||||
<div class="alert alert-warning">
|
||||
{{ $t('customDayStartInfo1') }}
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-7">
|
||||
<select
|
||||
v-model="newDayStart"
|
||||
class="form-control"
|
||||
>
|
||||
<option
|
||||
v-for="option in dayStartOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<button
|
||||
class="btn btn-block btn-primary mt-1"
|
||||
:disabled="newDayStart === user.preferences.dayStart"
|
||||
@click="openDayStartModal()"
|
||||
>
|
||||
{{ $t('saveCustomDayStart') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<h5>{{ $t('timezone') }}</h5>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-12">
|
||||
<p v-html="$t('timezoneUTC', {utc: timezoneOffsetToUtc})"></p>
|
||||
<p v-html="$t('timezoneInfo')"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<day-start-adjustment />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
@@ -568,16 +526,15 @@
|
||||
|
||||
<script>
|
||||
import hello from 'hellojs';
|
||||
import moment from 'moment';
|
||||
import axios from 'axios';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
import restoreModal from './restoreModal';
|
||||
import resetModal from './resetModal';
|
||||
import deleteModal from './deleteModal';
|
||||
import dayStartAdjustment from './dayStartAdjustment';
|
||||
import { SUPPORTED_SOCIAL_NETWORKS } from '@/../../common/script/constants';
|
||||
import changeClass from '@/../../common/script/ops/changeClass';
|
||||
import getUtcOffset from '@/../../common/script/fns/getUtcOffset';
|
||||
import notificationsMixin from '../../mixins/notifications';
|
||||
import sounds from '../../libs/sounds';
|
||||
import { buildAppleAuthUrl } from '../../libs/auth';
|
||||
@@ -590,27 +547,15 @@ export default {
|
||||
restoreModal,
|
||||
resetModal,
|
||||
deleteModal,
|
||||
dayStartAdjustment,
|
||||
},
|
||||
mixins: [notificationsMixin],
|
||||
data () {
|
||||
const dayStartOptions = [];
|
||||
for (let number = 0; number < 24; number += 1) {
|
||||
const meridian = number < 12 ? 'AM' : 'PM';
|
||||
const hour = number % 12;
|
||||
const option = {
|
||||
value: number,
|
||||
name: `${hour || 12}:00 ${meridian}`,
|
||||
};
|
||||
dayStartOptions.push(option);
|
||||
}
|
||||
|
||||
return {
|
||||
SOCIAL_AUTH_NETWORKS: [],
|
||||
party: {},
|
||||
// Made available by the server as a script
|
||||
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
||||
dayStartOptions,
|
||||
newDayStart: 0,
|
||||
temporaryDisplayName: '',
|
||||
usernameUpdates: { username: '' },
|
||||
emailUpdates: {},
|
||||
@@ -634,13 +579,6 @@ export default {
|
||||
availableAudioThemes () {
|
||||
return ['off', ...this.content.audioThemes];
|
||||
},
|
||||
timezoneOffsetToUtc () {
|
||||
const offsetString = moment().utcOffset(getUtcOffset(this.user)).format('Z');
|
||||
return `UTC${offsetString}`;
|
||||
},
|
||||
dayStart () {
|
||||
return this.user.preferences.dayStart;
|
||||
},
|
||||
hasClass () {
|
||||
return this.$store.getters['members:hasClass'](this.user);
|
||||
},
|
||||
@@ -690,7 +628,6 @@ export default {
|
||||
this.SOCIAL_AUTH_NETWORKS = SUPPORTED_SOCIAL_NETWORKS;
|
||||
// @TODO: We may need to request the party here
|
||||
this.party = this.$store.state.party;
|
||||
this.newDayStart = this.user.preferences.dayStart;
|
||||
this.usernameUpdates.username = this.user.auth.local.username || null;
|
||||
this.temporaryDisplayName = this.user.profile.name;
|
||||
this.emailUpdates.newEmail = this.user.auth.local.email || null;
|
||||
@@ -790,32 +727,6 @@ export default {
|
||||
return false;
|
||||
});
|
||||
},
|
||||
calculateNextCron () {
|
||||
let nextCron = moment().hours(this.newDayStart).minutes(0).seconds(0)
|
||||
.milliseconds(0);
|
||||
|
||||
const currentHour = moment().format('H');
|
||||
if (currentHour >= this.newDayStart) {
|
||||
nextCron = nextCron.add(1, 'day');
|
||||
}
|
||||
|
||||
return nextCron.format(`${this.user.preferences.dateFormat.toUpperCase()} @ h:mm a`);
|
||||
},
|
||||
openDayStartModal () {
|
||||
const nextCron = this.calculateNextCron();
|
||||
// @TODO: Add generic modal
|
||||
if (!window.confirm(this.$t('sureChangeCustomDayStartTime', { time: nextCron }))) return; // eslint-disable-line no-alert
|
||||
this.saveDayStart();
|
||||
// $rootScope.openModal('change-day-start', { scope: $scope });
|
||||
},
|
||||
async saveDayStart () {
|
||||
this.user.preferences.dayStart = this.newDayStart;
|
||||
await axios.post('/api/v4/user/custom-day-start', {
|
||||
dayStart: this.newDayStart,
|
||||
});
|
||||
// @TODO
|
||||
// Notification.text(response.data.data.message);
|
||||
},
|
||||
async changeLanguage (e) {
|
||||
const newLang = e.target.value;
|
||||
this.user.preferences.language = newLang;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { storiesOf } from '@storybook/vue';
|
||||
|
||||
import Subscription from './subscription.vue';
|
||||
import { mockStore } from '../../../config/storybook/mock.data';
|
||||
|
||||
storiesOf('Subscriptions/Detail Page', module)
|
||||
.add('subscribed', () => ({
|
||||
components: { Subscription },
|
||||
template: `
|
||||
<div style="position: absolute; margin: 20px">
|
||||
<subscription ></subscription>
|
||||
</div>
|
||||
`,
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
},
|
||||
store: mockStore({
|
||||
userData: {
|
||||
purchased: {
|
||||
plan: {
|
||||
customerId: 'customer-id',
|
||||
planId: 'plan-id',
|
||||
subscriptionId: 'sub-id',
|
||||
gemsBought: 22,
|
||||
dateUpdated: new Date(2021, 0, 15),
|
||||
consecutive: {
|
||||
count: 2,
|
||||
gemCapExtra: 4,
|
||||
offset: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
@@ -93,7 +93,7 @@
|
||||
<div class="subscribe-card mx-auto">
|
||||
<div
|
||||
v-if="hasSubscription && !hasCanceledSubscription"
|
||||
class="d-flex flex-column align-items-center my-4"
|
||||
class="d-flex flex-column align-items-center"
|
||||
>
|
||||
<div class="round-container bg-green-10 d-flex align-items-center justify-content-center">
|
||||
<div
|
||||
@@ -102,7 +102,7 @@
|
||||
v-html="icons.checkmarkIcon"
|
||||
></div>
|
||||
</div>
|
||||
<h2 class="green-10 mx-auto">
|
||||
<h2 class="green-10 mx-auto mb-75">
|
||||
{{ $t('youAreSubscribed') }}
|
||||
</h2>
|
||||
<div
|
||||
@@ -180,17 +180,17 @@
|
||||
</div>
|
||||
<div
|
||||
v-if="hasSubscription"
|
||||
class="bg-gray-700 p-2 text-center"
|
||||
class="bg-gray-700 py-3 mt-4 mb-3 text-center"
|
||||
>
|
||||
<div class="header-mini mb-3">
|
||||
{{ $t('subscriptionStats') }}
|
||||
</div>
|
||||
<div class="d-flex justify-content-around">
|
||||
<div class="ml-4 mr-3">
|
||||
<div class="d-flex">
|
||||
<div class="stat-column">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-calendar mr-2"
|
||||
class="svg-icon svg-calendar mr-1"
|
||||
v-html="icons.calendarIcon"
|
||||
>
|
||||
</div>
|
||||
@@ -204,49 +204,53 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-spacer"></div>
|
||||
<div>
|
||||
<div class="stat-column">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-gem mr-2"
|
||||
class="svg-icon svg-gem mr-1"
|
||||
v-html="icons.gemIcon"
|
||||
>
|
||||
</div>
|
||||
<div class="number-heavy">
|
||||
{{ user.purchased.plan.consecutive.gemCapExtra }}
|
||||
{{ gemCap }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-label">
|
||||
{{ $t('gemCapExtra') }}
|
||||
{{ $t('gemCap') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-spacer"></div>
|
||||
<div>
|
||||
<div class="stat-column">
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-hourglass mt-1 mr-2"
|
||||
class="svg-icon svg-hourglass mt-1 mr-1"
|
||||
v-html="icons.hourglassIcon"
|
||||
>
|
||||
</div>
|
||||
<div class="number-heavy">
|
||||
{{ user.purchased.plan.consecutive.trinkets }}
|
||||
{{ nextHourGlass }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats-label">
|
||||
{{ $t('mysticHourglassesTooltip') }}
|
||||
{{ $t('nextHourglass') }}*
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 nextHourglassDescription" v-once>
|
||||
*{{ $t('nextHourglassDescription') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-center align-items-center mt-4 mb-3">
|
||||
<div class="d-flex flex-column justify-content-center align-items-center mb-3">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon svg-heart mb-1"
|
||||
class="svg-icon svg-heart mb-2"
|
||||
v-html="icons.heartIcon"
|
||||
>
|
||||
</div>
|
||||
<div class="stats-label">
|
||||
<div class="thanks-for-support">
|
||||
{{ $t('giftSubscriptionText4') }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -350,7 +354,7 @@
|
||||
.cancel-card {
|
||||
width: 28rem;
|
||||
border: 2px solid $gray-500;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
@@ -405,7 +409,10 @@
|
||||
}
|
||||
|
||||
.number-heavy {
|
||||
font-size: 24px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.Pet-Jackalope-RoyalPurple {
|
||||
@@ -423,7 +430,10 @@
|
||||
|
||||
.stats-label {
|
||||
font-size: 12px;
|
||||
color: $gray-200;
|
||||
color: $gray-100;
|
||||
margin-top: 6px;
|
||||
font-weight: bold;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.stats-spacer {
|
||||
@@ -433,8 +443,9 @@
|
||||
}
|
||||
|
||||
.subscribe-card {
|
||||
padding-top: 2rem;
|
||||
width: 28rem;
|
||||
border-radius: 4px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
background-color: $white;
|
||||
}
|
||||
@@ -452,7 +463,14 @@
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.svg-calendar, .svg-heart {
|
||||
.svg-calendar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.svg-heart {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
@@ -479,8 +497,10 @@
|
||||
}
|
||||
|
||||
.svg-gem {
|
||||
width: 32px;
|
||||
height: 28px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.svg-gems {
|
||||
@@ -494,8 +514,10 @@
|
||||
}
|
||||
|
||||
.svg-hourglass {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.svg-gift-box {
|
||||
@@ -521,11 +543,34 @@
|
||||
.w-55 {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.nextHourglassDescription {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
line-height: 1.33;
|
||||
color: $gray-100;
|
||||
margin-left: 100px;
|
||||
margin-right: 100px;
|
||||
}
|
||||
|
||||
.justify-content-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.thanks-for-support {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
text-align: center;
|
||||
color: $gray-100;
|
||||
}
|
||||
|
||||
.stat-column {
|
||||
width: 33%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import min from 'lodash/min';
|
||||
import moment from 'moment';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
@@ -551,6 +596,7 @@ import logo from '@/assets/svg/habitica-logo-purple.svg';
|
||||
import paypalLogo from '@/assets/svg/paypal-logo.svg';
|
||||
import subscriberGems from '@/assets/svg/subscriber-gems.svg';
|
||||
import subscriberHourglasses from '@/assets/svg/subscriber-hourglasses.svg';
|
||||
import { getPlanContext } from '@/../../common/script/cron';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -649,23 +695,9 @@ export default {
|
||||
months: parseFloat(this.user.purchased.plan.extraMonths).toFixed(2),
|
||||
};
|
||||
},
|
||||
buyGemsGoldCap () {
|
||||
return {
|
||||
amount: min(this.gemGoldCap),
|
||||
};
|
||||
},
|
||||
gemGoldCap () {
|
||||
const baseCap = 25;
|
||||
const gemCapIncrement = 5;
|
||||
const capIncrementThreshold = 3;
|
||||
const { gemCapExtra } = this.user.purchased.plan.consecutive;
|
||||
const blocks = subscriptionBlocks[this.subscription.key].months / capIncrementThreshold;
|
||||
const flooredBlocks = Math.floor(blocks);
|
||||
|
||||
const userTotalDropCap = baseCap + gemCapExtra + flooredBlocks * gemCapIncrement;
|
||||
const maxDropCap = 50;
|
||||
|
||||
return [userTotalDropCap, maxDropCap];
|
||||
gemCap () {
|
||||
return planGemLimits.convCap
|
||||
+ this.user.purchased.plan.consecutive.gemCapExtra;
|
||||
},
|
||||
numberOfMysticHourglasses () {
|
||||
const numberOfHourglasses = subscriptionBlocks[this.subscription.key].months / 3;
|
||||
@@ -719,6 +751,16 @@ export default {
|
||||
subscriptionEndDate () {
|
||||
return moment(this.user.purchased.plan.dateTerminated).format('MM/DD/YYYY');
|
||||
},
|
||||
nextHourGlassDate () {
|
||||
const currentPlanContext = getPlanContext(this.user, new Date());
|
||||
|
||||
return currentPlanContext.nextHourglassDate;
|
||||
},
|
||||
nextHourGlass () {
|
||||
const nextHourglassMonth = this.nextHourGlassDate.format('MMM');
|
||||
|
||||
return nextHourglassMonth;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
|
||||
Reference in New Issue
Block a user