web/satellite: added Upgrade to Paid Tier banner
Added new info banner to show user their used and total storage values with a button to upgrade to Paid Tier with auto limit increase Change-Id: I827818dcb5179358df246218a47feb61bc1a1bac
This commit is contained in:
parent
1230cde317
commit
bab43af6ce
@ -264,6 +264,7 @@ func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
CompanyName string `json:"companyName"`
|
||||
EmployeeCount string `json:"employeeCount"`
|
||||
HaveSalesContact bool `json:"haveSalesContact"`
|
||||
PaidTier bool `json:"paidTier"`
|
||||
}
|
||||
|
||||
auth, err := console.GetAuth(ctx)
|
||||
@ -283,6 +284,7 @@ func (a *Auth) GetAccount(w http.ResponseWriter, r *http.Request) {
|
||||
user.Position = auth.User.Position
|
||||
user.EmployeeCount = auth.User.EmployeeCount
|
||||
user.HaveSalesContact = auth.User.HaveSalesContact
|
||||
user.PaidTier = auth.User.PaidTier
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(&user)
|
||||
|
@ -93,6 +93,7 @@
|
||||
"declaration-block-trailing-semicolon": "always",
|
||||
"declaration-colon-space-before": "never",
|
||||
"declaration-colon-space-after": "always",
|
||||
"declaration-colon-newline-after": null,
|
||||
"number-leading-zero": "always",
|
||||
"function-url-quotes": "always",
|
||||
"font-family-name-quotes": "always-unless-keyword",
|
||||
|
@ -135,6 +135,7 @@ export class AuthHttpApi {
|
||||
userResponse.partnerId,
|
||||
userResponse.password,
|
||||
userResponse.projectLimit,
|
||||
userResponse.paidTier,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
<template>
|
||||
<div class="account-billing-area">
|
||||
<InfoBar/>
|
||||
<div class="account-billing-area__notification-container" v-if="hasNoCreditCard">
|
||||
<div class="account-billing-area__notification-container__negative-balance" v-if="isBalanceNegative">
|
||||
<NegativeBalanceIcon/>
|
||||
@ -62,7 +61,6 @@ import SmallDepositHistory from '@/components/account/billing/depositAndBillingH
|
||||
import EstimatedCostsAndCredits from '@/components/account/billing/estimatedCostsAndCredits/EstimatedCostsAndCredits.vue';
|
||||
import CreditsHistory from '@/components/account/billing/freeCredits/CreditsHistory.vue';
|
||||
import HistoryDropdown from '@/components/account/billing/HistoryDropdown.vue';
|
||||
import InfoBar from '@/components/account/billing/InfoBar.vue';
|
||||
import PaymentMethods from '@/components/account/billing/paymentMethods/PaymentMethods.vue';
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
|
||||
@ -90,7 +88,6 @@ import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
ExpandIcon,
|
||||
HideIcon,
|
||||
CreditsHistory,
|
||||
InfoBar,
|
||||
VLoader,
|
||||
},
|
||||
})
|
||||
@ -330,7 +327,7 @@ export default class BillingArea extends Vue {
|
||||
}
|
||||
|
||||
&__notification-container {
|
||||
margin-top: 60px;
|
||||
margin-top: 20px;
|
||||
|
||||
&__negative-balance,
|
||||
&__low-balance {
|
||||
|
@ -1,174 +0,0 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="billing-info-bar">
|
||||
<div class="billing-info-bar__info">
|
||||
<VLoader class="billing-info-loader" v-if="isDataFetching" width="20px" height="20px"/>
|
||||
<b v-else class="billing-info-bar__info__storage-value">{{ storageRemaining }}</b>
|
||||
<span class="billing-info-bar__info__storage-label">of Storage Remaining</span>
|
||||
<VLoader class="billing-info-loader" v-if="isDataFetching" width="20px" height="20px"/>
|
||||
<b v-else class="billing-info-bar__info__bandwidth-value">{{ bandwidthRemaining }}</b>
|
||||
<span class="billing-info-bar__info__bandwidth-label">of Bandwidth Remaining</span>
|
||||
<router-link class="billing-info-bar__info__button" :to="projectDashboardPath">Details</router-link>
|
||||
</div>
|
||||
<a
|
||||
class="billing-info-bar__link"
|
||||
:href="projectLimitsIncreaseRequestURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Request Limit Increase ->
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { Size } from '@/utils/bytesSize';
|
||||
import { MetaUtils } from '@/utils/meta';
|
||||
|
||||
/**
|
||||
* VBanner is common banner for needed pages
|
||||
*/
|
||||
@Component({
|
||||
components: {
|
||||
VLoader,
|
||||
},
|
||||
})
|
||||
export default class InfoBar extends Vue {
|
||||
public readonly projectDashboardPath: string = RouteConfig.ProjectDashboard.path;
|
||||
public isDataFetching: boolean = true;
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render.
|
||||
* Fetches project limits.
|
||||
*/
|
||||
public async mounted(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id);
|
||||
|
||||
this.isDataFetching = false;
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted string of remaining storage.
|
||||
*/
|
||||
public get storageRemaining(): string {
|
||||
const storageUsed = this.$store.state.projectsModule.currentLimits.storageUsed;
|
||||
const storageLimit = this.$store.state.projectsModule.currentLimits.storageLimit;
|
||||
|
||||
const difference = storageLimit - storageUsed;
|
||||
if (difference < 0) {
|
||||
return '0 Bytes';
|
||||
}
|
||||
|
||||
const remaining = new Size(difference, 2);
|
||||
|
||||
return `${remaining.formattedBytes}${remaining.label}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted string of remaining bandwidth.
|
||||
*/
|
||||
public get bandwidthRemaining(): string {
|
||||
const bandwidthUsed = this.$store.state.projectsModule.currentLimits.bandwidthUsed;
|
||||
const bandwidthLimit = this.$store.state.projectsModule.currentLimits.bandwidthLimit;
|
||||
|
||||
const difference = bandwidthLimit - bandwidthUsed;
|
||||
if (difference < 0) {
|
||||
return '0 Bytes';
|
||||
}
|
||||
|
||||
const remaining = new Size(difference, 2);
|
||||
|
||||
return `${remaining.formattedBytes}${remaining.label}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns project limits increase request url from config.
|
||||
*/
|
||||
public get projectLimitsIncreaseRequestURL(): string {
|
||||
return MetaUtils.getMetaContent('project-limits-increase-request-url');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.billing-info-bar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #dce5f2;
|
||||
width: calc(100% - 60px);
|
||||
padding: 10px 30px;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
color: #2b3543;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__storage-value,
|
||||
&__storage-label,
|
||||
&__bandwidth-value,
|
||||
&__bandwidth-label {
|
||||
margin-right: 5px;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__button {
|
||||
padding: 5px 10px;
|
||||
margin-left: 15px;
|
||||
background-color: #fff;
|
||||
opacity: 0.8;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
letter-spacing: 0.02em;
|
||||
color: rgba(43, 53, 67, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-family: 'font_medium', sans-serif;
|
||||
color: #2683ff;
|
||||
}
|
||||
}
|
||||
|
||||
.billing-info-loader {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
||||
.billing-info-bar {
|
||||
|
||||
&__info {
|
||||
|
||||
&__storage-value,
|
||||
&__storage-label,
|
||||
&__bandwidth-value,
|
||||
&__bandwidth-label {
|
||||
white-space: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -52,5 +52,4 @@ export default class SortingHeader extends Vue {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -0,0 +1,118 @@
|
||||
// Copyright (C) 2021 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="pt-bar">
|
||||
<p class="pt-bar__info">
|
||||
You are currently using
|
||||
<VLoader
|
||||
v-if="isDataFetching"
|
||||
class="pt-bar__info__loader"
|
||||
is-white="true"
|
||||
width="20px"
|
||||
height="20px"
|
||||
/>
|
||||
<b class="pt-bar__info__bold" v-else>{{storageUsed}}</b>
|
||||
storage out of the
|
||||
<b class="pt-bar__info__bold">150GB</b>
|
||||
included in the free account.
|
||||
</p>
|
||||
<p class="pt-bar__functional">
|
||||
Upload up to 75TB.
|
||||
<b class="pt-bar__info__bold upgrade">Upgrade now.</b>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
|
||||
import { PAYMENTS_MUTATIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { ProjectLimits } from '@/types/projects';
|
||||
import { Size } from '@/utils/bytesSize';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
VLoader,
|
||||
},
|
||||
})
|
||||
export default class PaidTierBar extends Vue {
|
||||
/**
|
||||
* Mounted lifecycle hook after initial render.
|
||||
* Fetches total limits.
|
||||
*/
|
||||
public async mounted(): Promise<void> {
|
||||
try {
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.GET_TOTAL_LIMITS);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
await this.$store.commit(PAYMENTS_MUTATIONS.TOGGLE_PAID_TIER_BANNER_TO_LOADED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if total limits data is fetching.
|
||||
*/
|
||||
public get isDataFetching(): boolean {
|
||||
return this.$store.state.paymentsModule.isPaidTierBarLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted string of used storage.
|
||||
*/
|
||||
public get storageUsed(): string {
|
||||
if (this.totalLimits.storageUsed === 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
const used = new Size(this.totalLimits.storageUsed, 0);
|
||||
|
||||
return `${used.formattedBytes}${used.label}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns total limits from store.
|
||||
*/
|
||||
private get totalLimits(): ProjectLimits {
|
||||
return this.$store.state.projectsModule.totalLimits;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pt-bar {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #0047ff;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #eee;
|
||||
padding: 5px 30px;
|
||||
|
||||
&__info,
|
||||
&__functional {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&__bold {
|
||||
margin: 0 5px;
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
|
||||
&__loader {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
@ -20,12 +20,11 @@ import StripeCardInput from '@/components/account/billing/paymentMethods/StripeC
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
|
||||
const {
|
||||
ADD_CREDIT_CARD,
|
||||
GET_CREDIT_CARDS,
|
||||
GET_BALANCE,
|
||||
} = PAYMENTS_ACTIONS;
|
||||
|
||||
interface StripeForm {
|
||||
@ -52,6 +51,9 @@ export default class AddCardForm extends Vue {
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(ADD_CREDIT_CARD, token);
|
||||
|
||||
// We fetch User one more time to update their Paid Tier status.
|
||||
await this.$store.dispatch(USER_ACTIONS.GET);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
|
||||
|
@ -30,7 +30,8 @@ import ProjectUsage from '@/components/project/usage/ProjectUsage.vue';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { PAYMENTS_ACTIONS, PAYMENTS_MUTATIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { PM_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { MetaUtils } from '@/utils/meta';
|
||||
|
||||
@ -60,6 +61,10 @@ export default class ProjectDashboard extends Vue {
|
||||
const FIRST_PAGE = 1;
|
||||
|
||||
try {
|
||||
await this.$store.commit(PAYMENTS_MUTATIONS.TOGGLE_PAID_TIER_BANNER_TO_LOADING);
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.GET_TOTAL_LIMITS);
|
||||
await this.$store.commit(PAYMENTS_MUTATIONS.TOGGLE_PAID_TIER_BANNER_TO_LOADED);
|
||||
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, FIRST_PAGE);
|
||||
|
||||
this.areBucketsFetching = false;
|
||||
|
@ -27,6 +27,8 @@ export const PAYMENTS_MUTATIONS = {
|
||||
SET_PREVIOUS_ROLLUP_PRICE: 'SET_PREVIOUS_ROLLUP_PRICE',
|
||||
SET_PRICE_SUMMARY: 'SET_PRICE_SUMMARY',
|
||||
SET_PRICE_SUMMARY_FOR_SELECTED_PROJECT: 'SET_PRICE_SUMMARY_FOR_SELECTED_PROJECT',
|
||||
TOGGLE_PAID_TIER_BANNER_TO_LOADING: 'TOGGLE_PAID_TIER_BANNER_TO_LOADING',
|
||||
TOGGLE_PAID_TIER_BANNER_TO_LOADED: 'TOGGLE_PAID_TIER_BANNER_TO_LOADED',
|
||||
};
|
||||
|
||||
export const PAYMENTS_ACTIONS = {
|
||||
@ -57,6 +59,8 @@ const {
|
||||
SET_PROJECT_USAGE_AND_CHARGES,
|
||||
SET_PRICE_SUMMARY,
|
||||
SET_PRICE_SUMMARY_FOR_SELECTED_PROJECT,
|
||||
TOGGLE_PAID_TIER_BANNER_TO_LOADING,
|
||||
TOGGLE_PAID_TIER_BANNER_TO_LOADED,
|
||||
} = PAYMENTS_MUTATIONS;
|
||||
|
||||
const {
|
||||
@ -87,6 +91,7 @@ export class PaymentsState {
|
||||
public priceSummaryForSelectedProject: number = 0;
|
||||
public startDate: Date = new Date();
|
||||
public endDate: Date = new Date();
|
||||
public isPaidTierBarLoading: boolean = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,6 +170,12 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState>
|
||||
|
||||
state.priceSummaryForSelectedProject = usageAndChargesForSelectedProject.summary();
|
||||
},
|
||||
[TOGGLE_PAID_TIER_BANNER_TO_LOADING](state: PaymentsState): void {
|
||||
state.isPaidTierBarLoading = true;
|
||||
},
|
||||
[TOGGLE_PAID_TIER_BANNER_TO_LOADED](state: PaymentsState): void {
|
||||
state.isPaidTierBarLoading = false;
|
||||
},
|
||||
[CLEAR](state: PaymentsState) {
|
||||
state.balance = new AccountBalance();
|
||||
state.paymentsHistory = [];
|
||||
|
@ -54,6 +54,7 @@ export function makeUsersModule(api: UsersApi): StoreModule<User> {
|
||||
}
|
||||
|
||||
state.projectLimit = user.projectLimit;
|
||||
state.paidTier = user.paidTier;
|
||||
},
|
||||
|
||||
[CLEAR](state: User): void {
|
||||
|
@ -34,6 +34,7 @@ export class User {
|
||||
public partnerId: string = '',
|
||||
public password: string = '',
|
||||
public projectLimit: number = 0,
|
||||
public paidTier: boolean = false,
|
||||
public isProfessional: boolean = false,
|
||||
public position: string = '',
|
||||
public companyName: string = '',
|
||||
|
@ -15,6 +15,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="!isLoading" class="dashboard__wrap">
|
||||
<PaidTierBar v-if="!isPaidTierStatus && !isOnboardingTour"/>
|
||||
<DashboardHeader/>
|
||||
<div class="dashboard__wrap__main-area">
|
||||
<NavigationArea class="regular-navigation"/>
|
||||
@ -29,6 +30,7 @@
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import PaidTierBar from '@/components/account/billing/paidTier/PaidTierBar.vue';
|
||||
import DashboardHeader from '@/components/header/HeaderArea.vue';
|
||||
import NavigationArea from '@/components/navigation/NavigationArea.vue';
|
||||
|
||||
@ -48,7 +50,6 @@ import { MetaUtils } from '@/utils/meta';
|
||||
|
||||
const {
|
||||
SETUP_ACCOUNT,
|
||||
GET_BALANCE,
|
||||
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP,
|
||||
} = PAYMENTS_ACTIONS;
|
||||
|
||||
@ -57,6 +58,7 @@ const {
|
||||
NavigationArea,
|
||||
DashboardHeader,
|
||||
LoaderImage,
|
||||
PaidTierBar,
|
||||
},
|
||||
})
|
||||
export default class DashboardArea extends Vue {
|
||||
@ -124,6 +126,20 @@ export default class DashboardArea extends Vue {
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns user's paid tier status from store.
|
||||
*/
|
||||
public get isPaidTierStatus(): boolean {
|
||||
return this.$store.state.usersModule.paidTier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if current route is onboarding tour.
|
||||
*/
|
||||
public get isOnboardingTour(): boolean {
|
||||
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns satellite name from store (config).
|
||||
*/
|
||||
|
@ -5,6 +5,7 @@ exports[`Dashboard renders correctly when data is loaded 1`] = `
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="dashboard__wrap">
|
||||
<!---->
|
||||
<dashboardheader-stub></dashboardheader-stub>
|
||||
<div class="dashboard__wrap__main-area">
|
||||
<navigationarea-stub class="regular-navigation"></navigationarea-stub>
|
||||
|
Loading…
Reference in New Issue
Block a user