web/satellite: onboarding tour: adding payment methods step

Change-Id: I40c6680de4778700611f2f6978a02688d50d792f
This commit is contained in:
VitaliiShpital 2020-04-22 19:21:12 +03:00
parent 03871d17c3
commit befe7574e1
75 changed files with 2185 additions and 435 deletions

View File

@ -57,9 +57,11 @@ import VDatepicker from '@/components/common/VDatePicker.vue';
import DatePickerIcon from '@/../static/images/account/billing/datePicker.svg'; import DatePickerIcon from '@/../static/images/account/billing/datePicker.svg';
import { RouteConfig } from '@/router';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments'; import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { DateRange } from '@/types/payments'; import { DateRange } from '@/types/payments';
import { Project } from '@/types/projects';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { ProjectOwning } from '@/utils/projectOwning'; import { ProjectOwning } from '@/utils/projectOwning';
@ -93,6 +95,12 @@ export default class BillingArea extends Vue {
* Fetches billing history and project limits. * Fetches billing history and project limits.
*/ */
public async beforeMount(): Promise<void> { public async beforeMount(): Promise<void> {
if (!this.$store.getters.selectedProject.id) {
await this.$router.push(RouteConfig.OnboardingTour.path);
return;
}
try { try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BILLING_HISTORY); await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BILLING_HISTORY);
if (this.$store.getters.canUserCreateFirstProject && !this.userHasOwnProject) { if (this.$store.getters.canUserCreateFirstProject && !this.userHasOwnProject) {
@ -103,10 +111,6 @@ export default class BillingArea extends Vue {
await this.$notify.error(error.message); await this.$notify.error(error.message);
} }
if (!this.$store.getters.selectedProject.id) {
return;
}
try { try {
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id); await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id);
} catch (error) { } catch (error) {

View File

@ -5,9 +5,7 @@
<div class="billing-history-area"> <div class="billing-history-area">
<div class="billing-history-area__title-area" @click="onBackToAccountClick"> <div class="billing-history-area__title-area" @click="onBackToAccountClick">
<div class="billing-history-area__title-area__back-button"> <div class="billing-history-area__title-area__back-button">
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg"> <BackImage/>
<path class="billing-history-svg-path" fill-rule="evenodd" clip-rule="evenodd" d="M7.22572 0.300601C7.62652 0.701402 7.62652 1.35123 7.22572 1.75203L3.50406 5.47368H11.9737C12.5405 5.47368 13 5.93318 13 6.5C13 7.06682 12.5405 7.52632 11.9737 7.52632H3.50406L7.22572 11.248C7.62652 11.6488 7.62652 12.2986 7.22572 12.6994C6.82491 13.1002 6.17509 13.1002 5.77429 12.6994L0.300601 7.22571C-0.1002 6.82491 -0.1002 6.17509 0.300601 5.77428L5.77429 0.300601C6.17509 -0.1002 6.82491 -0.1002 7.22572 0.300601Z" fill="#384B65"/>
</svg>
</div> </div>
<p class="billing-history-area__title-area__title">Back to Account</p> <p class="billing-history-area__title-area__title">Back to Account</p>
</div> </div>
@ -36,6 +34,8 @@ import BillingItem from '@/components/account/billing/billingHistory/BillingItem
import SortingHeader from '@/components/account/billing/billingHistory/SortingHeader.vue'; import SortingHeader from '@/components/account/billing/billingHistory/SortingHeader.vue';
import VPagination from '@/components/common/VPagination.vue'; import VPagination from '@/components/common/VPagination.vue';
import BackImage from '@/../static/images/account/billing/back.svg';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { BillingHistoryItem } from '@/types/payments'; import { BillingHistoryItem } from '@/types/payments';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@ -45,6 +45,7 @@ import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
BillingItem, BillingItem,
SortingHeader, SortingHeader,
VPagination, VPagination,
BackImage,
}, },
}) })
export default class BillingHistory extends Vue { export default class BillingHistory extends Vue {
@ -118,7 +119,7 @@ export default class BillingHistory extends Vue {
.billing-history-area__title-area__back-button { .billing-history-area__title-area__back-button {
background-color: #2683ff; background-color: #2683ff;
.billing-history-svg-path { .back-button-svg-path {
fill: #fff; fill: #fff;
} }
} }

View File

@ -54,8 +54,6 @@ export default class AddCardForm extends Vue {
try { try {
await this.$store.dispatch(ADD_CREDIT_CARD, token); await this.$store.dispatch(ADD_CREDIT_CARD, token);
await this.$store.dispatch(GET_BILLING_HISTORY);
await this.$store.dispatch(GET_BALANCE);
} catch (error) { } catch (error) {
await this.$notify.error(error.message); await this.$notify.error(error.message);

View File

@ -114,13 +114,13 @@ export default class AddStorjForm extends Vue {
*/ */
private get isDepositValueValid(): boolean { private get isDepositValueValid(): boolean {
switch (true) { switch (true) {
case (this.tokenDepositValue < 50 || this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT) && !this.userHasOwnProject: case (this.tokenDepositValue < this.DEFAULT_TOKEN_DEPOSIT_VALUE || this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT) && !this.userHasOwnProject:
this.$notify.error('First deposit amount must be more than 50 and less than 1000000'); this.$notify.error('First deposit amount must be more than $50 and less than $1000000');
this.setDefault(); this.setDefault();
return false; return false;
case this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT || this.tokenDepositValue === 0: case this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT || this.tokenDepositValue === 0:
this.$notify.error('Deposit amount must be more than 0 and less than 1000000'); this.$notify.error('Deposit amount must be more than $0 and less than $1000000');
this.setDefault(); this.setDefault();
return false; return false;

View File

@ -65,7 +65,6 @@
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { PaymentAmountOption } from '@/types/payments'; import { PaymentAmountOption } from '@/types/payments';
import { Project } from '@/types/projects';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames'; import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { ProjectOwning } from '@/utils/projectOwning'; import { ProjectOwning } from '@/utils/projectOwning';
@ -276,6 +275,7 @@ export default class TokenDepositSelection extends Vue {
&__svg { &__svg {
cursor: pointer; cursor: pointer;
min-height: 25px;
} }
} }
} }

View File

@ -113,6 +113,10 @@ export default class VButton extends Vue {
} }
} }
&.blue-white {
border: 2px solid #2683ff !important;
}
&.red { &.red {
box-shadow: none !important; box-shadow: none !important;
background-color: transparent !important; background-color: transparent !important;

View File

@ -4,7 +4,7 @@
<template> <template>
<div class="account-dropdown-choice-container" id="accountDropdown"> <div class="account-dropdown-choice-container" id="accountDropdown">
<div class="account-dropdown-overflow-container"> <div class="account-dropdown-overflow-container">
<div class="account-dropdown-item-container settings" @click="onAccountSettingsClick"> <div class="account-dropdown-item-container settings" v-if="!isOnboardingTour" @click="onAccountSettingsClick">
<div class="account-dropdown-item-container__image-container"> <div class="account-dropdown-item-container__image-container">
<AccountSettingsIcon class="account-dropdown-item-container__image-container__image"/> <AccountSettingsIcon class="account-dropdown-item-container__image-container__image"/>
</div> </div>
@ -48,6 +48,13 @@ import { LocalData } from '@/utils/localData';
export default class AccountDropdown extends Vue { export default class AccountDropdown extends Vue {
private readonly auth: AuthHttpApi = new AuthHttpApi(); private readonly auth: AuthHttpApi = new AuthHttpApi();
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
}
/** /**
* Closes account dropdown. * Closes account dropdown.
*/ */

View File

@ -3,14 +3,6 @@
<template> <template>
<div class="new-project-container"> <div class="new-project-container">
<VInfo
v-if="isMockButtonShown"
text="Please add a payment method"
>
<div class="new-project-button-mock">
<h1 class="new-project-button-mock__label">+ Create Project</h1>
</div>
</VInfo>
<div <div
v-if="isButtonShown" v-if="isButtonShown"
class="new-project-button-container" class="new-project-button-container"
@ -75,13 +67,6 @@ export default class NewProjectArea extends Vue {
return this.$store.state.appStateModule.appState.isCreateProjectButtonShown; return this.$store.state.appStateModule.appState.isCreateProjectButtonShown;
} }
/**
* Indicates if new project creation mock button is shown.
*/
public get isMockButtonShown(): boolean {
return !(this.userHasOwnProject || this.$store.getters.canUserCreateFirstProject);
}
/** /**
* Indicates if user has own project. * Indicates if user has own project.
*/ */
@ -124,39 +109,4 @@ export default class NewProjectArea extends Vue {
} }
} }
} }
.new-project-button-mock {
display: flex;
align-items: center;
justify-content: center;
width: 156px;
height: 40px;
border-radius: 6px;
background-color: #dadde5;
border: 1px solid #dadde5;
&__label {
font-family: 'font_medium', sans-serif;
font-size: 15px;
line-height: 22px;
color: #acb0bc;
}
}
/deep/ .info__message-box {
background-image: url('../../../static/images/header/info.png');
background-repeat: no-repeat;
height: auto;
width: auto;
top: 41px;
left: 157px;
padding: 30px 20px 25px 20px;
white-space: nowrap;
&__text {
text-align: left;
font-size: 13px;
line-height: 17px;
}
}
</style> </style>

View File

@ -31,29 +31,44 @@ export default class NavigationArea extends Vue {
*/ */
public areResourceItemsShown: boolean = true; public areResourceItemsShown: boolean = true;
public isResourceButtonShown: boolean = false; public isResourceButtonShown: boolean = false;
/** /**
* Indicates if account related navigation links appears. * Indicates if account related navigation links appears.
*/ */
public areAccountItemsShown: boolean = true; public areAccountItemsShown: boolean = true;
public isAccountButtonShown: boolean = false; public isAccountButtonShown: boolean = false;
public homePath: string = RouteConfig.Account.with(RouteConfig.Billing).path;
/**
* Toggles resource items visibility.
*/
public toggleResourceItemsVisibility(): void { public toggleResourceItemsVisibility(): void {
this.areResourceItemsShown = !this.areResourceItemsShown; this.areResourceItemsShown = !this.areResourceItemsShown;
} }
/**
* Toggles resource button visibility.
*/
public toggleResourceButtonVisibility(): void { public toggleResourceButtonVisibility(): void {
this.isResourceButtonShown = !this.isResourceButtonShown; this.isResourceButtonShown = !this.isResourceButtonShown;
} }
/**
* Toggles account items visibility.
*/
public toggleAccountItemsVisibility(): void { public toggleAccountItemsVisibility(): void {
this.areAccountItemsShown = !this.areAccountItemsShown; this.areAccountItemsShown = !this.areAccountItemsShown;
} }
/**
* Toggles account button visibility.
*/
public toggleAccountButtonVisibility(): void { public toggleAccountButtonVisibility(): void {
this.isAccountButtonShown = !this.isAccountButtonShown; this.isAccountButtonShown = !this.isAccountButtonShown;
} }
/**
* Array of navigation links with icons.
*/
// TODO: Use SvgLoaderComponent to reduce markup lines // TODO: Use SvgLoaderComponent to reduce markup lines
public readonly navigation: NavigationLink[] = [ public readonly navigation: NavigationLink[] = [
RouteConfig.ProjectDashboard.withIcon( RouteConfig.ProjectDashboard.withIcon(
@ -73,6 +88,9 @@ export default class NavigationArea extends Vue {
`), `),
]; ];
/**
* Array of account related navigation links.
*/
public readonly accountNavigation: NavigationLink[] = [ public readonly accountNavigation: NavigationLink[] = [
RouteConfig.Account.with(RouteConfig.Settings), RouteConfig.Account.with(RouteConfig.Settings),
RouteConfig.Account.with(RouteConfig.Billing), RouteConfig.Account.with(RouteConfig.Billing),
@ -80,25 +98,65 @@ export default class NavigationArea extends Vue {
// RouteConfig.Account.with(RouteConfig.Referral), // RouteConfig.Account.with(RouteConfig.Referral),
]; ];
public get isLinkDisabled(): boolean { /**
return this.$store.state.projectsModule.selectedProject.id === ''; * Returns home path depending on app's state.
*/
public get homePath(): string {
if (this.isOnboardingTour) {
return RouteConfig.OnboardingTour.path;
}
return RouteConfig.ProjectDashboard.path;
} }
/**
* Indicates if resources displaying button is shown.
*/
public get isResourcesDisplayingButtonShown(): boolean { public get isResourcesDisplayingButtonShown(): boolean {
return !this.areResourceItemsShown && this.isResourceButtonShown; return !this.areResourceItemsShown && this.isResourceButtonShown;
} }
/**
* Indicates if resources items hiding button is shown.
*/
public get isResourcesHidingButtonShown(): boolean { public get isResourcesHidingButtonShown(): boolean {
return this.areResourceItemsShown && this.isResourceButtonShown; return this.areResourceItemsShown && this.isResourceButtonShown;
} }
/**
* Indicates if account items displaying button is shown.
*/
public get isAccountItemsDisplayingButtonShown(): boolean { public get isAccountItemsDisplayingButtonShown(): boolean {
return !this.areAccountItemsShown && this.isAccountButtonShown; return !this.areAccountItemsShown && this.isAccountButtonShown;
} }
/**
* Indicates if account items hiding button is shown.
*/
public get isAccountItemsHidingButtonShown(): boolean { public get isAccountItemsHidingButtonShown(): boolean {
return this.areAccountItemsShown && this.isAccountButtonShown; return this.areAccountItemsShown && this.isAccountButtonShown;
} }
/**
* Indicates if roter link is disabled.
*/
public get isLinkDisabled(): boolean {
return this.isOnboardingTour || this.isNoProject;
}
/**
* Indicates if current route is onboarding tour.
*/
private get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
}
/**
* Indicates if there is no projects.
*/
private get isNoProject(): boolean {
return this.$store.state.projectsModule.projects.length === 0;
}
} }
</script> </script>

View File

@ -9,41 +9,100 @@
</router-link> </router-link>
<ProjectSelectionArea class="project-selection-area"/> <ProjectSelectionArea class="project-selection-area"/>
<p class="navigation-area__project-title">PROJECT</p> <p class="navigation-area__project-title">PROJECT</p>
<router-link :aria-label="navItem.name" class="navigation-area__item-container" :class="{ disabled: isLinkDisabled }" v-for="navItem in navigation" :key="navItem.name" :to="navItem.path"> <router-link
:aria-label="navItem.name"
class="navigation-area__item-container"
:class="{ disabled: isLinkDisabled }"
v-for="navItem in navigation"
:key="navItem.name"
:to="navItem.path"
>
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<div v-html="navItem.icon"></div> <div v-html="navItem.icon"></div>
<h1 class="navigation-area__item-container__link-container__title">{{navItem.name}}</h1> <h1 class="navigation-area__item-container__link-container__title">{{navItem.name}}</h1>
</div> </div>
</router-link> </router-link>
<div class="navigation-area__resources-title" :class="{ custom: areResourceItemsShown }" @mouseenter="toggleResourceButtonVisibility" @mouseleave="toggleResourceButtonVisibility"> <div
class="navigation-area__resources-title"
:class="{ custom: areResourceItemsShown }"
@mouseenter="toggleResourceButtonVisibility"
@mouseleave="toggleResourceButtonVisibility"
>
<p class="navigation-area__resources-title__title">RESOURCES</p> <p class="navigation-area__resources-title__title">RESOURCES</p>
<p v-if="isResourcesDisplayingButtonShown" @click="toggleResourceItemsVisibility" class="navigation-area__resources-title__button">Show</p> <p
<p v-if="isResourcesHidingButtonShown" @click="toggleResourceItemsVisibility" class="navigation-area__resources-title__button">Hide</p> v-if="isResourcesDisplayingButtonShown"
@click="toggleResourceItemsVisibility"
class="navigation-area__resources-title__button"
>
Show
</p>
<p
v-if="isResourcesHidingButtonShown"
@click="toggleResourceItemsVisibility"
class="navigation-area__resources-title__button"
>
Hide
</p>
</div> </div>
<a v-if="areResourceItemsShown" class="navigation-area__item-container" href="https://documentation.tardigrade.io" target="_blank"> <a
v-if="areResourceItemsShown"
class="navigation-area__item-container"
:class="{ disabled: isLinkDisabled }"
href="https://documentation.storj.io"
target="_blank"
>
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<DocsIcon class="svg"/> <DocsIcon class="svg"/>
<h1 class="navigation-area__item-container__link-container__title docs-title">Docs</h1> <h1 class="navigation-area__item-container__link-container__title docs-title">Docs</h1>
</div> </div>
</a> </a>
<a v-if="areResourceItemsShown" class="navigation-area__item-container support-item" href="mailto:support@storj.io" target="_blank"> <a
v-if="areResourceItemsShown"
class="navigation-area__item-container support-item"
:class="{ disabled: isLinkDisabled }"
href="mailto:support@storj.io"
target="_blank"
>
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<SupportIcon class="svg"/> <SupportIcon class="svg"/>
<h1 class="navigation-area__item-container__link-container__title">Support</h1> <h1 class="navigation-area__item-container__link-container__title">Support</h1>
</div> </div>
</a> </a>
<div class="navigation-area__account-title" :class="{custom: areAccountItemsShown}" @mouseenter="toggleAccountButtonVisibility" @mouseleave="toggleAccountButtonVisibility"> <div
class="navigation-area__account-title"
:class="{ custom: areAccountItemsShown }"
@mouseenter="toggleAccountButtonVisibility"
@mouseleave="toggleAccountButtonVisibility"
>
<p class="navigation-area__account-title__title">ACCOUNT</p> <p class="navigation-area__account-title__title">ACCOUNT</p>
<p v-if="isAccountItemsDisplayingButtonShown" @click="toggleAccountItemsVisibility" class="navigation-area__account-title__button">Show</p> <p
<p v-if="isAccountItemsHidingButtonShown" @click="toggleAccountItemsVisibility" class="navigation-area__account-title__button">Hide</p> v-if="isAccountItemsDisplayingButtonShown"
@click="toggleAccountItemsVisibility"
class="navigation-area__account-title__button"
>
Show
</p>
<p
v-if="isAccountItemsHidingButtonShown"
@click="toggleAccountItemsVisibility"
class="navigation-area__account-title__button"
>
Hide
</p>
</div> </div>
<div v-if="areAccountItemsShown" class="navigation-area__account-area"> <div v-if="areAccountItemsShown" class="navigation-area__account-area">
<router-link class="navigation-area__item-container account-item" v-for="navItem in accountNavigation" :key="navItem.name" :to="navItem.path"> <router-link
class="navigation-area__item-container account-item"
:class="{ disabled: isLinkDisabled }"
v-for="navItem in accountNavigation"
:key="navItem.name"
:to="navItem.path"
>
<div class="navigation-area__item-container__link-container" > <div class="navigation-area__item-container__link-container" >
<h1 class="navigation-area__item-container__link-container__title account-item">{{navItem.name}}</h1> <h1 class="navigation-area__item-container__link-container__title account-item">{{navItem.name}}</h1>
</div> </div>
</router-link> </router-link>
</div> </div>
<div class="divider" :class="{'custom-divider': areAccountItemsShown}"></div> <div class="divider" :class="{ 'custom-divider': areAccountItemsShown }"></div>
</div> </div>
</div> </div>

View File

@ -152,6 +152,7 @@ a {
} }
.disabled { .disabled {
cursor: not-allowed;
pointer-events: none; pointer-events: none;
opacity: 0.6; opacity: 0.6;
} }

View File

@ -0,0 +1,102 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="tour-area">
<ProgressBar/>
<OverviewStep
v-if="isDefaultState"
@setAddPaymentState="setAddPaymentState"
/>
<AddPaymentStep
v-if="isAddPaymentState"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ProgressBar from '@/components/onboardingTour/ProgressBar.vue';
import AddPaymentStep from '@/components/onboardingTour/steps/AddPaymentStep.vue';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import CheckedImage from '@/../static/images/common/checked.svg';
import { RouteConfig } from '@/router';
import { TourState } from '@/utils/constants/onboardingTourEnums';
@Component({
components: {
AddPaymentStep,
ProgressBar,
OverviewStep,
CheckedImage,
},
})
export default class OnboardingTourArea extends Vue {
public areaState: number = TourState.DEFAULT;
/**
* Lifecycle hook after initial render.
* Sets area to needed state.
*/
public mounted(): void {
if (this.$store.state.projectsModule.projects.length > 0) {
try {
this.$router.push(RouteConfig.ProjectDashboard.path);
} catch (error) {
return;
}
return;
}
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isTransactionCompleted) {
this.setAddPaymentState();
}
}
/**
* Indicates if area is in default state.
*/
public get isDefaultState(): boolean {
return this.areaState === TourState.DEFAULT;
}
/**
* Indicates if area is adding payment method state.
*/
public get isAddPaymentState(): boolean {
return this.areaState === TourState.ADDING_PAYMENT;
}
/**
* Sets area's state to adding payment method state.
*/
public setAddPaymentState(): void {
this.areaState = TourState.ADDING_PAYMENT;
}
}
</script>
<style scoped lang="scss">
.tour-area {
padding: 0 100px;
}
@media screen and (max-width: 1380px) {
.tour-area {
padding: 0 50px;
}
}
@media screen and (max-width: 1000px) {
.tour-area {
padding: 0 25px;
}
}
</style>

View File

@ -0,0 +1,102 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="progress-bar-container">
<div class="progress-bar-container__progress-area">
<div class="progress-bar-container__progress-area__circle">
<CheckedImage/>
</div>
<div class="progress-bar-container__progress-area__bar"/>
<div class="progress-bar-container__progress-area__circle">
<CheckedImage/>
</div>
<div class="progress-bar-container__progress-area__bar"/>
<div class="progress-bar-container__progress-area__circle">
<CheckedImage/>
</div>
</div>
<div class="progress-bar-container__titles-area">
<span class="progress-bar-container__titles-area__title">Name Your Project</span>
<span class="progress-bar-container__titles-area__title api-key-title">Create an API Key</span>
<span class="progress-bar-container__titles-area__title">Upload Data</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CheckedImage from '@/../static/images/common/checked.svg';
@Component({
components: {
CheckedImage,
},
})
export default class ProgressBar extends Vue {}
</script>
<style scoped lang="scss">
.progress-bar-container {
width: 100%;
&__progress-area {
width: calc(100% - 420px);
display: flex;
justify-content: space-between;
align-items: center;
padding: 25px 210px 6px 210px;
&__circle {
display: flex;
justify-content: center;
align-items: center;
min-width: 20px;
height: 20px;
background-color: #c5cbdb;
border-radius: 10px;
}
&__bar {
width: 100%;
height: 4px;
background-color: #c5cbdb;
}
}
&__titles-area {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 188px 0 178px;
&__title {
font-family: 'font_regular', sans-serif;
font-size: 10px;
line-height: 15px;
color: rgba(0, 0, 0, 0.4);
}
}
}
.api-key-title {
padding: 0 15px 0 0;
}
@media screen and (max-width: 800px) {
.progress-bar-container {
&__progress-area {
width: calc(100% - 300px);
padding: 25px 150px 6px 150px;
}
&__titles-area {
padding: 0 128px 0 118px;
}
}
}
</style>

View File

@ -0,0 +1,217 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="payment-step">
<h1 class="payment-step__title">Get Started with 5 GB Free</h1>
<p class="payment-step__sub-title">
Adding a payment method ensures your project wont be interrupted after your <b>free</b> credit is used.
</p>
<div class="payment-step__methods-container">
<div class="payment-step__methods-container__title-area">
<h2 class="payment-step__methods-container__title-area__title">Payment Method</h2>
<div class="payment-step__methods-container__title-area__options-area">
<span
class="payment-step__methods-container__title-area__options-area__token"
@click="setAddStorjState"
:class="{ selected: isAddStorjState }"
>
STORJ Token
</span>
<span
class="payment-step__methods-container__title-area__options-area__card"
@click="setAddCardState"
:class="{ selected: isAddCardState }"
>
Card
</span>
</div>
</div>
<div class="payment-step__methods-container__blur" v-if="isLoading"/>
</div>
<AddCardState
v-if="isAddCardState"
@toggleIsLoading="toggleIsLoading"
/>
<AddStorjState
v-if="isAddStorjState"
@toggleIsLoading="toggleIsLoading"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import AddCardState from '@/components/onboardingTour/steps/paymentStates/AddCardState.vue';
import AddStorjState from '@/components/onboardingTour/steps/paymentStates/AddStorjState.vue';
import { AddingPaymentState } from '@/utils/constants/onboardingTourEnums';
@Component({
components: {
AddStorjState,
AddCardState,
},
})
export default class AddPaymentStep extends Vue {
public areaState: number = AddingPaymentState.ADD_CARD;
public isLoading: boolean = false;
/**
* Lifecycle hook after initial render.
* Sets area to needed state.
*/
public mounted(): void {
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isTransactionCompleted) {
this.setAddStorjState();
}
}
/**
* Indicates if area is in adding card state.
*/
public get isAddCardState(): boolean {
return this.areaState === AddingPaymentState.ADD_CARD;
}
/**
* Indicates if area is in adding tokens state.
*/
public get isAddStorjState(): boolean {
return this.areaState === AddingPaymentState.ADD_STORJ;
}
/**
* Sets area to adding card state.
*/
public setAddCardState(): void {
this.areaState = AddingPaymentState.ADD_CARD;
}
/**
* Sets area to adding tokens state.
*/
public setAddStorjState(): void {
this.areaState = AddingPaymentState.ADD_STORJ;
}
/**
* Toggles area's loading state.
*/
public toggleIsLoading(): void {
this.isLoading = !this.isLoading;
}
}
</script>
<style scoped lang="scss">
h1,
h2,
p {
margin: 0;
}
.payment-step {
font-family: 'font_regular', sans-serif;
margin-top: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 0 140px;
&__title {
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
}
&__sub-title {
font-size: 16px;
line-height: 19px;
color: #354049;
margin-bottom: 35px;
text-align: center;
word-break: break-word;
}
&__methods-container {
padding: 30px 45px 10px 45px;
width: calc(100% - 90px);
border-radius: 8px 8px 0 0;
background-color: #fff;
position: relative;
&__title-area {
display: flex;
align-items: center;
justify-content: space-between;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 22px;
line-height: 27px;
color: #354049;
}
&__options-area {
display: flex;
align-items: flex-start;
justify-content: space-between;
min-height: 21px;
&__token,
&__card {
font-size: 14px;
line-height: 18px;
color: #a9b5c1;
text-align: center;
cursor: pointer;
}
&__token {
min-width: 110px;
}
&__card {
margin-left: 10px;
min-width: 65px;
}
}
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
border-radius: 8px;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
}
.selected {
color: #2582ff;
border-bottom: 3px solid #2582ff;
}
@media screen and (max-width: 1550px) {
.payment-step {
padding: 0 70px;
}
}
@media screen and (max-width: 800px) {
.payment-step {
padding: 0 25px;
}
}
</style>

View File

@ -1,7 +1,7 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
<template src="./OverviewArea.html"></template> <template src="./overviewStep.html"></template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
@ -12,8 +12,6 @@ import FirstStepIcon from '@/../static/images/common/one.svg';
import ThirdStepIcon from '@/../static/images/common/three.svg'; import ThirdStepIcon from '@/../static/images/common/three.svg';
import SecondStepIcon from '@/../static/images/common/two.svg'; import SecondStepIcon from '@/../static/images/common/two.svg';
import { RouteConfig } from '@/router';
@Component({ @Component({
components: { components: {
VButton, VButton,
@ -22,9 +20,15 @@ import { RouteConfig } from '@/router';
SecondStepIcon, SecondStepIcon,
}, },
}) })
export default class OverviewArea extends Vue { export default class OverviewStep extends Vue {
public readonly billingPath: string = RouteConfig.Account.with(RouteConfig.Billing).path; /**
* Holds button click logic.
* Sets tour state to adding payment method state.
*/
public onClick(): void {
this.$emit('setAddPaymentState');
}
} }
</script> </script>
<style scoped lang="scss" src="./OverviewArea.scss"></style> <style scoped lang="scss" src="./overviewStep.scss"></style>

View File

@ -0,0 +1,43 @@
<!--Copyright (C) 2020 Storj Labs, Inc.-->
<!--See LICENSE for copying information.-->
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<p class="overview-area__sub-title">
Youre just a few steps away from uploading your first object to the 100% secure, decentralized cloud. After
adding payment, youll create a project, API key, get set up with Storj, and start uploading objects.
</p>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__step">
<FirstStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/project.jpg" alt="project image">
<h2 class="overview-area__steps-area__step__title">Name Your Project</h2>
<span class="overview-area__steps-area__step__subtitle">
Projects are where buckets are created for storing data.
</span>
</div>
<div class="overview-area__steps-area__step second-step">
<SecondStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/api-key.jpg" alt="api keys image">
<h2 class="overview-area__steps-area__step__title">Create an API Key</h2>
<span class="overview-area__steps-area__step__subtitle">
Generate access to your project to upload data.
</span>
</div>
<div class="overview-area__steps-area__step">
<ThirdStepIcon class="overview-area__steps-area__step__icon"/>
<img class="overview-step-image" src="@/../static/images/onboardingTour/uplink.jpg" alt="uplink image">
<h2 class="overview-area__steps-area__step__title">Upload Data</h2>
<span class="overview-area__steps-area__step__subtitle">
Store your data on the secure, decentralized cloud.
</span>
</div>
</div>
<VButton
class="get-started-button"
label="Get Started"
width="251px"
height="56px"
:on-press="onClick"
/>
</div>

View File

@ -0,0 +1,89 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
p,
h1,
h2 {
margin: 0;
}
.overview-area {
width: auto;
padding: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: 'font_regular', sans-serif;
background-color: #fff;
border-radius: 6px;
margin-top: 25px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 25px;
}
&__sub-title {
font-size: 16px;
line-height: 26px;
color: #354049;
margin-bottom: 60px;
text-align: center;
max-width: 815px;
}
&__steps-area {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 50px;
&__step {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
position: relative;
max-width: 190px;
width: 190px;
&__icon {
position: absolute;
top: -15px;
left: 80px;
}
&__title {
font-size: 16px;
line-height: 26px;
text-align: center;
color: #354049;
margin: 15px 0 5px 0;
}
&__subtitle {
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 17px;
text-align: center;
color: #61666b;
word-break: break-word;
}
}
}
}
.second-step {
margin: 0 50px;
}
@media screen and (max-width: 800px) {
.second-step {
margin: 0 10px;
}
}

