web/satellite: low STORJ balance notification

Added a new banner to the dashboard of all projects. This banner is displayed when a user meets the following conditions: they have no credit cards on file, they have a payment history with tokens, and their estimated charges are higher than their current balance.

Issue:
https://github.com/storj/storj/issues/6234

Change-Id: I1f90ae81032d459111b111d23ce2e1d8119e649d
This commit is contained in:
Vitalii 2023-09-14 15:22:08 +03:00
parent bc517cae2f
commit ecc527ad3b
8 changed files with 63 additions and 38 deletions

View File

@ -144,11 +144,14 @@ function routeToCoupons(): void {
/**
* Mounted lifecycle hook after initial render.
* Fetches account balance.
* Fetches account balance and credit cards.
*/
onMounted(async (): Promise<void> => {
try {
await billingStore.getBalance();
await Promise.all([
billingStore.getBalance(),
billingStore.getCreditCards(),
]);
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.BILLING_AREA);
}

View File

@ -201,18 +201,8 @@ export const useBillingStore = defineStore('billing', () => {
state.wallet = new Wallet();
}
const canUserCreateFirstProject = computed((): boolean => {
return state.balance.sum > 0 || state.creditCards.length > 0;
});
const isBalancePositive = computed((): boolean => {
return state.balance.sum > 0;
});
return {
state,
canUserCreateFirstProject,
isBalancePositive,
getBalance,
getWallet,
claimWallet,

View File

@ -158,6 +158,10 @@ export class AccountBalance {
return parseFloat(this._coins);
}
public get credits(): number {
return parseFloat(this._credits);
}
public get formattedCredits(): string {
return formatPrice(decimalShift(this._credits, 2));
}
@ -167,7 +171,7 @@ export class AccountBalance {
}
public get sum(): number {
return this.freeCredits + this.coins;
return this.credits + this.coins;
}
public hasCredits(): boolean {

View File

@ -161,13 +161,6 @@ onMounted(async () => {
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
try {
await billingStore.getCreditCards();
} catch (error) {
error.message = `Unable to get credit cards. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
try {
await projectsStore.getUserInvitations();
} catch (error) {

View File

@ -30,11 +30,20 @@
:dashboard-ref="parentRef"
:on-link-click="redirectToBillingPage"
/>
<v-banner
v-if="isLowBalance && parentRef"
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"
severity="warning"
:dashboard-ref="parentRef"
:on-link-click="redirectToBillingOverview"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from 'vue-router';
@ -42,12 +51,14 @@ import { useUsersStore } from '@/store/modules/usersStore';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { useAppStore } from '@/store/modules/appStore';
import { RouteConfig } from '@/types/router';
import { useBillingStore } from '@/store/modules/billingStore';
import VBanner from '@/components/common/VBanner.vue';
import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue';
const router = useRouter();
const billingStore = useBillingStore();
const usersStore = useUsersStore();
const appStore = useAppStore();
@ -69,6 +80,15 @@ const isAccountWarned = computed((): boolean => {
return usersStore.state.user.freezeStatus.warned;
});
/**
* Indicates if low STORJ token balance banner is shown.
*/
const isLowBalance = computed((): boolean => {
return !billingStore.state.creditCards.length &&
billingStore.state.nativePaymentsHistory.length > 0 &&
billingStore.state.balance.sum < billingStore.state.projectCharges.getPrice();
});
/* whether the paid tier banner should be shown */
const isPaidTierBannerShown = computed((): boolean => {
return !usersStore.state.user.paidTier
@ -97,6 +117,13 @@ function togglePMModal(): void {
async function redirectToBillingPage(): Promise<void> {
await router.push(RouteConfig.AccountSettings.with(RouteConfig.Billing2.with(RouteConfig.BillingPaymentMethods2)).path);
}
/**
* Redirects to Billing Page Overview tab.
*/
async function redirectToBillingOverview(): Promise<void> {
await router.push(RouteConfig.AccountSettings.with(RouteConfig.Billing2.with(RouteConfig.BillingOverview2)).path);
}
</script>
<style scoped lang="scss">
@ -104,9 +131,9 @@ async function redirectToBillingPage(): Promise<void> {
margin-bottom: 20px;
&__upgrade,
&__project-limit,
&__freeze,
&__warning {
&__warning,
&__low-balance {
margin: 20px 0 0;
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
}

View File

@ -82,11 +82,12 @@
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { Project, ProjectInvitation } from '@/types/projects';
import { RouteConfig } from '@/types/router';
import {
AnalyticsErrorEventSource,
AnalyticsEvent,
} from '@/utils/constants/analyticsEventNames';
import { User } from '@/types/users';
@ -97,6 +98,8 @@ import { useProjectsStore } from '@/store/modules/projectsStore';
import { useConfigStore } from '@/store/modules/configStore';
import { useResize } from '@/composables/resize';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useNotify } from '@/utils/hooks';
import EmptyProjectItem from '@/views/all-dashboard/components/EmptyProjectItem.vue';
import ProjectItem from '@/views/all-dashboard/components/ProjectItem.vue';
@ -110,12 +113,14 @@ import RocketIcon from '@/../static/images/common/rocket.svg';
import CardsIcon from '@/../static/images/common/cardsIcon.svg';
import TableIcon from '@/../static/images/common/tableIcon.svg';
const billingStore = useBillingStore();
const appStore = useAppStore();
const configStore = useConfigStore();
const usersStore = useUsersStore();
const projectsStore = useProjectsStore();
const analyticsStore = useAnalyticsStore();
const notify = useNotify();
const { isMobile } = useResize();
const content = ref<HTMLElement | null>(null);
@ -169,6 +174,23 @@ function onCreateProjectClicked(): void {
appStore.updateActiveModal(MODALS.createProjectPrompt);
}
}
onMounted(async () => {
if (!configStore.state.config.nativeTokenPaymentsEnabled) {
return;
}
try {
await Promise.all([
billingStore.getBalance(),
billingStore.getProjectUsageAndChargesCurrentRollup(),
billingStore.getCreditCards(),
billingStore.getNativePaymentsHistory(),
]);
} catch (error) {
notify.notifyError(error, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
}
});
</script>
<style scoped lang="scss">

View File

@ -437,13 +437,6 @@ onMounted(async () => {
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
try {
await billingStore.getCreditCards();
} catch (error) {
error.message = `Unable to get credit cards. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
try {
await projectsStore.getUserInvitations();
} catch (error) {

View File

@ -112,13 +112,6 @@ onBeforeMount(async () => {
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
try {
await billingStore.getCreditCards();
} catch (error) {
error.message = `Unable to get credit cards. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
}
await selectProject(route.params.projectId as string);
if (!agStore.state.accessGrantsWebWorker) await agStore.startWorker();