Merge branch 'develop' into sabrecat/teams-rebase

This commit is contained in:
SabreCat
2022-04-07 14:55:16 -05:00
45 changed files with 1632 additions and 1274 deletions
@@ -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', {