View File

@ -0,0 +1,200 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-card-state">
<div class="add-card-state__input-container">
<StripeCardInput
ref="stripeCardInput"
:on-stripe-response-callback="addCard"
/>
<div class="add-card-state__input-container__blur" v-if="isLoading"/>
</div>
<div class="add-card-state__security-info">
<LockImage/>
<span class="add-card-state__security-info__text">
Your card is secured by 128-bit SSL and AES-256 encryption. Your information is secure.
</span>
</div>
<div class="add-card-state__button" :class="{ loading: isLoading }" @click="onConfirmAddStripe">
<img
v-if="isLoading"
class="add-card-state__button__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
<span class="add-card-state__button__label">{{ isLoading ? 'Adding' : 'Add Payment' }}</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import StripeCardInput from '@/components/account/billing/paymentMethods/StripeCardInput.vue';
import LockImage from '@/../static/images/account/billing/lock.svg';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
const {
ADD_CREDIT_CARD,
GET_CREDIT_CARDS,
GET_BILLING_HISTORY,
GET_BALANCE,
} = PAYMENTS_ACTIONS;
interface StripeForm {
onSubmit(): Promise<void>;
}
@Component({
components: {
StripeCardInput,
LockImage,
},
})
export default class AddCardState extends Vue {
public isLoading: boolean = false;
public $refs!: {
stripeCardInput: StripeCardInput & StripeForm;
};
/**
* Provides card information to Stripe.
*/
public async onConfirmAddStripe(): Promise<void> {
await this.$refs.stripeCardInput.onSubmit();
this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, {
project_id: this.$store.getters.selectedProject.id,
});
}
/**
* Adds card after Stripe confirmation.
*
* @param token from Stripe
*/
public async addCard(token: string) {
if (this.isLoading) return;
this.isLoading = true;
this.$emit('toggleIsLoading');
try {
await this.$store.dispatch(ADD_CREDIT_CARD, token);
} catch (error) {
await this.$notify.error(error.message);
this.setDefaultState();
return;
}
await this.$notify.success('Card successfully added');
try {
await this.$store.dispatch(GET_CREDIT_CARDS);
} catch (error) {
await this.$notify.error(error.message);
}
this.setDefaultState();
// TODO: rework after adding next step
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
/**
* Sets area to default state.
*/
private setDefaultState(): void {
this.isLoading = false;
this.$emit('toggleIsLoading');
}
}
</script>
<style scoped lang="scss">
.add-card-state {
font-family: 'font_regular', sans-serif;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
&__input-container {
position: relative;
width: calc(100% - 90px);
padding: 25px 45px 35px 45px;
background-color: #fff;
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
&__security-info {
width: calc(100% - 70px);
padding: 15px 35px;
background-color: #cef0e3;
border-radius: 0 0 8px 8px;
margin-bottom: 45px;
display: flex;
align-items: center;
justify-content: center;
&__text {
margin-left: 5px;
font-size: 15px;
line-height: 18px;
color: #1a9666;
}
}
&__button {
display: flex;
justify-content: center;
align-items: center;
width: 156px;
height: 48px;
cursor: pointer;
border-radius: 6px;
background-color: #2683ff;
&__loading-image {
margin-right: 5px;
width: 18px;
height: 18px;
}
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
line-height: 23px;
color: #fff;
word-break: keep-all;
white-space: nowrap;
}
&:hover {
background-color: #0059d0;
}
}
}
.loading {
opacity: 0.6;
pointer-events: none;
}
</style>

