web/satellite: limits info bar added to billing page, free credit amount changed

Change-Id: I08f52e5dab16f5d176a909b29952718891f406d5
This commit is contained in:
VitaliiShpital 2020-03-12 17:32:40 +02:00 committed by Vitalii Shpital
parent 27f811a9e1
commit 54dbc5173d
28 changed files with 382 additions and 341 deletions

View File

@ -37,7 +37,7 @@ import DepositAndBilling from '@/components/account/billing/billingHistory/Depos
import MonthlyBillingSummary from '@/components/account/billing/monthlySummary/MonthlyBillingSummary.vue';
import PaymentMethods from '@/components/account/billing/paymentMethods/PaymentMethods.vue';
import { Project } from '@/types/projects';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectOwning } from '@/utils/projectOwning';
@Component({
@ -49,6 +49,22 @@ import { ProjectOwning } from '@/utils/projectOwning';
},
})
export default class BillingArea extends Vue {
/**
* Mounted lifecycle hook after initial render.
* Fetches project limits.
*/
public async mounted(): Promise<void> {
if (!this.$store.getters.selectedProject.id) {
return;
}
try {
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id);
} catch (error) {
await this.$notify.error(error.message);
}
}
/**
* Holds minimum safe balance in cents.
* If balance is lower - yellow notification should appear.
@ -61,7 +77,7 @@ export default class BillingArea extends Vue {
public get areBalanceAndSummaryVisible(): boolean {
const isBalancePositive: boolean = this.$store.state.paymentsModule.balance > 0;
return isBalancePositive || ProjectOwning.userHasOwnProject();
return isBalancePositive || new ProjectOwning(this.$store).userHasOwnProject();
}
/**
@ -96,7 +112,7 @@ export default class BillingArea extends Vue {
font-size: 32px;
line-height: 39px;
color: #384b65;
margin: 45px 0 35px 0;
margin: 60px 0 0 0;
}
&__notification-container {

View File

@ -54,7 +54,7 @@ export default class AccountBalance extends Vue {
this.$notify.error(error.message);
}
if (this.balance > 0 && !ProjectOwning.userHasOwnProject()) {
if (this.balance > 0 && !new ProjectOwning(this.$store).userHasOwnProject()) {
this.$store.dispatch(APP_STATE_ACTIONS.SHOW_CREATE_PROJECT_BUTTON);
}
}
@ -87,7 +87,7 @@ export default class AccountBalance extends Vue {
align-items: center;
justify-content: space-between;
padding: 40px;
margin: 55px 0 32px 0;
margin: 32px 0;
background-color: #fff;
border-radius: 8px;
font-family: 'font_regular', sans-serif;

View File

@ -13,6 +13,7 @@
label="Add STORJ"
width="123px"
height="48px"
is-blue-white="true"
:on-press="onAddSTORJ"
/>
</div>
@ -293,7 +294,7 @@ export default class PaymentMethods extends Vue {
this.isLoading = true;
if ((this.tokenDepositValue < 50 || this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT) && !ProjectOwning.userHasOwnProject()) {
if ((this.tokenDepositValue < 50 || this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT) && !new ProjectOwning(this.$store).userHasOwnProject()) {
await this.$notify.error('First deposit amount must be more than 50 and less than 1000000');
this.tokenDepositValue = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
this.areaState = PaymentMethodsBlockState.DEFAULT;
@ -378,14 +379,17 @@ export default class PaymentMethods extends Vue {
this.isLoading = false;
this.isLoaded = true;
if (!ProjectOwning.userHasOwnProject()) {
if (!new ProjectOwning(this.$store).userHasOwnProject()) {
await this.$store.dispatch(APP_STATE_ACTIONS.SHOW_CREATE_PROJECT_BUTTON);
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CONTENT_BLUR);
}
setTimeout(() => {
this.onCancel();
this.isLoaded = false;
setTimeout(() => {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CONTENT_BLUR);
}, 500);
}, 2000);
}

View File

@ -122,7 +122,7 @@ export default class TokenDepositSelection extends Vue {
* Returns payment options depending on user having his own project.
*/
public get options(): PaymentAmountOption[] {
if (!ProjectOwning.userHasOwnProject()) {
if (!new ProjectOwning(this.$store).userHasOwnProject()) {
return this.initialPaymentOptions;
}

View File

@ -157,7 +157,7 @@ export default class NoApiKeysArea extends Vue {
@media screen and (max-width: 1590px) {
.no-api-keys-area {
padding: 0 0 65px 0;
padding: 65px 0;
&__steps-area {
@ -189,41 +189,7 @@ export default class NoApiKeysArea extends Vue {
}
}
@media screen and (max-width: 1240px) {
.no-api-keys-area {
&__steps-area {
&__numbers {
padding: 0 90px;
}
&__items {
&__create-api-key,
&__setup-uplink,
&__store-data {
&__title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 16px;
text-align: center;
color: #354049;
}
}
}
}
}
.no-api-keys-area-image {
max-width: 220px;
max-height: 160px;
}
}
@media screen and (max-width: 1190px) {
@media screen and (max-width: 900px) {
.no-api-keys-area {
@ -256,38 +222,4 @@ export default class NoApiKeysArea extends Vue {
max-height: 140px;
}
}
@media screen and (max-width: 1110px) {
.no-api-keys-area {
&__steps-area {
&__numbers {
padding: 0 65px;
}
&__items {
&__create-api-key,
&__setup-uplink,
&__store-data {
&__title {
font-family: 'font_bold', sans-serif;
font-size: 12px;
line-height: 16px;
text-align: center;
color: #354049;
}
}
}
}
}
.no-api-keys-area-image {
max-width: 180px;
max-height: 120px;
}
}
</style>

View File

@ -1,109 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="banner-wrap">
<div class="banner" :class="{link: isLinkActive}" @click="onBannerClick">
<NotificationSvg />
<div class="column">
<p class="banner__text">{{ text }}</p>
<p class="banner__additional-text">{{ additionalText }}</p>
</div>
<ArrowRightIcon class="banner__arrow" v-if="isLinkActive" />
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import ArrowRightIcon from '@/../static/images/common/BlueArrowRight.svg';
import NotificationSvg from '@/../static/images/notifications/notification.svg';
/**
* VBanner is custom banner on top of all pages in Dashboard
*/
@Component({
components: {
NotificationSvg,
ArrowRightIcon,
},
})
export default class VBanner extends Vue {
@Prop({default: ''})
private readonly text: string;
@Prop({default: ''})
private readonly additionalText: string;
@Prop({default: '/'})
private readonly path: string;
/**
* Indicates if redirect arrow should be rendered if current route is not destination.
*/
public get isLinkActive(): boolean {
return this.$route.path !== this.path;
}
/**
* Redirects to destination route.
*/
public onBannerClick(): void {
if (!this.isLinkActive) {
return;
}
this.$router.push(this.path);
}
}
</script>
<style scoped lang="scss">
.banner-wrap {
margin: 32px 65px 15px 65px;
}
.banner {
position: relative;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 20px;
border-radius: 12px;
background-color: #d0e3fe;
&__text {
font-family: 'font_medium', sans-serif;
font-size: 16px;
font-weight: 500;
line-height: 19px;
color: #1e2d42;
margin: 0;
}
&__additional-text {
font-family: 'font_regular', sans-serif;
margin: 3px 0 0 0;
font-size: 14px;
color: #717e92;
}
&__arrow {
position: absolute;
top: 50%;
right: 32px;
transform: translate(0, -50%);
}
}
.column {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
margin: 0 17px;
}
.link {
cursor: pointer;
}
</style>

View File

@ -30,6 +30,8 @@ export default class VButton extends Vue {
@Prop({default: false})
private readonly isDeletion: boolean;
@Prop({default: false})
private readonly isBlueWhite: boolean;
@Prop({default: false})
private isDisabled: boolean;
@Prop({default: () => { return; }})
private readonly onPress: Function;
@ -45,6 +47,8 @@ export default class VButton extends Vue {
if (this.isDeletion) return 'container red';
if (this.isBlueWhite) return 'container blue-white';
return 'container';
}
}
@ -61,6 +65,15 @@ export default class VButton extends Vue {
}
}
.blue-white {
background-color: transparent !important;
border: 2px solid #2683ff !important;
.label {
color: #2683ff !important;
}
}
.disabled {
background-color: #dadde5 !important;
border-color: #dadde5 !important;
@ -88,7 +101,8 @@ export default class VButton extends Vue {
&:hover {
background-color: #0059d0;
&.white {
&.white,
&.blue-white {
box-shadow: none !important;
background-color: #2683ff !important;
border: 1px solid #2683ff !important;

View File

@ -0,0 +1,90 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="info-bar">
<div class="info-bar__info-area">
<b class="info-bar__info-area__first-value">{{ firstValue }}</b>
<span class="info-bar__info-area__first-description">{{ firstDescription }}</span>
<b class="info-bar__info-area__second-value">{{ secondValue }}</b>
<span class="info-bar__info-area__second-description">{{ secondDescription }}</span>
<router-link class="info-bar__info-area__button" :to="path">Details</router-link>
</div>
<a class="info-bar__link" :href="link" target="_blank">{{ linkLabel }}</a>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
/**
* VBanner is custom banner on top of all pages in Dashboard
*/
@Component
export default class VInfoBar extends Vue {
@Prop({default: ''})
private readonly firstValue: string;
@Prop({default: ''})
private readonly secondValue: string;
@Prop({default: ''})
private readonly firstDescription: string;
@Prop({default: ''})
private readonly secondDescription: string;
@Prop({default: ''})
private readonly link: string;
@Prop({default: ''})
private readonly linkLabel: string;
@Prop({default: '/'})
private readonly path: string;
}
</script>
<style scoped lang="scss">
.info-bar {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #dce5f2;
width: calc(100% - 130px);
padding: 10px 65px;
font-family: 'font_regular', sans-serif;
color: #2b3543;
&__info-area {
display: flex;
align-items: center;
&__first-value,
&__second-value,
&__first-description,
&__second-description {
margin-right: 5px;
font-size: 14px;
line-height: 17px;
}
&__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;
opacity: 0.75;
font-family: 'font_medium', sans-serif;
color: #2683ff;
}
}
</style>

View File

@ -45,7 +45,7 @@ export default class NewProjectArea extends Vue {
* Toggles new project button visibility depending on user having his own project or payment method.
*/
public beforeMount(): void {
if (ProjectOwning.userHasOwnProject() || !this.$store.getters.isBonusCouponApplied) {
if (new ProjectOwning(this.$store).userHasOwnProject() || !this.$store.getters.isBonusCouponApplied) {
this.$store.dispatch(APP_STATE_ACTIONS.HIDE_CREATE_PROJECT_BUTTON);
return;
@ -79,7 +79,7 @@ export default class NewProjectArea extends Vue {
* Indicates if new project creation mock button is shown.
*/
public get isMockButtonShown(): boolean {
return !ProjectOwning.userHasOwnProject() && !this.$store.getters.isBonusCouponApplied;
return !new ProjectOwning(this.$store).userHasOwnProject() && !this.$store.getters.isBonusCouponApplied;
}
}
</script>
@ -97,21 +97,20 @@ export default class NewProjectArea extends Vue {
width: 156px;
height: 40px;
border-radius: 6px;
border: 1px solid #afb7c1;
background-color: white;
border: 2px solid #2683ff;
background-color: transparent;
cursor: pointer;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 15px;
line-height: 22px;
color: #354049;
color: #2683ff;
}
&:hover {
background-color: #2683ff;
border: 1px solid #2683ff;
box-shadow: 0 4px 20px rgba(35, 121, 236, 0.4);
border: 2px solid #2683ff;
.new-project-button-container__label {
color: white;

View File

@ -36,7 +36,7 @@ export default class NavigationArea extends Vue {
*/
public areAccountItemsShown: boolean = true;
public isAccountButtonShown: boolean = false;
public homePath: string = RouteConfig.ProjectOverview.path;
public homePath: string = RouteConfig.Account.with(RouteConfig.Billing).path;
public toggleResourceItemsVisibility(): void {
this.areResourceItemsShown = !this.areResourceItemsShown;

View File

@ -2,12 +2,13 @@
<!--See LICENSE for copying information.-->
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<p class="overview-area__sub-title">
To get started, lets add a payment method.<br/>Well provide you with a $55 credit right away!
</p>
<router-link class="overview-area__button" :to="billingPath" >Add Payment Info & Get $55</router-link>
<span class="overview-area__charge-info">You will not be charged unless $55 is used</span>
<div class="overview-area__welcome-container">
<h1 class="overview-area__welcome-container__title">Welcome to Storj</h1>
<p class="overview-area__welcome-container__sub-title">
To create a project, get started by adding a payment method - <br/>your first 5GB are free for your first project.
</p>
<router-link class="overview-area__welcome-container__button" :to="billingPath" >Add Payment</router-link>
</div>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__numbers">
<FirstStepIcon class="overview-area__steps-area__numbers__icon"/>

View File

@ -8,49 +8,53 @@ h1 {
.overview-area {
width: auto;
height: calc(100% - 170px);
padding: 85px;
height: calc(100% - 150px);
padding: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: 'font_regular', sans-serif;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 20px;
}
&__welcome-container {
width: auto;
background-color: #fff;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 73px 261px;
margin-bottom: 32px;
&__sub-title {
font-size: 16px;
line-height: 24px;
color: #354049;
margin-bottom: 30px;
text-align: center;
}
&__button {
font-size: 16px;
line-height: 23px;
padding: 10px 15px;
color: #fff;
background-color: #2683ff;
border-radius: 8px;
margin-bottom: 10px;
&:hover {
background-color: #0059d0;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 20px;
}
}
&__charge-info {
font-size: 14px;
line-height: 22px;
color: #898989;
margin-bottom: 90px;
&__sub-title {
font-size: 16px;
line-height: 24px;
color: #354049;
margin-bottom: 30px;
text-align: center;
}
&__button {
font-size: 16px;
line-height: 23px;
padding: 10px 15px;
color: #fff;
background-color: #2683ff;
border-radius: 8px;
&:hover {
background-color: #0059d0;
}
}
}
&__steps-area {
@ -112,6 +116,11 @@ h1 {
.overview-area {
&__welcome-container {
width: 626px;
padding: 73px 55px;
}
&__steps-area {
&__numbers {
@ -141,45 +150,3 @@ h1 {
max-height: 150px;
}
}
@media screen and (max-width: 1200px) {
.overview-area {
&__steps-area {
&__numbers {
padding: 0 70px;
}
&__items {
&__add-payment-info,
&__create-project,
&__create-api-key {
&__title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 16px;
text-align: center;
color: #354049;
}
}
}
}
}
.overview-area-image {
max-width: 190px;
max-height: 130px;
}
}
@media screen and (max-width: 1110px) {
.overview-area {
width: calc(100% - 80px);
padding: 85px 40px;
}
}

View File

@ -140,6 +140,7 @@ export default class NewProjectPopup extends Vue {
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BILLING_HISTORY);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.createdProjectId);
} catch (error) {
await this.$notify.error(error.message);
}

View File

@ -23,7 +23,6 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { Dimensions, Size } from '@/utils/bytesSize';
@Component

View File

@ -1,14 +1,17 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import store from '@/store';
import { Store } from 'vuex';
import { Project } from '@/types/projects';
/**
* ProjectOwning exposes method checking if user has his own project.
*/
export class ProjectOwning {
public static userHasOwnProject(): boolean {
return store.state.projectsModule.projects.some((project: Project) => project.ownerId === store.getters.user.id);
public constructor(public store: Store<any>) {}
public userHasOwnProject(): boolean {
return this.store.state.projectsModule.projects.some((project: Project) => project.ownerId === this.store.getters.user.id);
}
}

View File

@ -11,12 +11,16 @@
<div class="dashboard-container__wrap__column">
<DashboardHeader/>
<div class="dashboard-container__main-area">
<div class="dashboard-container__main-area__banner-area">
<VBanner
v-if="isBannerShown"
text="Weve Now Added Billing!"
additional-text="Your attention is required. Add a credit card to set up your account."
:path="billingPath"
<div class="dashboard-container__main-area__bar-area">
<VInfoBar
v-if="isInfoBarShown"
:first-value="storageRemaining"
:second-value="bandwidthRemaining"
first-description="of Storage Remaining"
second-description="of Bandwidth Remaining"
:path="projectDetailsPath"
link="https://support.tardigrade.io/hc/en-us/requests/new?ticket_form_id=360000683212"
link-label="Request Limit Increase ->"
/>
</div>
<div class="dashboard-container__main-area__content">
@ -48,7 +52,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VBanner from '@/components/common/VBanner.vue';
import VInfoBar from '@/components/common/VInfoBar.vue';
import DashboardHeader from '@/components/header/HeaderArea.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
@ -64,12 +68,14 @@ import { PROJECT_USAGE_ACTIONS } from '@/store/modules/usage';
import { USER_ACTIONS } from '@/store/modules/users';
import { CreditCard } from '@/types/payments';
import { Project } from '@/types/projects';
import { Size } from '@/utils/bytesSize';
import {
API_KEYS_ACTIONS,
APP_STATE_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
import { AppState } from '@/utils/constants/appStateEnum';
import { ProjectOwning } from '@/utils/projectOwning';
const {
SETUP_ACCOUNT,
@ -83,13 +89,16 @@ const {
components: {
NavigationArea,
DashboardHeader,
VBanner,
VInfoBar,
AddImage,
CloseCrossIcon,
},
})
export default class DashboardArea extends Vue {
public readonly billingPath: string = RouteConfig.Account.with(RouteConfig.Billing).path;
/**
* Holds router link to project details page.
*/
public readonly projectDetailsPath: string = RouteConfig.ProjectOverview.with(RouteConfig.ProjectDetails).path;
/**
* Lifecycle hook after initial render.
@ -196,7 +205,7 @@ export default class DashboardArea extends Vue {
await this.$notify.error(`Unable to fetch buckets. ${error.message}`);
}
if (this.$store.getters.isBonusCouponApplied && !this.userHasHisOwnProject) {
if (this.$store.getters.isBonusCouponApplied && !new ProjectOwning(this.$store).userHasOwnProject()) {
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CONTENT_BLUR);
}
@ -204,14 +213,34 @@ export default class DashboardArea extends Vue {
}
/**
* Indicates if bonus banner should be rendered.
* Indicates if info bar is shown.
*/
public get isBannerShown(): boolean {
const isOverviewPage = this.$route.name === RouteConfig.Overview.name;
public get isInfoBarShown(): boolean {
const isBillingPage = this.$route.name === RouteConfig.Billing.name;
const hasCreditCards = this.$store.state.paymentsModule.creditCards.length > 0;
return !(isOverviewPage || isBillingPage || hasCreditCards);
return isBillingPage && new ProjectOwning(this.$store).userHasOwnProject();
}
/**
* 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 remaining = new Size(storageLimit - storageUsed, 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 remaining = new Size(bandwidthLimit - bandwidthUsed, 2);
return `${remaining.formattedBytes}${remaining.label}`;
}
/**
@ -255,13 +284,6 @@ export default class DashboardArea extends Vue {
return availableRoutes.includes(this.$router.currentRoute.path.toLowerCase());
}
/**
* Indicates if user has his own project.
*/
private get userHasHisOwnProject(): boolean {
return this.$store.state.projectsModule.projects.some((project: Project) => project.ownerId === this.$store.getters.user.id);
}
}
</script>
@ -294,7 +316,7 @@ export default class DashboardArea extends Vue {
display: flex;
flex-direction: column;
&__banner-area {
&__bar-area {
flex: 0 1 auto;
}

View File

@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#2683FF"/>
<path d="M22.2259 19.2681C21.598 18.9212 20.9305 18.6568 20.2696 18.3825C19.8863 18.2239 19.5195 18.0388 19.1956 17.7811C18.5578 17.2722 18.6801 16.4461 19.4269 16.1189C19.6384 16.0264 19.8598 15.9966 20.0845 15.9834C20.9503 15.9372 21.7732 16.0958 22.5563 16.4725C22.9463 16.6609 23.0751 16.6014 23.2073 16.1949C23.3461 15.7653 23.4618 15.3291 23.5906 14.8962C23.6766 14.6054 23.5708 14.4138 23.2965 14.2915C22.7943 14.0701 22.2787 13.9115 21.7368 13.8256C21.0296 13.7165 21.0296 13.7132 21.0263 13.0027C21.023 12.0015 21.023 12.0015 20.0184 12.0015C19.873 12.0015 19.7276 11.9982 19.5823 12.0015C19.113 12.0147 19.0337 12.0973 19.0205 12.5698C19.0139 12.7813 19.0205 12.9928 19.0172 13.2076C19.0139 13.8355 19.0106 13.8256 18.4091 14.0437C16.9552 14.5724 16.0563 15.5638 15.9605 17.1499C15.8746 18.5543 16.6082 19.5027 17.7615 20.1934C18.4719 20.6197 19.2584 20.8708 20.0118 21.2046C20.3059 21.3335 20.5868 21.4822 20.8314 21.687C21.5551 22.2852 21.4229 23.2798 20.5637 23.6565C20.1044 23.8581 19.6186 23.9077 19.1196 23.8449C18.3497 23.7491 17.6128 23.5475 16.9188 23.1873C16.5123 22.9758 16.3934 23.032 16.2546 23.4715C16.1356 23.8515 16.0299 24.2348 15.9241 24.6182C15.782 25.1337 15.8349 25.2559 16.3273 25.4972C16.9551 25.8012 17.626 25.9565 18.31 26.0655C18.8453 26.1515 18.8619 26.1746 18.8685 26.7298C18.8718 26.9809 18.8718 27.2353 18.8751 27.4865C18.8784 27.8037 19.0304 27.9888 19.3575 27.9954C19.7276 28.002 20.1011 28.002 20.4712 27.9921C20.7752 27.9855 20.9305 27.8202 20.9305 27.5129C20.9305 27.1693 20.947 26.8223 20.9338 26.4786C20.9173 26.1283 21.0693 25.9499 21.4064 25.8574C22.1829 25.6459 22.8438 25.2295 23.3527 24.6082C24.7671 22.8899 24.2284 20.3751 22.2259 19.2681Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -10,7 +10,8 @@ import { makeNotificationsModule } from '@/store/modules/notifications';
import { makePaymentsModule, PAYMENTS_MUTATIONS } from '@/store/modules/payments';
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
import { makeUsersModule, USER_MUTATIONS } from '@/store/modules/users';
import { CreditCard } from '@/types/payments';
import { NOTIFICATION_MUTATIONS } from '@/store/mutationConstants';
import { BillingHistoryItem, BillingHistoryItemStatus, BillingHistoryItemType, CreditCard } from '@/types/payments';
import { Project } from '@/types/projects';
import { User } from '@/types/users';
import { Notificator } from '@/utils/plugins/notificator';
@ -71,7 +72,26 @@ describe('PaymentMethods', () => {
wrapper.vm.$data.tokenDepositValue = 1000000;
await wrapper.vm.onConfirmAddSTORJ();
expect((store.state as any).notificationsModule.notificationQueue[0].message).toMatch('First deposit amount must be more than 50 and less than 1000000');
expect((store.state as any).notificationsModule.notificationQueue[1].message).toMatch('First deposit amount must be more than 50 and less than 1000000');
});
it('user is able to add less than 50$ after coupon is applied', async () => {
window.open = jest.fn();
const billingTransactionItem = new BillingHistoryItem('itemId', 'test', 50, 50,
BillingHistoryItemStatus.Completed, 'test', new Date(), new Date(), BillingHistoryItemType.Transaction);
const project = new Project('testId', 'test', 'test', 'test', 'id', true);
store.commit(NOTIFICATION_MUTATIONS.CLEAR);
store.commit(PAYMENTS_MUTATIONS.SET_BILLING_HISTORY, [billingTransactionItem]);
store.commit(PROJECTS_MUTATIONS.ADD, project);
const wrapper = mount(PaymentMethods, {
store,
localVue,
});
wrapper.vm.$data.tokenDepositValue = 30;
await wrapper.vm.onConfirmAddSTORJ();
expect((store.state as any).notificationsModule.notificationQueue[0].type).toMatch('SUCCESS');
});
it('renders correctly after add card and cancel click', async (done) => {

View File

@ -70,7 +70,7 @@ exports[`PaymentMethods renders correctly after add STORJ and cancel click 2`] =
<h1 class="payment-methods-area__functional-area__title text">Payment Method</h1>
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="button container" style="width: 123px; height: 48px;">
<div class="button container blue-white" style="width: 123px; height: 48px;">
<h1 class="label">Add STORJ</h1>
</div>
</div>
@ -142,7 +142,7 @@ exports[`PaymentMethods renders correctly after add card and cancel click 2`] =
<h1 class="payment-methods-area__functional-area__title text">Payment Method</h1>
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<vbutton-stub label="Add STORJ" width="123px" height="48px" onpress="function () { [native code] }" class="button"></vbutton-stub>
<vbutton-stub label="Add STORJ" width="123px" height="48px" isbluewhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
</div>
</div>
</div>
@ -166,7 +166,7 @@ exports[`PaymentMethods renders correctly after continue To Coin Payments click
<h1 class="payment-methods-area__functional-area__title text">Payment Method</h1>
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="button container" style="width: 123px; height: 48px;">
<div class="button container blue-white" style="width: 123px; height: 48px;">
<h1 class="label">Add STORJ</h1>
</div>
</div>
@ -206,7 +206,7 @@ exports[`PaymentMethods renders correctly with card 1`] = `
<h1 class="payment-methods-area__functional-area__title text">Payment Method</h1>
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="button container" style="width: 123px; height: 48px;">
<div class="button container blue-white" style="width: 123px; height: 48px;">
<h1 class="label">Add STORJ</h1>
</div>
</div>
@ -276,7 +276,7 @@ exports[`PaymentMethods renders correctly without card 1`] = `
<h1 class="payment-methods-area__functional-area__title text">Payment Method</h1>
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="button container" style="width: 123px; height: 48px;">
<div class="button container blue-white" style="width: 123px; height: 48px;">
<h1 class="label">Add STORJ</h1>
</div>
</div>

View File

@ -0,0 +1,31 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import VInfoBar from '@/components/common/VInfoBar.vue';
import { router } from '@/router';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('VInfoBar.vue', () => {
it('renders correctly', () => {
const wrapper = shallowMount(VInfoBar, {
propsData: {
firstValue: '5GB',
secondValue: '5GB',
firstDescription: 'test1',
secondDescription: 'test2',
link: 'testlink',
linkLabel: 'label',
path: '/',
},
router,
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VInfoBar.vue renders correctly 1`] = `
<div class="info-bar">
<div class="info-bar__info-area"><b class="info-bar__info-area__first-value">5GB</b> <span class="info-bar__info-area__first-description">test1</span> <b class="info-bar__info-area__second-value">5GB</b> <span class="info-bar__info-area__second-description">test2</span>
<router-link-stub to="/" tag="a" event="click" class="info-bar__info-area__button">Details</router-link-stub>
</div> <a href="testlink" target="_blank" class="info-bar__link">label</a>
</div>
`;

View File

@ -39,7 +39,7 @@ describe('NewProjectArea', () => {
});
expect(wrapper).toMatchSnapshot();
expect(wrapper.findAll('.new-project-button-container').length).toBe(0);
expect(wrapper.findAll('.new-project-button-container').length).toBe(0); // user is unable to create project.
});
it('renders correctly without projects and without payment methods with info tooltip', async () => {
@ -89,10 +89,8 @@ describe('NewProjectArea', () => {
const user = new User('ownerId', 'test', 'test', 'test@test.test', 'test', 'test');
const project = new Project('id', 'test', 'test', 'test', 'ownerId', true);
store.commit(APP_STATE_MUTATIONS.TOGGLE_NEW_PROJECT_POPUP);
store.commit(PAYMENTS_MUTATIONS.SET_BILLING_HISTORY, []);
store.commit(USER_MUTATIONS.SET_USER, user);
store.commit(PROJECTS_MUTATIONS.SET_PROJECTS, [project]);
store.commit(APP_STATE_MUTATIONS.HIDE_CREATE_PROJECT_BUTTON);
const wrapper = mount(NewProjectArea, {
store,

View File

@ -3,7 +3,7 @@
exports[`NavigationArea snapshot not changed with project 1`] = `
<div class="navigation-area">
<div class="navigation-area__wrap">
<router-link-stub to="/project-overview" tag="a" event="click" class="navigation-area__logo">
<router-link-stub to="/account/billing" tag="a" event="click" class="navigation-area__logo">
<logoicon-stub class="navigation-area__logo__img"></logoicon-stub>
<logotexticon-stub class="navigation-area__logo__text"></logotexticon-stub>
</router-link-stub>
@ -88,7 +88,7 @@ exports[`NavigationArea snapshot not changed with project 1`] = `
exports[`NavigationArea snapshot not changed without project 1`] = `
<div class="navigation-area">
<div class="navigation-area__wrap">
<router-link-stub to="/project-overview" tag="a" event="click" class="navigation-area__logo">
<router-link-stub to="/account/billing" tag="a" event="click" class="navigation-area__logo">
<logoicon-stub class="navigation-area__logo__img"></logoicon-stub>
<logotexticon-stub class="navigation-area__logo__text"></logotexticon-stub>
</router-link-stub>

View File

@ -7,8 +7,6 @@ import { BillingHistoryItem, CreditCard, PaymentsApi, ProjectCharge, TokenDeposi
* Mock for PaymentsApi
*/
export class PaymentsMock implements PaymentsApi {
private tokenDeposit: TokenDeposit;
setupAccount(): Promise<void> {
throw new Error('Method not implemented');
}
@ -42,6 +40,6 @@ export class PaymentsMock implements PaymentsApi {
}
makeTokenDeposit(amount: number): Promise<TokenDeposit> {
return Promise.resolve(this.tokenDeposit);
return Promise.resolve(new TokenDeposit(amount, 'testAddress', 'testLink'));
}
}

View File

@ -3,11 +3,14 @@
import OverviewArea from '@/components/overview/OverviewArea.vue';
import { shallowMount } from '@vue/test-utils';
import { router } from '@/router';
import { mount } from '@vue/test-utils';
describe('OverviewArea.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(OverviewArea);
const wrapper = mount(OverviewArea, {
router,
});
expect(wrapper).toMatchSnapshot();
});

View File

@ -2,18 +2,25 @@
exports[`OverviewArea.vue renders correctly 1`] = `
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<p class="overview-area__sub-title">
To get started, lets add a payment method.<br>Well provide you with a $55 credit right away!
</p>
<router-link-stub to="/account/billing" tag="a" event="click" class="overview-area__button">Add Payment Info &amp; Get $55</router-link-stub> <span class="overview-area__charge-info">You will not be charged unless $55 is used</span>
<div class="overview-area__welcome-container">
<h1 class="overview-area__welcome-container__title">Welcome to Storj</h1>
<p class="overview-area__welcome-container__sub-title">
To create a project, get started by adding a payment method - <br>your first 5GB are free for your first project.
</p> <a href="/account/billing" class="overview-area__welcome-container__button">Add Payment</a>
</div>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__numbers">
<firststepicon-stub class="overview-area__steps-area__numbers__icon"></firststepicon-stub>
<div class="overview-area-divider"></div>
<secondstepicon-stub class="overview-area__steps-area__numbers__icon"></secondstepicon-stub>
<div class="overview-area-divider"></div>
<thirdstepicon-stub class="overview-area__steps-area__numbers__icon"></thirdstepicon-stub>
<div class="overview-area__steps-area__numbers"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__numbers__icon">
<circle cx="15" cy="15" r="15" fill="#519CFF"></circle>
<path d="M17.0916 9.36364H14.7791L11.8984 11.1875V13.3693L14.5632 11.6989H14.6314V21H17.0916V9.36364Z" fill="white"></path>
</svg>
<div class="overview-area-divider"></div> <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__numbers__icon">
<circle cx="15" cy="15" r="15" fill="#519CFF"></circle>
<path d="M11.4041 21H19.6996V18.9886H14.8132V18.9091L16.5121 17.2443C18.9041 15.0625 19.5462 13.9716 19.5462 12.6477C19.5462 10.6307 17.8984 9.20455 15.4041 9.20455C12.9609 9.20455 11.2848 10.6648 11.2905 12.9489H13.6257C13.62 11.8352 14.3246 11.1534 15.3871 11.1534C16.4098 11.1534 17.1712 11.7898 17.1712 12.8125C17.1712 13.7386 16.603 14.375 15.5462 15.392L11.4041 19.2273V21Z" fill="white"></path>
</svg>
<div class="overview-area-divider"></div> <svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__numbers__icon">
<circle cx="15" cy="15" r="15" fill="#519CFF"></circle>
<path d="M15.4709 21.1591C18.0845 21.1591 19.9538 19.7216 19.9482 17.733C19.9538 16.2841 19.0334 15.25 17.3232 15.0341V14.9432C18.6243 14.7102 19.522 13.7898 19.5163 12.483C19.522 10.6477 17.9141 9.20455 15.505 9.20455C13.1186 9.20455 11.3232 10.6023 11.2891 12.6136H13.647C13.6754 11.7273 14.4879 11.1534 15.4936 11.1534C16.4879 11.1534 17.1527 11.7557 17.147 12.6307C17.1527 13.5455 16.3743 14.1648 15.255 14.1648H14.1697V15.9716H15.255C16.5732 15.9716 17.397 16.6307 17.3913 17.5682C17.397 18.4943 16.6016 19.1307 15.4766 19.1307C14.3913 19.1307 13.5788 18.5625 13.5334 17.7102H11.0561C11.0959 19.7443 12.9141 21.1591 15.4709 21.1591Z" fill="white"></path>
</svg>
</div>
<div class="overview-area__steps-area__items">
<div class="overview-area__steps-area__items__add-payment-info">

View File

@ -0,0 +1,40 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
import { makeUsersModule, USER_MUTATIONS } from '@/store/modules/users';
import { Project } from '@/types/projects';
import { User } from '@/types/users';
import { ProjectOwning } from '@/utils/projectOwning';
import { createLocalVue } from '@vue/test-utils';
import { ProjectsApiMock } from '../mock/api/projects';
import { UsersApiMock } from '../mock/api/users';
const usersApi = new UsersApiMock();
const usersModule = makeUsersModule(usersApi);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const localVue = createLocalVue();
localVue.use(Vuex);
const store = new Vuex.Store({ modules: { usersModule, projectsModule }});
describe('projectOwning', () => {
it('user has no project', () => {
const user = new User('ownerId');
store.commit(USER_MUTATIONS.SET_USER, user);
expect(new ProjectOwning(store).userHasOwnProject()).toBeFalsy();
});
it('user has project', () => {
const project = new Project('id', 'test', 'test', 'test', 'ownerId', true);
store.commit(PROJECTS_MUTATIONS.ADD, project);
expect(new ProjectOwning(store).userHasOwnProject()).toBeTruthy();
});
});

View File

@ -8,7 +8,7 @@ exports[`Dashboard renders correctly when data is loaded 1`] = `
<div class="dashboard-container__wrap__column">
<dashboardheader-stub></dashboardheader-stub>
<div class="dashboard-container__main-area">
<div class="dashboard-container__main-area__banner-area">
<div class="dashboard-container__main-area__bar-area">
<!---->
</div>
<div class="dashboard-container__main-area__content">
@ -36,7 +36,7 @@ exports[`Dashboard renders correctly without project and with payment method 1`]
<div class="dashboard-container__wrap__column">
<dashboardheader-stub></dashboardheader-stub>
<div class="dashboard-container__main-area">
<div class="dashboard-container__main-area__banner-area">
<div class="dashboard-container__main-area__bar-area">
<!---->
</div>
<div class="dashboard-container__main-area__content">
@ -67,7 +67,7 @@ exports[`Dashboard renders correctly without project and with payment method 2`]
<div class="dashboard-container__wrap__column">
<dashboardheader-stub></dashboardheader-stub>
<div class="dashboard-container__main-area">
<div class="dashboard-container__main-area__banner-area">
<div class="dashboard-container__main-area__bar-area">
<!---->
</div>
<div class="dashboard-container__main-area__content">