web/satellite/vuetify-poc: enable/disable billing features depending on config value

Added client side logic to disable billing features depending on config value.

Issue:
https://github.com/storj/storj-private/issues/464

Change-Id: I80ead8c91a39a387a1651efc700ca2d2341b6e0f
This commit is contained in:
Vitalii 2023-10-18 17:47:23 +03:00 committed by Storj Robot
parent 5c49ba1d85
commit b0a52f4b51
8 changed files with 132 additions and 62 deletions

View File

@ -32,7 +32,7 @@
/>
<v-banner
v-if="isLowBalance && parentRef"
v-if="isLowBalance && parentRef && billingEnabled"
class="all-dashboard-banners__low-balance"
message="Your STORJ Token balance is low. Deposit more STORJ tokens or add a credit card to avoid interruptions in service."
link-text="Go to billing"

View File

@ -17,7 +17,7 @@
</template>
<v-card-title class="font-weight-bold">
{{ !isProjectLimitReached ? 'Create New Project' : 'Get More Projects' }}
{{ isProjectLimitReached && billingEnabled ? 'Get More Projects' : 'Create New Project' }}
</v-card-title>
<template #append>
@ -36,7 +36,10 @@
<v-form v-model="formValid" class="pa-7" @submit.prevent>
<v-row>
<template v-if="!isProjectLimitReached">
<v-col v-if="isProjectLimitReached && billingEnabled">
Upgrade to Pro Account to create more projects and gain access to higher limits.
</v-col>
<template v-else>
<v-col cols="12">
Projects are where you and your team can upload and manage data, and view usage statistics and billing.
</v-col>
@ -76,9 +79,6 @@
/>
</v-col>
</template>
<v-col v-else>
Upgrade to Pro Account to create more projects and gain access to higher limits.
</v-col>
</v-row>
</v-form>
@ -97,10 +97,10 @@
variant="flat"
:loading="isLoading"
block
:append-icon="isProjectLimitReached ? 'mdi-arrow-right' : undefined"
:append-icon="isProjectLimitReached && billingEnabled ? 'mdi-arrow-right' : undefined"
@click="onPrimaryClick"
>
{{ !isProjectLimitReached ? 'Create Project' : 'Upgrade' }}
{{ isProjectLimitReached && billingEnabled ? 'Upgrade' : 'Create Project' }}
</v-btn>
</v-col>
</v-row>
@ -146,6 +146,8 @@ import { useProjectsStore } from '@/store/modules/projectsStore';
import { useUsersStore } from '@/store/modules/usersStore';
import { useNotify } from '@/utils/hooks';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useConfigStore } from '@/store/modules/configStore';
import { useAppStore } from '@poc/store/appStore';
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
@ -164,6 +166,9 @@ const model = computed<boolean>({
const projectsStore = useProjectsStore();
const usersStore = useUsersStore();
const configStore = useConfigStore();
const appStore = useAppStore();
const { isLoading, withLoading } = useLoading();
const notify = useNotify();
const router = useRouter();
@ -184,11 +189,21 @@ const descriptionRules: ValidationRule<string>[] = [
v => v.length <= MAX_DESCRIPTION_LENGTH || 'Description is too long',
];
/**
* Indicates if billing features are enabled.
*/
const billingEnabled = computed<boolean>(() => configStore.state.config.billingFeaturesEnabled);
function startUpgradeFlow(): void {
model.value = false;
appStore.toggleUpgradeFlow(true);
}
/**
* Handles primary button click.
*/
async function onPrimaryClick(): Promise<void> {
if (isProjectLimitReached.value) {
if (isProjectLimitReached.value && billingEnabled.value) {
isUpgradeDialogShown.value = true;
return;
}

View File

@ -30,7 +30,7 @@
</template>
</navigation-item>
<navigation-item title="Account Billing" to="billing">
<navigation-item v-if="billingEnabled" title="Account Billing" to="billing">
<template #prepend>
<icon-card />
</template>
@ -54,6 +54,7 @@ import { useDisplay } from 'vuetify';
import { useAppStore } from '@poc/store/appStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useConfigStore } from '@/store/modules/configStore';
import IconCard from '@poc/components/icons/IconCard.vue';
import IconSettings from '@poc/components/icons/IconSettings.vue';
@ -62,6 +63,7 @@ import NavigationItem from '@poc/layouts/default/NavigationItem.vue';
const analyticsStore = useAnalyticsStore();
const appStore = useAppStore();
const configStore = useConfigStore();
const { mdAndDown } = useDisplay();
@ -70,6 +72,11 @@ const model = computed<boolean>({
set: value => appStore.toggleNavigationDrawer(value),
});
/**
* Indicates if billing features are enabled.
*/
const billingEnabled = computed<boolean>(() => configStore.state.config.billingFeaturesEnabled);
/**
* Returns the path to the most recent non-account-related page.
*/

View File

@ -87,7 +87,7 @@
<!-- My Account Menu -->
<v-list class="px-2 rounded-lg">
<v-list-item class="py-2 rounded-lg">
<v-list-item v-if="billingEnabled" class="py-2 rounded-lg">
<!-- <template #prepend>
<icon-team size="18"></icon-team>
</template> -->
@ -104,16 +104,18 @@
</v-list-item-title>
</v-list-item>
<v-list-item v-if="!isPaidTier" link class="my-1 rounded-lg" @click="toggleUpgradeFlow">
<template #prepend>
<icon-upgrade size="18" />
</template>
<v-list-item-title class="text-body-2 ml-3">
Upgrade to Pro
</v-list-item-title>
</v-list-item>
<template v-if="billingEnabled">
<v-list-item v-if="!isPaidTier" link class="my-1 rounded-lg" @click="toggleUpgradeFlow">
<template #prepend>
<icon-upgrade size="18" />
</template>
<v-list-item-title class="text-body-2 ml-3">
Upgrade to Pro
</v-list-item-title>
</v-list-item>
<v-divider class="my-2" />
<v-divider class="my-2" />
</template>
<v-list-item class="py-2 rounded-lg">
<template #prepend>
@ -127,7 +129,7 @@
<v-divider class="my-2" />
<v-list-item link class="my-1 rounded-lg" router-link to="/account/billing" @click="closeSideNav">
<v-list-item v-if="billingEnabled" link class="my-1 rounded-lg" router-link to="/account/billing" @click="closeSideNav">
<template #prepend>
<icon-card size="18" />
</template>
@ -231,6 +233,11 @@ const props = withDefaults(defineProps<{
showNavDrawerButton: false,
});
/**
* Indicates if billing features are enabled.
*/
const billingEnabled = computed<boolean>(() => configStore.state.config.billingFeaturesEnabled);
/**
* Returns the name of the current satellite.
*/

View File

@ -17,7 +17,7 @@
</template>
<script setup lang="ts">
import { onBeforeMount, ref, watch } from 'vue';
import { computed, onBeforeMount, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { VApp, VProgressCircular } from 'vuetify/components';
@ -36,6 +36,7 @@ import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify } from '@/utils/hooks';
import { useConfigStore } from '@/store/modules/configStore';
import SessionWrapper from '@poc/components/utils/SessionWrapper.vue';
import UpgradeAccountDialog from '@poc/components/dialogs/upgradeAccountFlow/UpgradeAccountDialog.vue';
@ -51,9 +52,15 @@ const abTestingStore = useABTestingStore();
const projectsStore = useProjectsStore();
const appStore = useAppStore();
const agStore = useAccessGrantsStore();
const configStore = useConfigStore();
const isLoading = ref<boolean>(true);
/**
* Indicates if billing features are enabled.
*/
const billingEnabled = computed<boolean>(() => configStore.state.config.billingFeaturesEnabled);
/**
* Selects the project with the given URL ID, redirecting to the
* all projects dashboard if no such project exists.
@ -121,11 +128,13 @@ onBeforeMount(async () => {
return;
}
try {
await billingStore.setupAccount();
} catch (error) {
error.message = `Unable to setup account. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
if (billingEnabled.value) {
try {
await billingStore.setupAccount();
} catch (error) {
error.message = `Unable to setup account. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
}
await selectProject(route.params.id as string);

View File

@ -104,6 +104,16 @@ export const router = createRouter({
routes,
});
router.beforeEach((to, _, next) => {
const configStore = useConfigStore();
if (!configStore.state.config.billingFeaturesEnabled && to.name === RouteName.Billing) {
next({ name: RouteName.AccountSettings });
return;
}
next();
});
export function startTitleWatcher(): void {
const projectsStore = useProjectsStore();
const configStore = useConfigStore();

View File

@ -32,14 +32,14 @@
link="https://docs.storj.io/dcs/pricing#per-segment-fee"
/>
</v-col>
<v-col v-if="!isPaidTier" cols="auto">
<v-col v-if="!isPaidTier && billingEnabled" cols="auto">
<v-btn @click="appStore.toggleUpgradeFlow(true)">
Upgrade
</v-btn>
</v-col>
</v-row>
<v-row class="d-flex align-center justify-center mt-2">
<v-row class="d-flex align-center mt-2">
<v-col cols="12" sm="6" md="4" lg="2">
<CardStatsComponent icon="file" title="Files" subtitle="Project files" :data="limits.objectCount.toLocaleString()" to="buckets" />
</v-col>
@ -55,7 +55,7 @@
<v-col cols="12" sm="6" md="4" lg="2">
<CardStatsComponent icon="team" title="Team" subtitle="Project members" :data="teamSize.toLocaleString()" to="team" />
</v-col>
<v-col cols="12" sm="6" md="4" lg="2">
<v-col v-if="billingEnabled" cols="12" sm="6" md="4" lg="2">
<CardStatsComponent icon="card" title="Billing" :subtitle="`${paidTierString} account`" :data="paidTierString" to="/account/billing" />
</v-col>
</v-row>
@ -135,13 +135,13 @@
:used="`${limits.segmentUsed.toLocaleString()} Used`"
:limit="`Limit: ${limits.segmentLimit.toLocaleString()}`"
:available="`${availableSegment.toLocaleString()} Available`"
:cta="isPaidTier ? 'Learn more' : 'Need more?'"
:cta="!isPaidTier && billingEnabled ? 'Need more?' : 'Learn more'"
@cta-click="onSegmentsCTAClicked"
/>
</v-col>
<v-col cols="12" md="6">
<UsageProgressComponent
v-if="billingStore.state.coupon"
v-if="billingStore.state.coupon && billingEnabled"
icon="check"
:title="isFreeTierCoupon ? 'Free Usage' : 'Coupon'"
:progress="couponProgress"
@ -184,6 +184,9 @@ import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames
import { useNotify } from '@/utils/hooks';
import { useAppStore } from '@poc/store/appStore';
import { LocalData } from '@/utils/localData';
import { ProjectMembersPage } from '@/types/projectMembers';
import { AccessGrantsPage } from '@/types/accessGrants';
import { useConfigStore } from '@/store/modules/configStore';
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
import PageSubtitleComponent from '@poc/components/PageSubtitleComponent.vue';
@ -205,6 +208,7 @@ const pmStore = useProjectMembersStore();
const agStore = useAccessGrantsStore();
const billingStore = useBillingStore();
const bucketsStore = useBucketsStore();
const configStore = useConfigStore();
const notify = useNotify();
const router = useRouter();
@ -218,6 +222,11 @@ const limitToChange = ref<LimitToChange>(LimitToChange.Storage);
const isCreateBucketDialogShown = ref<boolean>(false);
const isSetPassphraseDialogShown = ref<boolean>(false);
/**
* Indicates if billing features are enabled.
*/
const billingEnabled = computed<boolean>(() => configStore.state.config.billingFeaturesEnabled);
/**
* Returns percent of coupon used.
*/
@ -267,25 +276,6 @@ const isPaidTier = computed((): boolean => {
return usersStore.state.user.paidTier;
});
/**
* Returns formatted amount.
*/
function usedLimitFormatted(value: number): string {
return formattedValue(new Size(value, 2));
}
/**
* Formats value to needed form and returns it.
*/
function formattedValue(value: Size): string {
switch (value.label) {
case Dimensions.Bytes:
return '0';
default:
return `${value.formattedBytes.replace(/\.0+$/, '')}${value.label}`;
}
}
/**
* Returns user account tier string.
*/
@ -409,6 +399,25 @@ const promptForPassphrase = computed((): boolean => {
return bucketsStore.state.promptForPassphrase;
});
/**
* Returns formatted amount.
*/
function usedLimitFormatted(value: number): string {
return formattedValue(new Size(value, 2));
}
/**
* Formats value to needed form and returns it.
*/
function formattedValue(value: Size): string {
switch (value.label) {
case Dimensions.Bytes:
return '0';
default:
return `${value.formattedBytes.replace(/\.0+$/, '')}${value.label}`;
}
}
/**
* Used container size recalculation for charts resizing.
*/
@ -429,7 +438,7 @@ function getDimension(dataStamps: DataStamp[]): Dimensions {
* or the edit limit dialog.
*/
function onNeedMoreClicked(source: LimitToChange): void {
if (!isPaidTier.value) {
if (!isPaidTier.value && billingEnabled.value) {
appStore.toggleUpgradeFlow(true);
return;
}
@ -442,7 +451,7 @@ function onNeedMoreClicked(source: LimitToChange): void {
* Conditionally opens the upgrade dialog or docs link.
*/
function onSegmentsCTAClicked(): void {
if (!isPaidTier.value) {
if (!isPaidTier.value && billingEnabled.value) {
appStore.toggleUpgradeFlow(true);
return;
}
@ -477,16 +486,24 @@ onMounted(async (): Promise<void> => {
const past = new Date();
past.setDate(past.getDate() - 30);
try {
await Promise.all([
projectsStore.getDailyProjectData({ since: past, before: now }),
projectsStore.getProjectLimits(projectID),
let promises: Promise<void | ProjectMembersPage | AccessGrantsPage>[] = [
projectsStore.getDailyProjectData({ since: past, before: now }),
projectsStore.getProjectLimits(projectID),
pmStore.getProjectMembers(FIRST_PAGE, projectID),
agStore.getAccessGrants(FIRST_PAGE, projectID),
bucketsStore.getBuckets(FIRST_PAGE, projectID),
];
if (billingEnabled.value) {
promises = [
...promises,
billingStore.getProjectUsageAndChargesCurrentRollup(),
billingStore.getCoupon(),
pmStore.getProjectMembers(FIRST_PAGE, projectID),
agStore.getAccessGrants(FIRST_PAGE, projectID),
bucketsStore.getBuckets(FIRST_PAGE, projectID),
]);
];
}
try {
await Promise.all(promises);
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_DASHBOARD_PAGE);
}

View File

@ -45,7 +45,7 @@
<v-divider />
<v-list-item v-if="!isPaidTier">
<v-list-item v-if="!isPaidTier && billingEnabled">
<v-list-item-title>Free Account</v-list-item-title>
<v-list-item-subtitle>
{{ storageLimitFormatted }} Storage / {{ bandwidthLimitFormatted }} Bandwidth.
@ -160,6 +160,11 @@ const configStore = useConfigStore();
const notify = useNotify();
/**
* Indicates if billing features are enabled.
*/
const billingEnabled = computed<boolean>(() => configStore.state.config.billingFeaturesEnabled);
/**
* Returns selected project from the store.
*/