View File

@ -0,0 +1,221 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-storj-state">
<div class="add-storj-state__container">
<p class="add-storj-state__container__bonus-info">
Deposit STORJ Token to your account and receive a 10% bonus, or $10 for every $100.
</p>
<div class="add-storj-state__container__deposit-area">
<p class="add-storj-state__container__deposit-area__info">
<b>Please Note:</b> Your first deposit of $50 or more in STORJ Token is applied to your account
after Coin Payments verifies payment<br/><br/>5GB are your starting project limits. Increased
amounts are available
<a
class="add-storj-state__container__deposit-area__info__request-link"
href="https://support.tardigrade.io/hc/en-us/requests/new?ticket_form_id=360000683212"
target="_blank"
>
per request.
</a>
</p>
<PayingStep
v-if="isDefaultState"
@toggleIsLoading="toggleIsLoading"
@setVerifyingState="setVerifyingState"
/>
<VerifyingStep
v-if="isVerifyingState"
@setDefaultState="setDefaultState"
/>
<VerifiedStep v-if="isVerifiedState"/>
</div>
<div class="add-storj-state__container__blur" v-if="isLoading"/>
</div>
<VButton
width="222px"
height="48px"
label="Name Your Project"
:on-press="createProject"
:is-disabled="isButtonDisabled"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import PayingStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/PayingStep.vue';
import VerifiedStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/VerifiedStep.vue';
import VerifyingStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/VerifyingStep.vue';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AddingStorjState } from '@/utils/constants/onboardingTourEnums';
@Component({
components: {
VerifiedStep,
VerifyingStep,
PayingStep,
VButton,
},
})
export default class AddStorjState extends Vue {
public isLoading: boolean = false;
public addingTokenState: number = AddingStorjState.DEFAULT;
/**
* Lifecycle hook after initial render.
* Sets area to needed state.
*/
public beforeMount(): void {
switch (true) {
case this.$store.getters.isTransactionCompleted:
this.setVerifiedState();
return;
case this.$store.getters.isTransactionProcessing:
this.setVerifyingState();
return;
default:
this.setDefaultState();
}
}
/**
* Indicates if area is in default state.
*/
public get isDefaultState(): boolean {
return this.addingTokenState === AddingStorjState.DEFAULT;
}
/**
* Indicates if area is in verifying state.
*/
public get isVerifyingState(): boolean {
return this.addingTokenState === AddingStorjState.VERIFYING;
}
/**
* Indicates if area is in verified state.
*/
public get isVerifiedState(): boolean {
return this.addingTokenState === AddingStorjState.VERIFIED;
}
/**
* Indicates if button is disabled.
*/
public get isButtonDisabled(): boolean {
return !this.$store.getters.canUserCreateFirstProject;
}
/**
* Sets area to default state.
*/
public setDefaultState(): void {
this.addingTokenState = AddingStorjState.DEFAULT;
}
/**
* Sets area to verifying state.
*/
public setVerifyingState(): void {
this.addingTokenState = AddingStorjState.VERIFYING;
}
/**
* Sets area to verified state.
*/
public setVerifiedState(): void {
this.addingTokenState = AddingStorjState.VERIFIED;
}
/**
* Toggles area's loading state.
*/
public toggleIsLoading(): void {
this.isLoading = !this.isLoading;
this.$emit('toggleIsLoading');
}
/**
* Starts creating project process.
*/
public createProject(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
}
</script>
<style scoped lang="scss">
p,
h2 {
margin: 0;
}
.add-storj-state {
font-family: 'font_regular', sans-serif;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
&__container {
width: calc(100% - 90px);
padding: 20px 45px 45px 45px;
background-color: #fff;
border-radius: 0 0 8px 8px;
margin-bottom: 35px;
position: relative;
&__bonus-info {
padding: 20px 30px;
width: calc(100% - 60px);
background-color: #edf4fe;
border-radius: 6px;
font-size: 14px;
line-height: 20px;
color: #7b8eab;
}
&__deposit-area {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-top: 30px;
&__info {
background-color: rgba(245, 246, 250, 0.65);
padding: 35px;
border-radius: 6px;
font-size: 12px;
line-height: 17px;
color: #7b8eab;
width: calc(40% - 70px);
margin-right: 40px;
&__request-link {
text-decoration: underline;
}
}
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
}
</style>

View File

@ -0,0 +1,168 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="paying-step">
<div class="paying-step__title-area">
<img src="@/../static/images/onboardingTour/coins.png" alt="coins image">
<h2 class="paying-step__title-area__title">
Select Your Deposit Amount
</h2>
<img
v-if="isLoading"
class="loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
</div>
<TokenDepositSelection
class="paying-step__form"
@onChangeTokenValue="onChangeTokenValue"
/>
<VButton
width="100%"
height="48px"
label="Continue to Coin Payments"
:on-press="onConfirmAddSTORJ"
:is-blue-white="true"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import TokenDepositSelection from '@/components/account/billing/paymentMethods/TokenDepositSelection.vue';
import VButton from '@/components/common/VButton.vue';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
const {
MAKE_TOKEN_DEPOSIT,
GET_BILLING_HISTORY,
} = PAYMENTS_ACTIONS;
@Component({
components: {
VButton,
TokenDepositSelection,
},
})
export default class PayingStep extends Vue {
private readonly DEFAULT_TOKEN_DEPOSIT_VALUE = 50; // in dollars.
private readonly MAX_TOKEN_AMOUNT = 1000000; // in dollars.
private tokenDepositValue: number = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
public isLoading: boolean = false;
/**
* Event for changing token deposit value.
*/
public onChangeTokenValue(value: number): void {
this.tokenDepositValue = value;
}
/**
* onConfirmAddSTORJ checks if amount is valid and if so process token.
* payment and return state to default
*/
public async onConfirmAddSTORJ(): Promise<void> {
if (this.isLoading) return;
this.isLoading = true;
this.$emit('toggleIsLoading');
if (this.tokenDepositValue < this.DEFAULT_TOKEN_DEPOSIT_VALUE || this.tokenDepositValue >= this.MAX_TOKEN_AMOUNT) {
await this.$notify.error('First deposit amount must be more than $50 and less than $1000000');
this.setDefaultState();
return;
}
try {
const tokenResponse = await this.$store.dispatch(MAKE_TOKEN_DEPOSIT, this.tokenDepositValue * 100);
await this.$notify.success(`Successfully created new deposit transaction! \nAddress:${tokenResponse.address} \nAmount:${tokenResponse.amount}`);
const depositWindow = window.open(tokenResponse.link, '_blank');
if (depositWindow) {
depositWindow.focus();
}
} catch (error) {
await this.$notify.error(error.message);
this.setDefaultState();
}
this.$segment.track(SegmentEvent.PAYMENT_METHOD_ADDED, {
project_id: this.$store.getters.selectedProject.id,
});
this.tokenDepositValue = this.DEFAULT_TOKEN_DEPOSIT_VALUE;
try {
await this.$store.dispatch(GET_BILLING_HISTORY);
} catch (error) {
await this.$notify.error(error.message);
}
this.setDefaultState();
this.$emit('setVerifyingState');
}
/**
* Sets area to default state.
*/
private setDefaultState(): void {
this.isLoading = false;
this.$emit('toggleIsLoading');
}
}
</script>
<style scoped lang="scss">
p,
h2 {
margin: 0;
}
.paying-step {
display: flex;
flex-direction: column;
width: 60%;
&__title-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 25px;
&__title {
font-size: 20px;
line-height: 26px;
color: #384b65;
margin-left: 15px;
}
}
&__form {
width: 100%;
margin-bottom: 20px;
/deep/ .selected-container,
/deep/ .options-container,
/deep/ .payment-selection-blur {
width: 100%;
}
/deep/ .custom-input {
width: calc(100% - 61px);
}
}
}
.loading-image {
width: 18px;
height: 18px;
margin-left: 10px;
}
</style>

View File

@ -0,0 +1,50 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="verified-step">
<VerifiedImage/>
<h2 class="verified-step__title">Payment Verified</h2>
<span class="verified-step__sub-title">
Your payment has been successfully verified. Click the button below to name your project and start uploading data.
</span>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VerifiedImage from '@/../static/images/onboardingTour/verified.svg';
@Component({
components: {
VerifiedImage,
},
})
export default class VerifiedStep extends Vue {}
</script>
<style scoped lang="scss">
.verified-step {
width: 60%;
padding-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
&__title {
font-size: 18px;
line-height: 22px;
color: #354049;
}
&__sub-title {
font-size: 12px;
line-height: 17px;
text-align: center;
color: #354049;
}
}
</style>

View File

@ -0,0 +1,30 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template src="./verifyingStep.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BackImage from '@/../static/images/account/billing/back.svg';
import VerifyingImage from '@/../static/images/onboardingTour/verifying.svg';
@Component({
components: {
VerifyingImage,
BackImage,
},
})
export default class VerifyingStep extends Vue {
/**
* Holds logic for button click.
* Sets area to default state.
*/
public onBackClick(): void {
this.$emit('setDefaultState');
}
}
</script>
<style scoped lang="scss" src="./verifyingStep.scss"></style>

View File

@ -0,0 +1,22 @@
<!--Copyright (C) 2020 Storj Labs, Inc.-->
<!--See LICENSE for copying information.-->
<div class="verifying-step">
<VerifyingImage/>
<h2 class="verifying-step__title">Verifying Payment</h2>
<span class="verifying-step__sub-title">
Refresh this page in 10-15 minutes to continue after your purchase is verified. In the meantime, see how
easy it is to get started visiting documentation page, or join the community to get involved.
</span>
<div class="verifying-step__buttons-area">
<a class="verifying-step__buttons-area__how-to-button" href="https://documentation.storj.io" target="_blank">
Documentation
</a>
<a class="verifying-step__buttons-area__community-button" href="https://storj.io/community/" target="_blank">
Community
</a>
</div>
<div class="verifying-step__back-button" @click="onBackClick">
<BackImage/>
</div>
</div>

View File

@ -0,0 +1,89 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
.verifying-step {
width: 60%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
font-family: 'font_regular', sans-serif;
position: relative;
&__title {
font-size: 18px;
line-height: 22px;
color: #354049;
}
&__sub-title {
font-size: 12px;
line-height: 17px;
text-align: center;
color: #354049;
}
&__buttons-area {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-top: 25px;
&__how-to-button,
&__community-button {
display: flex;
align-items: center;
justify-content: center;
width: 50%;
height: 44px;
font-size: 14px;
line-height: 20px;
border-radius: 8px;
border: 2px solid #2683ff;
}
&__how-to-button {
color: #2683ff;
background-color: #fff;
margin-right: 10px;
&:hover {
color: #fff;
background-color: #2683ff;
}
}
&__community-button {
color: #fff;
background-color: #2683ff;
&:hover {
background-color: #0059d0;
border: 2px solid #0059d0;
}
}
}
&__back-button {
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
width: 30px;
height: 30px;
border-radius: 15px;
cursor: pointer;
&:hover {
background-color: #2683ff;
.back-button-svg-path {
fill: #fff;
}
}
}
}

View File

@ -1,35 +0,0 @@
<!--Copyright (C) 2019 Storj Labs, Inc.-->
<!--See LICENSE for copying information.-->
<div class="overview-area">
<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"/>
<div class="overview-area-divider"></div>
<SecondStepIcon class="overview-area__steps-area__numbers__icon"/>
<div class="overview-area-divider"></div>
<ThirdStepIcon class="overview-area__steps-area__numbers__icon"/>
</div>
<div class="overview-area__steps-area__items">
<div class="overview-area__steps-area__items__add-payment-info">
<h2 class="overview-area__steps-area__items__add-payment-info__title">Add Payment Info</h2>
<img class="overview-area-image" src="@/../static/images/overview/payment.jpg" alt="payment image">
</div>
<div class="overview-area__steps-area__items__create-project">
<h2 class="overview-area__steps-area__items__create-project__title">Create a Project</h2>
<img class="overview-area-image" src="@/../static/images/overview/project.jpg" alt="project image">
</div>
<div class="overview-area__steps-area__items__create-api-key">
<h2 class="overview-area__steps-area__items__create-api-key__title">Create an API Key</h2>
<img class="overview-area-image" src="@/../static/images/overview/apikey.jpg" alt="api key image">
</div>
</div>
</div>
</div>

View File

@ -1,152 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
p,
h1 {
margin: 0;
}
.overview-area {
width: auto;
height: calc(100% - 150px);
padding: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-family: 'font_regular', sans-serif;
&__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;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #1b2533;
margin-bottom: 20px;
}
&__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 {
padding: 55px;
width: auto;
background-color: #fff;
border-radius: 12px;
&__numbers {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 125px;
margin-bottom: 15px;
&__icon {
min-width: 30px;
min-height: 30px;
}
}
&__items {
display: flex;
justify-content: space-between;
align-items: center;
&__add-payment-info,
&__create-project,
&__create-api-key {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 16px;
text-align: center;
color: #354049;
}
}
&__add-payment-info,
&__create-project {
margin-right: 20px;
}
}
}
}
.overview-area-divider {
width: calc(46% + 2px);
border: 1px solid #cfd5da;
}
@media screen and (max-width: 1450px) {
.overview-area {
&__welcome-container {
width: 626px;
padding: 73px 55px;
}
&__steps-area {
&__numbers {
padding: 0 80px;
}
&__items {
&__add-payment-info,
&__create-project,
&__create-api-key {
&__title {
font-family: 'font_bold', sans-serif;
font-size: 19px;
line-height: 16px;
text-align: center;
color: #354049;
}
}
}
}
}
.overview-area-image {
max-width: 210px;
max-height: 150px;
}
}

View File

@ -63,6 +63,7 @@ import VButton from '@/components/common/VButton.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg'; import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import SuccessIcon from '@/../static/images/project/success.svg'; import SuccessIcon from '@/../static/images/project/success.svg';
import { RouteConfig } from '@/router';
import { BUCKET_ACTIONS } from '@/store/modules/buckets'; import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments'; import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { PROJECTS_ACTIONS } from '@/store/modules/projects';
@ -174,6 +175,9 @@ export default class NewProjectPopup extends Vue {
this.checkIfUsersFirstProject(); this.checkIfUsersFirstProject();
this.isLoading = false; this.isLoading = false;
// TODO: remove after adding second step of onboarding tour
await this.$router.push(RouteConfig.ProjectDashboard.path);
} }
/** /**

View File

@ -19,6 +19,7 @@ import BucketArea from '@/components/project/buckets/BucketArea.vue';
import ProjectDetails from '@/components/project/ProjectDetails.vue'; import ProjectDetails from '@/components/project/ProjectDetails.vue';
import ProjectUsage from '@/components/project/usage/ProjectUsage.vue'; import ProjectUsage from '@/components/project/usage/ProjectUsage.vue';
import { RouteConfig } from '@/router';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames'; import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({ @Component({
@ -34,6 +35,12 @@ export default class ProjectDashboard extends Vue {
* Segment tracking is processed. * Segment tracking is processed.
*/ */
public mounted(): void { public mounted(): void {
if (!this.$store.getters.selectedProject.id) {
this.$router.push(RouteConfig.OnboardingTour.path);
return;
}
this.$segment.track(SegmentEvent.PROJECT_VIEWED, { this.$segment.track(SegmentEvent.PROJECT_VIEWED, {
project_id: this.$store.getters.selectedProject.id, project_id: this.$store.getters.selectedProject.id,
}); });

View File

@ -68,7 +68,15 @@ export default class BucketArea extends Vue {
* Lifecycle hook after initial render where buckets list is fetched. * Lifecycle hook after initial render where buckets list is fetched.
*/ */
public async mounted(): Promise<void> { public async mounted(): Promise<void> {
await this.$store.dispatch(FETCH, 1); if (!this.$store.getters.selectedProject.id) {
return;
}
try {
await this.$store.dispatch(FETCH, 1);
} catch (error) {
await this.$notify.error(error.message);
}
} }
/** /**

View File

@ -48,6 +48,10 @@ export default class ProjectUsage extends Vue {
* Fetches project limits. * Fetches project limits.
*/ */
public async mounted(): Promise<void> { public async mounted(): Promise<void> {
if (!this.$store.getters.selectedProject.id) {
return;
}
try { try {
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id); await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.$store.getters.selectedProject.id);
} catch (error) { } catch (error) {

View File

@ -206,7 +206,7 @@ export default class HeaderArea extends Vue {
const projects = await this.$store.dispatch(PROJECTS_ACTIONS.FETCH); const projects = await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
if (!projects.length) { if (!projects.length) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY); await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
await this.$router.push(RouteConfig.Account.with(RouteConfig.Billing).path); await this.$router.push(RouteConfig.OnboardingTour.path);
return; return;
} }

View File

@ -10,7 +10,7 @@ import BillingHistory from '@/components/account/billing/billingHistory/BillingH
import SettingsArea from '@/components/account/SettingsArea.vue'; import SettingsArea from '@/components/account/SettingsArea.vue';
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue'; import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
import Page404 from '@/components/errors/Page404.vue'; import Page404 from '@/components/errors/Page404.vue';
import OverviewArea from '@/components/overview/OverviewArea.vue'; import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import ProjectDashboard from '@/components/project/ProjectDashboard.vue'; import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue'; import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
@ -36,7 +36,7 @@ export abstract class RouteConfig {
public static ProjectDashboard = new NavigationLink('/project-dashboard', 'Dashboard'); public static ProjectDashboard = new NavigationLink('/project-dashboard', 'Dashboard');
public static Team = new NavigationLink('/project-members', 'Team'); public static Team = new NavigationLink('/project-members', 'Team');
public static ApiKeys = new NavigationLink('/api-keys', 'API Keys'); public static ApiKeys = new NavigationLink('/api-keys', 'API Keys');
public static Overview = new NavigationLink('/overview', 'Initial Overview'); public static OnboardingTour = new NavigationLink('/onboarding-tour', 'Onboarding Tour');
// child paths // child paths
public static Settings = new NavigationLink('settings', 'Settings'); public static Settings = new NavigationLink('settings', 'Settings');
@ -132,9 +132,9 @@ export const router = new Router({
component: ApiKeysArea, component: ApiKeysArea,
}, },
{ {
path: RouteConfig.Overview.path, path: RouteConfig.OnboardingTour.path,
name: RouteConfig.Overview.name, name: RouteConfig.OnboardingTour.name,
component: OverviewArea, component: OnboardingTourArea,
}, },
], ],
}, },

View File

@ -267,6 +267,20 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState>
&& billingItem.status === BillingHistoryItemStatus.Completed; && billingItem.status === BillingHistoryItemStatus.Completed;
}) && state.balance > 0) || state.creditCards.length > 0; }) && state.balance > 0) || state.creditCards.length > 0;
}, },
isTransactionProcessing: (state: PaymentsState): boolean => {
return state.billingHistory.some((billingItem: BillingHistoryItem) => {
return billingItem.amount >= 50 && billingItem.type === BillingHistoryItemType.Transaction
&& (billingItem.status === BillingHistoryItemStatus.Pending
|| billingItem.status === BillingHistoryItemStatus.Paid
|| billingItem.status === BillingHistoryItemStatus.Completed);
}) && state.balance === 0;
},
isTransactionCompleted: (state: PaymentsState): boolean => {
return (state.billingHistory.some((billingItem: BillingHistoryItem) => {
return billingItem.amount >= 50 && billingItem.type === BillingHistoryItemType.Transaction
&& billingItem.status === BillingHistoryItemStatus.Completed;
}) && state.balance > 0);
},
isInvoiceForPreviousRollup: (state: PaymentsState): boolean => { isInvoiceForPreviousRollup: (state: PaymentsState): boolean => {
const now = new Date(); const now = new Date();

View File

@ -152,6 +152,16 @@ export enum BillingHistoryItemStatus {
* Status showed if transaction successfully completed. * Status showed if transaction successfully completed.
*/ */
Completed = 'completed', Completed = 'completed',
/**
* Status showed if transaction successfully paid.
*/
Paid = 'paid',
/**
* Status showed if transaction is pending.
*/
Pending = 'pending',
} }
/** /**

View File

@ -0,0 +1,21 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
export enum TourState {
DEFAULT = 1,
ADDING_PAYMENT,
PROJECT,
API_KEY,
UPLOAD,
}
export enum AddingPaymentState {
ADD_CARD = 1,
ADD_STORJ,
}
export enum AddingStorjState {
DEFAULT = 1,
VERIFYING,
VERIFIED,
}

View File

@ -6,7 +6,7 @@
<div v-if="isLoading" class="loading-overlay active"> <div v-if="isLoading" class="loading-overlay active">
<img class="loading-image" src="@/../static/images/register/Loading.gif" alt="Company logo loading gif"> <img class="loading-image" src="@/../static/images/register/Loading.gif" alt="Company logo loading gif">
</div> </div>
<div v-if="!isLoading" class="dashboard-container__wrap"> <div v-else class="dashboard-container__wrap">
<NavigationArea class="regular-navigation"/> <NavigationArea class="regular-navigation"/>
<div class="dashboard-container__wrap__column"> <div class="dashboard-container__wrap__column">
<DashboardHeader/> <DashboardHeader/>
@ -45,7 +45,6 @@ import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments'; import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects'; import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { USER_ACTIONS } from '@/store/modules/users'; import { USER_ACTIONS } from '@/store/modules/users';
import { CreditCard } from '@/types/payments';
import { Project } from '@/types/projects'; import { Project } from '@/types/projects';
import { Size } from '@/utils/bytesSize'; import { Size } from '@/utils/bytesSize';
import { import {
@ -97,18 +96,40 @@ export default class DashboardArea extends Vue {
return; return;
} }
let balance: number = 0;
let creditCards: CreditCard[] = [];
try { try {
await this.$store.dispatch(SETUP_ACCOUNT); await this.$store.dispatch(SETUP_ACCOUNT);
balance = await this.$store.dispatch(GET_BALANCE); } catch (error) {
creditCards = await this.$store.dispatch(GET_CREDIT_CARDS); await this.$notify.error(`Unable to setup account. ${error.message}`);
}
try {
await this.$store.dispatch(GET_BALANCE);
} catch (error) {
await this.$notify.error(`Unable to get account balance. ${error.message}`);
}
try {
await this.$store.dispatch(GET_CREDIT_CARDS);
} catch (error) {
await this.$notify.error(`Unable to get credit cards. ${error.message}`);
}
try {
await this.$store.dispatch(GET_BILLING_HISTORY); await this.$store.dispatch(GET_BILLING_HISTORY);
} catch (error) {
await this.$notify.error(`Unable to get account billing history. ${error.message}`);
}
try {
await this.$store.dispatch(GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP); await this.$store.dispatch(GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP);
} catch (error) {
await this.$notify.error(`Unable to get usage and charges for previous billing period. ${error.message}`);
}
try {
await this.$store.dispatch(GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP); await this.$store.dispatch(GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
} catch (error) { } catch (error) {
await this.$notify.error(error.message); await this.$notify.error(`Unable to get usage and charges for current billing period. ${error.message}`);
} }
let projects: Project[] = []; let projects: Project[] = [];
@ -121,27 +142,13 @@ export default class DashboardArea extends Vue {
return; return;
} }
if (!projects.length && !creditCards.length && balance === 0) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
try {
await this.$router.push(RouteConfig.Overview.path);
} catch (error) {
return;
}
return;
}
if (!projects.length) { if (!projects.length) {
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY); await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
if (!this.isRouteAccessibleWithoutProject()) { try {
try { await this.$router.push(RouteConfig.OnboardingTour.path);
await this.$router.push(RouteConfig.Account.with(RouteConfig.Billing).path); } catch (error) {
} catch (error) { return;
return;
}
} }
return; return;
@ -226,19 +233,6 @@ export default class DashboardArea extends Vue {
public get isLoading(): boolean { public get isLoading(): boolean {
return this.$store.state.appStateModule.appState.fetchState === AppState.LOADING; return this.$store.state.appStateModule.appState.fetchState === AppState.LOADING;
} }
/**
* This method checks if current route is available when user has no created projects.
*/
private isRouteAccessibleWithoutProject(): boolean {
const availableRoutes = [
RouteConfig.Account.with(RouteConfig.Billing).path,
RouteConfig.Account.with(RouteConfig.Settings).path,
RouteConfig.Overview.path,
];
return availableRoutes.includes(this.$router.currentRoute.path.toLowerCase());
}
} }
</script> </script>
@ -288,16 +282,6 @@ export default class DashboardArea extends Vue {
} }
} }
@media screen and (max-width: 720px) {
.dashboard-container {
&__main-area {
left: 60px;
}
}
}
.loading-overlay { .loading-overlay {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -77,7 +77,7 @@ export default class Login extends Vue {
/** /**
* Performs login action. * Performs login action.
* Than change location to project overview. * Then changes location to billing page.
*/ */
public async onLogin(): Promise<void> { public async onLogin(): Promise<void> {
if (this.isLoading) { if (this.isLoading) {

View File

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="back-button-svg-path" fill-rule="evenodd" clip-rule="evenodd" d="M7.22572 0.300601C7.62652 0.701402 7.62652 1.35123 7.22572 1.75203L3.50406 5.47368H11.9737C12.5405 5.47368 13 5.93318 13 6.5C13 7.06682 12.5405 7.52632 11.9737 7.52632H3.50406L7.22572 11.248C7.62652 11.6488 7.62652 12.2986 7.22572 12.6994C6.82491 13.1002 6.17509 13.1002 5.77429 12.6994L0.300601 7.22571C-0.1002 6.82491 -0.1002 6.17509 0.300601 5.77428L5.77429 0.300601C6.17509 -0.1002 6.82491 -0.1002 7.22572 0.300601Z" fill="#384B65"/>
</svg>

After

Width:  |  Height:  |  Size: 623 B

View File

@ -0,0 +1,3 @@
<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -0,0 +1,3 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 34C26.3888 34 34 26.3888 34 17C34 7.61116 26.3888 0 17 0C7.61116 0 0 7.61116 0 17C0 26.3888 7.61116 34 17 34ZM24.4639 12.1445C23.7686 11.4507 22.6412 11.4507 21.9459 12.1445L14.8113 19.2624L11.8735 16.3315C11.1782 15.6378 10.0508 15.6378 9.35544 16.3315C8.66009 17.0252 8.66009 18.15 9.35544 18.8437L13.5522 23.0308C14.2476 23.7245 15.375 23.7245 16.0703 23.0308L24.4639 14.6567C25.1593 13.963 25.1593 12.8382 24.4639 12.1445Z" fill="#00BD8F"/>
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,3 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17 34C26.3889 34 34 26.3889 34 17C34 7.61115 26.3889 0 17 0C7.61115 0 0 7.61115 0 17C0 26.3889 7.61115 34 17 34ZM8 19C9.10455 19 10 18.1046 10 17C10 15.8954 9.10455 15 8 15C6.89545 15 6 15.8954 6 17C6 18.1046 6.89545 19 8 19ZM17 19C18.1046 19 19 18.1046 19 17C19 15.8954 18.1046 15 17 15C15.8954 15 15 15.8954 15 17C15 18.1046 15.8954 19 17 19ZM28 17C28 18.1046 27.1046 19 26 19C24.8954 19 24 18.1046 24 17C24 15.8954 24.8954 15 26 15C27.1046 15 28 15.8954 28 17Z" fill="#C7CDD2"/>
</svg>

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -66,12 +66,12 @@ describe('AddStorjForm', () => {
wrapper.vm.$data.tokenDepositValue = 30; wrapper.vm.$data.tokenDepositValue = 30;
await wrapper.vm.onConfirmAddSTORJ(); 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[0].message).toMatch('First deposit amount must be more than $50 and less than $1000000');
wrapper.vm.$data.tokenDepositValue = 1000000; wrapper.vm.$data.tokenDepositValue = 1000000;
await wrapper.vm.onConfirmAddSTORJ(); await wrapper.vm.onConfirmAddSTORJ();
expect((store.state as any).notificationsModule.notificationQueue[1].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 () => { it('user is able to add less than 50$ after coupon is applied', async () => {

View File

@ -42,17 +42,6 @@ describe('NewProjectArea', () => {
expect(wrapper.findAll('.new-project-button-container').length).toBe(0); // user is unable to create project. 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 () => {
const wrapper = mount(NewProjectArea, {
store,
localVue,
});
await wrapper.find('.info').trigger('mouseenter');
expect(wrapper).toMatchSnapshot();
});
it('renders correctly without projects and with credit card', async () => { it('renders correctly without projects and with credit card', async () => {
const creditCard = new CreditCard('id', 1, 2000, 'test', '0000', true); const creditCard = new CreditCard('id', 1, 2000, 'test', '0000', true);

View File

@ -2,7 +2,6 @@
exports[`NewProjectArea renders correctly without projects and with credit card 1`] = ` exports[`NewProjectArea renders correctly without projects and with credit card 1`] = `
<div class="new-project-container"> <div class="new-project-container">
<!---->
<div id="newProjectButton" class="new-project-button-container"> <div id="newProjectButton" class="new-project-button-container">
<h1 class="new-project-button-container__label">+ Create Project</h1> <h1 class="new-project-button-container__label">+ Create Project</h1>
</div> </div>
@ -12,7 +11,6 @@ exports[`NewProjectArea renders correctly without projects and with credit card
exports[`NewProjectArea renders correctly without projects and with credit card 2`] = ` exports[`NewProjectArea renders correctly without projects and with credit card 2`] = `
<div class="new-project-container"> <div class="new-project-container">
<!---->
<div id="newProjectButton" class="new-project-button-container"> <div id="newProjectButton" class="new-project-button-container">
<h1 class="new-project-button-container__label">+ Create Project</h1> <h1 class="new-project-button-container__label">+ Create Project</h1>
</div> </div>
@ -61,30 +59,6 @@ exports[`NewProjectArea renders correctly without projects and with credit card
exports[`NewProjectArea renders correctly without projects and without payment methods 1`] = ` exports[`NewProjectArea renders correctly without projects and without payment methods 1`] = `
<div class="new-project-container"> <div class="new-project-container">
<div class="info">
<div class="new-project-button-mock">
<h1 class="new-project-button-mock__label">+ Create Project</h1>
</div>
<!---->
</div>
<!---->
<!---->
</div>
`;
exports[`NewProjectArea renders correctly without projects and without payment methods with info tooltip 1`] = `
<div class="new-project-container">
<div class="info">
<div class="new-project-button-mock">
<h1 class="new-project-button-mock__label">+ Create Project</h1>
</div>
<div class="info__message-box">
<div class="info__message-box__text">
<p class="info__message-box__text__regular-text">Please add a payment method</p>
<p class="info__message-box__text__bold-text"></p>
</div>
</div>
</div>
<!----> <!---->
<!----> <!---->
</div> </div>

View File

@ -3,7 +3,7 @@
exports[`NavigationArea snapshot not changed with project 1`] = ` exports[`NavigationArea snapshot not changed with project 1`] = `
<div class="navigation-area"> <div class="navigation-area">
<div class="navigation-area__wrap"> <div class="navigation-area__wrap">
<router-link-stub to="/account/billing" tag="a" event="click" class="navigation-area__logo"> <router-link-stub to="/project-dashboard" tag="a" event="click" class="navigation-area__logo">
<logoicon-stub class="navigation-area__logo__img"></logoicon-stub> <logoicon-stub class="navigation-area__logo__img"></logoicon-stub>
<logotexticon-stub class="navigation-area__logo__text"></logotexticon-stub> <logotexticon-stub class="navigation-area__logo__text"></logotexticon-stub>
</router-link-stub> </router-link-stub>
@ -40,7 +40,7 @@ exports[`NavigationArea snapshot not changed with project 1`] = `
<p class="navigation-area__resources-title__title">RESOURCES</p> <p class="navigation-area__resources-title__title">RESOURCES</p>
<!----> <!---->
<!----> <!---->
</div> <a href="https://documentation.tardigrade.io" target="_blank" class="navigation-area__item-container"> </div> <a href="https://documentation.storj.io" target="_blank" class="navigation-area__item-container">
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<docsicon-stub class="svg"></docsicon-stub> <docsicon-stub class="svg"></docsicon-stub>
<h1 class="navigation-area__item-container__link-container__title docs-title">Docs</h1> <h1 class="navigation-area__item-container__link-container__title docs-title">Docs</h1>
@ -76,7 +76,7 @@ exports[`NavigationArea snapshot not changed with project 1`] = `
exports[`NavigationArea snapshot not changed without project 1`] = ` exports[`NavigationArea snapshot not changed without project 1`] = `
<div class="navigation-area"> <div class="navigation-area">
<div class="navigation-area__wrap"> <div class="navigation-area__wrap">
<router-link-stub to="/account/billing" tag="a" event="click" class="navigation-area__logo"> <router-link-stub to="/onboarding-tour" tag="a" event="click" class="navigation-area__logo">
<logoicon-stub class="navigation-area__logo__img"></logoicon-stub> <logoicon-stub class="navigation-area__logo__img"></logoicon-stub>
<logotexticon-stub class="navigation-area__logo__text"></logotexticon-stub> <logotexticon-stub class="navigation-area__logo__text"></logotexticon-stub>
</router-link-stub> </router-link-stub>
@ -113,12 +113,12 @@ exports[`NavigationArea snapshot not changed without project 1`] = `
<p class="navigation-area__resources-title__title">RESOURCES</p> <p class="navigation-area__resources-title__title">RESOURCES</p>
<!----> <!---->
<!----> <!---->
</div> <a href="https://documentation.tardigrade.io" target="_blank" class="navigation-area__item-container"> </div> <a href="https://documentation.storj.io" target="_blank" class="navigation-area__item-container disabled">
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<docsicon-stub class="svg"></docsicon-stub> <docsicon-stub class="svg"></docsicon-stub>
<h1 class="navigation-area__item-container__link-container__title docs-title">Docs</h1> <h1 class="navigation-area__item-container__link-container__title docs-title">Docs</h1>
</div> </div>
</a> <a href="mailto:support@storj.io" target="_blank" class="navigation-area__item-container support-item"> </a> <a href="mailto:support@storj.io" target="_blank" class="navigation-area__item-container support-item disabled">
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<supporticon-stub class="svg"></supporticon-stub> <supporticon-stub class="svg"></supporticon-stub>
<h1 class="navigation-area__item-container__link-container__title">Support</h1> <h1 class="navigation-area__item-container__link-container__title">Support</h1>
@ -130,12 +130,12 @@ exports[`NavigationArea snapshot not changed without project 1`] = `
<!----> <!---->
</div> </div>
<div class="navigation-area__account-area"> <div class="navigation-area__account-area">
<router-link-stub to="/account/settings" tag="a" event="click" class="navigation-area__item-container account-item"> <router-link-stub to="/account/settings" tag="a" event="click" class="navigation-area__item-container account-item disabled">
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<h1 class="navigation-area__item-container__link-container__title account-item">Settings</h1> <h1 class="navigation-area__item-container__link-container__title account-item">Settings</h1>
</div> </div>
</router-link-stub> </router-link-stub>
<router-link-stub to="/account/billing" tag="a" event="click" class="navigation-area__item-container account-item"> <router-link-stub to="/account/billing" tag="a" event="click" class="navigation-area__item-container account-item disabled">
<div class="navigation-area__item-container__link-container"> <div class="navigation-area__item-container__link-container">
<h1 class="navigation-area__item-container__link-container__title account-item">Billing</h1> <h1 class="navigation-area__item-container__link-container__title account-item">Billing</h1>
</div> </div>

View File

@ -1,9 +1,12 @@
// Copyright (C) 2019 Storj Labs, Inc. // Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
import Router from 'vue-router';
import Vuex from 'vuex'; import Vuex from 'vuex';
import NavigationArea from '@/components/navigation/NavigationArea.vue'; import NavigationArea from '@/components/navigation/NavigationArea.vue';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects'; import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
@ -19,6 +22,7 @@ const projectsModule = makeProjectsModule(api);
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
localVue.use(Router);
const store = new Vuex.Store({ modules: { projectsModule } }); const store = new Vuex.Store({ modules: { projectsModule } });
@ -29,10 +33,19 @@ const expectedLinks: NavigationLink[] = [
]; ];
describe('NavigationArea', () => { describe('NavigationArea', () => {
it('snapshot not changed without project', () => { it('snapshot not changed without project', (): void => {
const router = new Router({
mode: 'history',
routes: [{
path: '/',
name: RouteConfig.OnboardingTour.name,
component: OnboardingTourArea,
}],
});
const wrapper = shallowMount(NavigationArea, { const wrapper = shallowMount(NavigationArea, {
store, store,
localVue, localVue,
router,
}); });
const navigationElements = wrapper.findAll('.navigation-area__item-container'); const navigationElements = wrapper.findAll('.navigation-area__item-container');
@ -41,13 +54,22 @@ describe('NavigationArea', () => {
const accountButton = wrapper.findAll('.navigation-area__account-title__button'); const accountButton = wrapper.findAll('.navigation-area__account-title__button');
expect(navigationElements.length).toBe(7); expect(navigationElements.length).toBe(7);
expect(disabledElements.length).toBe(3); expect(disabledElements.length).toBe(7);
expect(resourcesButton.length).toBe(0); expect(resourcesButton.length).toBe(0);
expect(accountButton.length).toBe(0); expect(accountButton.length).toBe(0);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
const router = new Router({
mode: 'history',
routes: [{
path: '/',
name: RouteConfig.ProjectDashboard.name,
component: ProjectDashboard,
}],
});
it('snapshot not changed with project', async () => { it('snapshot not changed with project', async () => {
const projects = await store.dispatch('fetchProjects'); const projects = await store.dispatch('fetchProjects');
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, projects[0].id); store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, projects[0].id);
@ -55,6 +77,7 @@ describe('NavigationArea', () => {
const wrapper = shallowMount(NavigationArea, { const wrapper = shallowMount(NavigationArea, {
store, store,
localVue, localVue,
router,
}); });
const navigationElements = wrapper.findAll('.navigation-area__item-container'); const navigationElements = wrapper.findAll('.navigation-area__item-container');
@ -74,6 +97,7 @@ describe('NavigationArea', () => {
const wrapper = shallowMount(NavigationArea, { const wrapper = shallowMount(NavigationArea, {
store, store,
localVue, localVue,
router,
}); });
const navigationLinks = (wrapper.vm as any).navigation; const navigationLinks = (wrapper.vm as any).navigation;
@ -90,6 +114,7 @@ describe('NavigationArea', () => {
const wrapper = shallowMount(NavigationArea, { const wrapper = shallowMount(NavigationArea, {
store, store,
localVue, localVue,
router,
}); });
await wrapper.find('.navigation-area__resources-title').trigger('mouseenter'); await wrapper.find('.navigation-area__resources-title').trigger('mouseenter');

View File

@ -0,0 +1,14 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import ProgressBar from '@/components/onboardingTour/ProgressBar.vue';
import { mount } from '@vue/test-utils';
describe('ProgressBar.vue', () => {
it('renders correctly', (): void => {
const wrapper = mount(ProgressBar);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProgressBar.vue renders correctly 1`] = `
<div class="progress-bar-container">
<div class="progress-bar-container__progress-area">
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
<div class="progress-bar-container__progress-area__bar"></div>
<div class="progress-bar-container__progress-area__circle"><svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.61854 0.302314C8.02258 -0.100771 8.67764 -0.100771 9.08163 0.302314C9.48569 0.705397 9.48569 1.35893 9.08163 1.76202L4.20463 6.62768C3.8006 7.03077 3.14555 7.03077 2.74152 6.62768L0.303018 4.19485C-0.101006 3.79177 -0.101006 3.13823 0.303018 2.73515C0.707044 2.33206 1.3621 2.33206 1.76612 2.73515L3.47307 4.43813L7.61854 0.302314Z" fill="white"></path>
</svg></div>
</div>
<div class="progress-bar-container__titles-area"><span class="progress-bar-container__titles-area__title">Name Your Project</span> <span class="progress-bar-container__titles-area__title api-key-title">Create an API Key</span> <span class="progress-bar-container__titles-area__title">Upload Data</span></div>
</div>
`;

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`OnboardingTourArea.vue renders correctly 1`] = `
<div class="tour-area">
<progressbar-stub></progressbar-stub>
<overviewstep-stub></overviewstep-stub>
<!---->
</div>
`;

View File

@ -0,0 +1,34 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import { PaymentsHttpApi } from '@/api/payments';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { ProjectsApiMock } from '../mock/api/projects';
const localVue = createLocalVue();
localVue.use(Vuex);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const paymentsApi = new PaymentsHttpApi();
const paymentsModule = makePaymentsModule(paymentsApi);
const store = new Vuex.Store({ modules: { projectsModule, paymentsModule }});
describe('OnboardingTourArea.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(OnboardingTourArea, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,32 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import AddPaymentStep from '@/components/onboardingTour/steps/AddPaymentStep.vue';
import { PaymentsHttpApi } from '@/api/payments';
import { makePaymentsModule } from '@/store/modules/payments';
import { createLocalVue, shallowMount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
const paymentsApi = new PaymentsHttpApi();
const paymentsModule = makePaymentsModule(paymentsApi);
const store = new Vuex.Store({ modules: { paymentsModule }});
describe('AddPaymentStep.vue', () => {
it('renders correctly', async (): Promise<void> => {
const wrapper = shallowMount(AddPaymentStep, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
await wrapper.find('.payment-step__methods-container__title-area__options-area__token').trigger('click');
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,18 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import { mount } from '@vue/test-utils';
describe('OverviewStep.vue', () => {
it('renders correctly', async (): Promise<void> => {
const wrapper = mount(OverviewStep);
expect(wrapper).toMatchSnapshot();
await wrapper.find('.get-started-button').trigger('click');
expect(wrapper.emitted()).toHaveProperty('setAddPaymentState');
});
});

View File

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddPaymentStep.vue renders correctly 1`] = `
<div class="payment-step">
<h1 class="payment-step__title">Get Started with 5 GB Free</h1>
<p class="payment-step__sub-title">
Adding a payment method ensures your project wont be interrupted after your <b>free</b> credit is used.
</p>
<div class="payment-step__methods-container">
<div class="payment-step__methods-container__title-area">
<h2 class="payment-step__methods-container__title-area__title">Payment Method</h2>
<div class="payment-step__methods-container__title-area__options-area"><span class="payment-step__methods-container__title-area__options-area__token">
STORJ Token
</span> <span class="payment-step__methods-container__title-area__options-area__card selected">
Card
</span></div>
</div>
<!---->
</div>
<addcardstate-stub></addcardstate-stub>
<!---->
</div>
`;
exports[`AddPaymentStep.vue renders correctly 2`] = `
<div class="payment-step">
<h1 class="payment-step__title">Get Started with 5 GB Free</h1>
<p class="payment-step__sub-title">
Adding a payment method ensures your project wont be interrupted after your <b>free</b> credit is used.
</p>
<div class="payment-step__methods-container">
<div class="payment-step__methods-container__title-area">
<h2 class="payment-step__methods-container__title-area__title">Payment Method</h2>
<div class="payment-step__methods-container__title-area__options-area"><span class="payment-step__methods-container__title-area__options-area__token selected">
STORJ Token
</span> <span class="payment-step__methods-container__title-area__options-area__card">
Card
</span></div>
</div>
<!---->
</div>
<!---->
<addstorjstate-stub></addstorjstate-stub>
</div>
`;

View File

@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`OverviewStep.vue renders correctly 1`] = `
<div class="overview-area">
<h1 class="overview-area__title">Welcome to Storj</h1>
<p class="overview-area__sub-title">
Youre just a few steps away from uploading your first object to the 100% secure, decentralized cloud. After
adding payment, youll create a project, API key, get set up with Storj, and start uploading objects.
</p>
<div class="overview-area__steps-area">
<div class="overview-area__steps-area__step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__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> <img src="@/../static/images/onboardingTour/project.jpg" alt="project image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Name Your Project</h2> <span class="overview-area__steps-area__step__subtitle">
Projects are where buckets are created for storing data.
</span>
</div>
<div class="overview-area__steps-area__step second-step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__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> <img src="@/../static/images/onboardingTour/api-key.jpg" alt="api keys image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Create an API Key</h2> <span class="overview-area__steps-area__step__subtitle">
Generate access to your project to upload data.
</span>
</div>
<div class="overview-area__steps-area__step"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg" class="overview-area__steps-area__step__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> <img src="@/../static/images/onboardingTour/uplink.jpg" alt="uplink image" class="overview-step-image">
<h2 class="overview-area__steps-area__step__title">Upload Data</h2> <span class="overview-area__steps-area__step__subtitle">
Store your data on the secure, decentralized cloud.
</span>
</div>
</div>
<div class="get-started-button container" style="width: 251px; height: 56px;"><span class="label">Get Started</span></div>
</div>
`;

View File

@ -0,0 +1,14 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import AddCardState from '@/components/onboardingTour/steps/paymentStates/AddCardState.vue';
import { shallowMount } from '@vue/test-utils';
describe('AddCardState.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(AddCardState);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,55 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import AddStorjState from '@/components/onboardingTour/steps/paymentStates/AddStorjState.vue';
import { makePaymentsModule, PAYMENTS_MUTATIONS } from '@/store/modules/payments';
import { BillingHistoryItem, BillingHistoryItemStatus, BillingHistoryItemType } from '@/types/payments';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { PaymentsMock } from '../../../mock/api/payments';
const localVue = createLocalVue();
localVue.use(Vuex);
const paymentsApi = new PaymentsMock();
const paymentsModule = makePaymentsModule(paymentsApi);
const store = new Vuex.Store({ modules: { paymentsModule }});
describe('AddStorjState.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(AddStorjState, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly with pending transaction', (): void => {
const billingTransactionItem = new BillingHistoryItem('itemId', 'test', 50, 50,
BillingHistoryItemStatus.Pending, 'test', new Date(), new Date(), BillingHistoryItemType.Transaction);
store.commit(PAYMENTS_MUTATIONS.SET_BILLING_HISTORY, [billingTransactionItem]);
const wrapper = shallowMount(AddStorjState, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly with completed transaction', (): void => {
const billingTransactionItem = new BillingHistoryItem('itemId', 'test', 50, 50,
BillingHistoryItemStatus.Completed, 'test', new Date(), new Date(), BillingHistoryItemType.Transaction);
store.commit(PAYMENTS_MUTATIONS.SET_BILLING_HISTORY, [billingTransactionItem]);
store.commit(PAYMENTS_MUTATIONS.SET_BALANCE, 5000);
const wrapper = shallowMount(AddStorjState, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddCardState.vue renders correctly 1`] = `
<div class="add-card-state">
<div class="add-card-state__input-container">
<stripecardinput-stub onstriperesponsecallback="function () { [native code] }"></stripecardinput-stub>
<!---->
</div>
<div class="add-card-state__security-info">
<lockimage-stub></lockimage-stub> <span class="add-card-state__security-info__text">
Your card is secured by 128-bit SSL and AES-256 encryption. Your information is secure.
</span>
</div>
<div class="add-card-state__button">
<!----> <span class="add-card-state__button__label">Add Payment</span></div>
</div>
`;

View File

@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AddStorjState.vue renders correctly 1`] = `
<div class="add-storj-state">
<div class="add-storj-state__container">
<p class="add-storj-state__container__bonus-info">
Deposit STORJ Token to your account and receive a 10% bonus, or $10 for every $100.
</p>
<div class="add-storj-state__container__deposit-area">
<p class="add-storj-state__container__deposit-area__info"><b>Please Note:</b> Your first deposit of $50 or more in STORJ Token is applied to your account
after Coin Payments verifies payment<br><br>5GB are your starting project limits. Increased
amounts are available
<a href="https://support.tardigrade.io/hc/en-us/requests/new?ticket_form_id=360000683212" target="_blank" class="add-storj-state__container__deposit-area__info__request-link">
per request.
</a></p>
<payingstep-stub></payingstep-stub>
<!---->
<!---->
</div>
<!---->
</div>
<vbutton-stub label="Name Your Project" width="222px" height="48px" isdisabled="true" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;
exports[`AddStorjState.vue renders correctly with completed transaction 1`] = `
<div class="add-storj-state">
<div class="add-storj-state__container">
<p class="add-storj-state__container__bonus-info">
Deposit STORJ Token to your account and receive a 10% bonus, or $10 for every $100.
</p>
<div class="add-storj-state__container__deposit-area">
<p class="add-storj-state__container__deposit-area__info"><b>Please Note:</b> Your first deposit of $50 or more in STORJ Token is applied to your account
after Coin Payments verifies payment<br><br>5GB are your starting project limits. Increased
amounts are available
<a href="https://support.tardigrade.io/hc/en-us/requests/new?ticket_form_id=360000683212" target="_blank" class="add-storj-state__container__deposit-area__info__request-link">
per request.
</a></p>
<!---->
<!---->
<verifiedstep-stub></verifiedstep-stub>
</div>
<!---->
</div>
<vbutton-stub label="Name Your Project" width="222px" height="48px" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;
exports[`AddStorjState.vue renders correctly with pending transaction 1`] = `
<div class="add-storj-state">
<div class="add-storj-state__container">
<p class="add-storj-state__container__bonus-info">
Deposit STORJ Token to your account and receive a 10% bonus, or $10 for every $100.
</p>
<div class="add-storj-state__container__deposit-area">
<p class="add-storj-state__container__deposit-area__info"><b>Please Note:</b> Your first deposit of $50 or more in STORJ Token is applied to your account
after Coin Payments verifies payment<br><br>5GB are your starting project limits. Increased
amounts are available
<a href="https://support.tardigrade.io/hc/en-us/requests/new?ticket_form_id=360000683212" target="_blank" class="add-storj-state__container__deposit-area__info__request-link">
per request.
</a></p>
<!---->
<verifyingstep-stub></verifyingstep-stub>
<!---->
</div>
<!---->
</div>
<vbutton-stub label="Name Your Project" width="222px" height="48px" isdisabled="true" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;

View File

@ -0,0 +1,82 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import PayingStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/PayingStep.vue';
import { appStateModule } from '@/store/modules/appState';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
import { Project } from '@/types/projects';
import { Notificator } from '@/utils/plugins/notificator';
import { SegmentioPlugin } from '@/utils/plugins/segment';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import { PaymentsMock } from '../../../../mock/api/payments';
import { ProjectsApiMock } from '../../../../mock/api/projects';
const localVue = createLocalVue();
const segmentioPlugin = new SegmentioPlugin();
localVue.use(Vuex);
localVue.use(segmentioPlugin);
const paymentsApi = new PaymentsMock();
const paymentsModule = makePaymentsModule(paymentsApi);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const notificationsModule = makeNotificationsModule();
const store = new Vuex.Store({ modules: { paymentsModule, notificationsModule, appStateModule, projectsModule }});
class NotificatorPlugin {
public install() {
localVue.prototype.$notify = new Notificator(store);
}
}
const notificationsPlugin = new NotificatorPlugin();
localVue.use(notificationsPlugin);
describe('PayingStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(PayingStep, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('user is unable to add less than 50$ or more than 999999$', async (): Promise<void> => {
const wrapper = mount(PayingStep, {
store,
localVue,
});
wrapper.vm.$data.tokenDepositValue = 30;
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');
wrapper.vm.$data.tokenDepositValue = 1000000;
await wrapper.vm.onConfirmAddSTORJ();
expect((store.state as any).notificationsModule.notificationQueue[1].message).toMatch('First deposit amount must be more than $50 and less than $1000000');
});
it('continue to coin payments works correctly', async (): Promise<void> => {
const project = new Project('testId', 'test', 'test', 'test', 'id', true);
store.commit(PROJECTS_MUTATIONS.ADD, project);
window.open = jest.fn();
const wrapper = mount(PayingStep, {
store,
localVue,
});
wrapper.vm.$data.tokenDepositValue = 70;
await wrapper.vm.onConfirmAddSTORJ();
expect(wrapper.vm.$data.tokenDepositValue).toEqual(50);
});
});

View File

@ -0,0 +1,14 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import VerifiedStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/VerifiedStep.vue';
import { shallowMount } from '@vue/test-utils';
describe('VerifiedStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(VerifiedStep);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,18 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import VerifyingStep from '@/components/onboardingTour/steps/paymentStates/tokenSubSteps/VerifyingStep.vue';
import { shallowMount } from '@vue/test-utils';
describe('VerifyingStep.vue', () => {
it('renders correctly', async (): Promise<void> => {
const wrapper = shallowMount(VerifyingStep);
expect(wrapper).toMatchSnapshot();
await wrapper.find('.verifying-step__back-button').trigger('click');
expect(wrapper.emitted()).toEqual({ 'setDefaultState': [[]] });
});
});

View File

@ -0,0 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PayingStep.vue renders correctly 1`] = `
<div class="paying-step">
<div class="paying-step__title-area"><img src="@/../static/images/onboardingTour/coins.png" alt="coins image">
<h2 class="paying-step__title-area__title">
Select Your Deposit Amount
</h2>
<!---->
</div>
<tokendepositselection-stub class="paying-step__form"></tokendepositselection-stub>
<vbutton-stub label="Continue to Coin Payments" width="100%" height="48px" isbluewhite="true" onpress="function () { [native code] }"></vbutton-stub>
</div>
`;

View File

@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VerifiedStep.vue renders correctly 1`] = `
<div class="verified-step">
<verifiedimage-stub></verifiedimage-stub>
<h2 class="verified-step__title">Payment Verified</h2> <span class="verified-step__sub-title">
Your payment has been successfully verified. Click the button below to name your project and start uploading data.
</span>
</div>
`;

View File

@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`VerifyingStep.vue renders correctly 1`] = `
<div class="verifying-step">
<verifyingimage-stub></verifyingimage-stub>
<h2 class="verifying-step__title">Verifying Payment</h2> <span class="verifying-step__sub-title">
Refresh this page in 10-15 minutes to continue after your purchase is verified. In the meantime, see how
easy it is to get started visiting documentation page, or join the community to get involved.
</span>
<div class="verifying-step__buttons-area"><a href="https://documentation.storj.io" target="_blank" class="verifying-step__buttons-area__how-to-button">
Documentation
</a> <a href="https://storj.io/community/" target="_blank" class="verifying-step__buttons-area__community-button">
Community
</a></div>
<div class="verifying-step__back-button">
<backimage-stub></backimage-stub>
</div>
</div>
`;

View File

@ -1,17 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import OverviewArea from '@/components/overview/OverviewArea.vue';
import { router } from '@/router';
import { mount } from '@vue/test-utils';
describe('OverviewArea.vue', () => {
it('renders correctly', (): void => {
const wrapper = mount(OverviewArea, {
router,
});
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,38 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`OverviewArea.vue renders correctly 1`] = `
<div class="overview-area">
<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"><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">
<h2 class="overview-area__steps-area__items__add-payment-info__title">Add Payment Info</h2> <img src="@/../static/images/overview/payment.jpg" alt="payment image" class="overview-area-image">
</div>
<div class="overview-area__steps-area__items__create-project">
<h2 class="overview-area__steps-area__items__create-project__title">Create a Project</h2> <img src="@/../static/images/overview/project.jpg" alt="project image" class="overview-area-image">
</div>
<div class="overview-area__steps-area__items__create-api-key">
<h2 class="overview-area__steps-area__items__create-api-key__title">Create an API Key</h2> <img src="@/../static/images/overview/apikey.jpg" alt="api key image" class="overview-area-image">
</div>
</div>
</div>
</div>
`;

View File

@ -2,7 +2,6 @@
exports[`Dashboard renders correctly when data is loaded 1`] = ` exports[`Dashboard renders correctly when data is loaded 1`] = `
<div class="dashboard-container"> <div class="dashboard-container">
<!---->
<div class="dashboard-container__wrap"> <div class="dashboard-container__wrap">
<navigationarea-stub class="regular-navigation"></navigationarea-stub> <navigationarea-stub class="regular-navigation"></navigationarea-stub>
<div class="dashboard-container__wrap__column"> <div class="dashboard-container__wrap__column">
@ -23,6 +22,5 @@ exports[`Dashboard renders correctly when data is loaded 1`] = `
exports[`Dashboard renders correctly when data is loading 1`] = ` exports[`Dashboard renders correctly when data is loading 1`] = `
<div class="dashboard-container"> <div class="dashboard-container">
<div class="loading-overlay active"><img src="@/../static/images/register/Loading.gif" alt="Company logo loading gif" class="loading-image"></div> <div class="loading-overlay active"><img src="@/../static/images/register/Loading.gif" alt="Company logo loading gif" class="loading-image"></div>
<!---->
</div> </div>
`; `;