web/satellite: update onboarding flow: removed redundant steps, routing applied

WHAT:
redundant steps removed from onboarding tour, new routing applied

WHY:
flow simplification

Change-Id: I153c5fe30914317ddd9bf7008d4e5e6a24f77f9a
This commit is contained in:
VitaliiShpital 2020-12-01 18:53:57 +02:00 committed by Vitalii Shpital
parent aaec8bfc5c
commit d5f1245cc7
36 changed files with 55 additions and 2045 deletions

View File

@ -199,7 +199,7 @@ export default class TokenDepositSelection extends Vue {
* Indicates if app state is in onboarding tour state. * Indicates if app state is in onboarding tour state.
*/ */
private get isOnboardingTour(): boolean { private get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name; return this.$route.path.includes(RouteConfig.OnboardingTour.path);
} }
} }
</script> </script>

View File

@ -60,7 +60,7 @@ export default class ProjectSelection extends Vue {
* Indicates if current route is onboarding tour. * Indicates if current route is onboarding tour.
*/ */
public get isOnboardingTour(): boolean { public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name; return this.$route.path.includes(RouteConfig.OnboardingTour.path);
} }
/** /**

View File

@ -24,7 +24,7 @@ export default class ResourcesDropdown extends Vue {
* Indicates if current route is onboarding tour. * Indicates if current route is onboarding tour.
*/ */
public get isOnboardingTour(): boolean { public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name; return this.$route.path.includes(RouteConfig.OnboardingTour.path);
} }
} }
</script> </script>

View File

@ -43,7 +43,7 @@ export default class ResourcesSelection extends Vue {
* Indicates if current route is onboarding tour. * Indicates if current route is onboarding tour.
*/ */
public get isOnboardingTour(): boolean { public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name; return this.$route.path.includes(RouteConfig.OnboardingTour.path);
} }
/** /**

View File

@ -43,7 +43,7 @@ export default class SettingsSelection extends Vue {
* Indicates if current route is onboarding tour. * Indicates if current route is onboarding tour.
*/ */
public get isOnboardingTour(): boolean { public get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name; return this.$route.path.includes(RouteConfig.OnboardingTour.path);
} }
/** /**

View File

@ -67,7 +67,7 @@ export default class NavigationArea extends Vue {
* Indicates if current route is onboarding tour. * Indicates if current route is onboarding tour.
*/ */
private get isOnboardingTour(): boolean { private get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name; return this.$route.path.includes(RouteConfig.OnboardingTour.path);
} }
} }
</script> </script>

View File

@ -11,34 +11,7 @@
<CloseImage class="tour-area__info-bar__close-img" @click="disableInfoBar"/> <CloseImage class="tour-area__info-bar__close-img" @click="disableInfoBar"/>
</div> </div>
<div class="tour-area__content"> <div class="tour-area__content">
<ProgressBar <router-view/>
:is-paywall-enabled="isPaywallEnabled"
:is-add-payment-step="isAddPaymentState"
:is-create-project-step="isCreateProjectState"
:is-create-api-key-step="isCreatApiKeyState"
:is-upload-data-step="isUploadDataState"
/>
<OverviewStep
v-if="isDefaultState && isPaywallEnabled"
@setAddPaymentState="setAddPaymentState"
/>
<OverviewStepNoPaywall
v-if="isDefaultState && !isPaywallEnabled"
@setCreateProjectState="setCreateProjectState"
/>
<AddPaymentStep
v-if="isAddPaymentState"
@setProjectState="setCreateProjectState"
/>
<CreateProjectStep
v-if="isCreateProjectState"
@setApiKeyState="setCreateApiKeyState"
/>
<CreateApiKeyStep
v-if="isCreatApiKeyState"
@setUploadDataState="setUploadDataState"
/>
<UploadDataStep v-if="isUploadDataState"/>
<img <img
v-if="isAddPaymentState" v-if="isAddPaymentState"
class="tour-area__content__tardigrade" class="tour-area__content__tardigrade"
@ -52,36 +25,16 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import ProgressBar from '@/components/onboardingTour/ProgressBar.vue';
import AddPaymentStep from '@/components/onboardingTour/steps/AddPaymentStep.vue';
import CreateApiKeyStep from '@/components/onboardingTour/steps/CreateApiKeyStep.vue';
import CreateProjectStep from '@/components/onboardingTour/steps/CreateProjectStep.vue';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import OverviewStepNoPaywall from '@/components/onboardingTour/steps/OverviewStepNoPaywall.vue';
import UploadDataStep from '@/components/onboardingTour/steps/UploadDataStep.vue';
import CheckedImage from '@/../static/images/common/checked.svg';
import CloseImage from '@/../static/images/onboardingTour/close.svg'; import CloseImage from '@/../static/images/onboardingTour/close.svg';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
import { TourState } from '@/utils/constants/onboardingTourEnums';
@Component({ @Component({
components: { components: {
OverviewStepNoPaywall,
UploadDataStep,
CreateApiKeyStep,
CreateProjectStep,
AddPaymentStep,
ProgressBar,
OverviewStep,
CheckedImage,
CloseImage, CloseImage,
}, },
}) })
export default class OnboardingTourArea extends Vue { export default class OnboardingTourArea extends Vue {
public areaState: number = TourState.DEFAULT;
public isInfoBarVisible: boolean = true; public isInfoBarVisible: boolean = true;
/** /**
@ -89,7 +42,7 @@ export default class OnboardingTourArea extends Vue {
* Sets area to needed state. * Sets area to needed state.
*/ */
public mounted(): void { public mounted(): void {
if (this.userHasProject && this.userHasApiKeys) { if (this.userHasProject && this.userHasAccessGrants) {
try { try {
this.$router.push(RouteConfig.ProjectDashboard.path); this.$router.push(RouteConfig.ProjectDashboard.path);
} catch (error) { } catch (error) {
@ -99,22 +52,22 @@ export default class OnboardingTourArea extends Vue {
return; return;
} }
if (this.userHasProject && !this.userHasApiKeys) { if (this.userHasProject && !this.userHasAccessGrants) {
this.disableInfoBar(); this.disableInfoBar();
this.setCreateApiKeyState(); this.setCreateAccessGrantStep();
return; return;
} }
if (this.$store.state.paymentsModule.creditCards.length > 0) { if (this.$store.state.paymentsModule.creditCards.length > 0) {
this.disableInfoBar(); this.disableInfoBar();
this.setCreateProjectState(); this.setCreateAccessGrantStep();
return; return;
} }
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isBalancePositive) { if (this.$store.getters.isTransactionProcessing || this.$store.getters.isBalancePositive) {
this.setAddPaymentState(); this.setAddPaymentStep();
} }
} }
@ -126,67 +79,17 @@ export default class OnboardingTourArea extends Vue {
} }
/** /**
* Indicates if area is in default state. * Sets area's state to adding payment method step.
*/ */
public get isDefaultState(): boolean { public setAddPaymentStep(): void {
return this.areaState === TourState.DEFAULT; this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.PaymentStep).path);
} }
/** /**
* Indicates if area is in adding payment method state. * Sets area's state to creating access grant step.
*/ */
public get isAddPaymentState(): boolean { public setCreateAccessGrantStep(): void {
return this.areaState === TourState.ADDING_PAYMENT; this.$router.push(RouteConfig.OnboardingTour.with(RouteConfig.AccessGrant).path);
}
/**
* Indicates if area is in creating project state.
*/
public get isCreateProjectState(): boolean {
return this.areaState === TourState.PROJECT;
}
/**
* Indicates if area is in api key state.
*/
public get isCreatApiKeyState(): boolean {
return this.areaState === TourState.API_KEY;
}
/**
* Indicates if area is in upload data state.
*/
public get isUploadDataState(): boolean {
return this.areaState === TourState.UPLOAD;
}
/**
* Sets area's state to adding payment method state.
*/
public setAddPaymentState(): void {
this.areaState = TourState.ADDING_PAYMENT;
}
/**
* Sets area's state to creating project state.
*/
public setCreateProjectState(): void {
this.disableInfoBar();
this.areaState = TourState.PROJECT;
}
/**
* Sets area's state to creating api key state.
*/
public setCreateApiKeyState(): void {
this.areaState = TourState.API_KEY;
}
/**
* Sets area's state to upload data state.
*/
public setUploadDataState(): void {
this.areaState = TourState.UPLOAD;
} }
/** /**
@ -196,6 +99,13 @@ export default class OnboardingTourArea extends Vue {
this.isInfoBarVisible = false; this.isInfoBarVisible = false;
} }
/**
* Indicates if area is on adding payment method step.
*/
public get isAddPaymentState(): boolean {
return this.$route.name === RouteConfig.PaymentStep.name;
}
/** /**
* Indicates if user has at least one project. * Indicates if user has at least one project.
*/ */
@ -204,10 +114,10 @@ export default class OnboardingTourArea extends Vue {
} }
/** /**
* Indicates if user has at least one API key. * Indicates if user has at least one access grant.
*/ */
private get userHasApiKeys(): boolean { private get userHasAccessGrants(): boolean {
return this.$store.state.apiKeysModule.page.apiKeys.length > 0; return this.$store.state.accessGrantsModule.page.accessGrants.length > 0;
} }
} }
</script> </script>
@ -246,7 +156,10 @@ export default class OnboardingTourArea extends Vue {
} }
&__content { &__content {
padding: 0 100px 80px 100px; display: flex;
flex-direction: column;
align-items: center;
padding: 110px 0 80px 0;
position: relative; position: relative;
&__tardigrade { &__tardigrade {
@ -257,24 +170,4 @@ export default class OnboardingTourArea extends Vue {
} }
} }
} }
@media screen and (max-width: 1550px) {
.tour-area {
&__content {
padding: 0 50px 80px 50px;
}
}
}
@media screen and (max-width: 1000px) {
.tour-area {
&__content {
padding: 0 25px 80px 25px;
}
}
}
</style> </style>

View File

@ -1,187 +0,0 @@
// 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
v-if="isPaywallEnabled"
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isAddPaymentStep || isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
>
<CheckedImage/>
</div>
<div
v-if="isPaywallEnabled"
class="progress-bar-container__progress-area__bar"
:class="{ 'completed-step': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
/>
<div
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
>
<CheckedImage/>
</div>
<div
class="progress-bar-container__progress-area__bar"
:class="{ 'completed-step': isCreateApiKeyStep || isUploadDataStep }"
/>
<div
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isCreateApiKeyStep || isUploadDataStep }"
>
<CheckedImage/>
</div>
<div
class="progress-bar-container__progress-area__bar"
:class="{ 'completed-step': isUploadDataStep }"
/>
<div
class="progress-bar-container__progress-area__circle"
:class="{ 'completed-step': isUploadDataStep }"
>
<CheckedImage/>
</div>
</div>
<div class="progress-bar-container__titles-area" :class="{ 'titles-area-no-paywall': !isPaywallEnabled }">
<span
v-if="isPaywallEnabled"
class="progress-bar-container__titles-area__title"
:class="{ 'completed-font-color': isAddPaymentStep || isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
>
Add Payment
</span>
<span
class="progress-bar-container__titles-area__title name-your-project-title"
:class="{ 'completed-font-color': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep, 'title-no-paywall': !isPaywallEnabled }"
>
Name Your Project
</span>
<span
class="progress-bar-container__titles-area__title api-key-title"
:class="{ 'completed-font-color': isCreateApiKeyStep || isUploadDataStep }"
>
Create an API Key
</span>
<span
class="progress-bar-container__titles-area__title"
:class="{ 'completed-font-color': isUploadDataStep }"
>
Upload Data
</span>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import CheckedImage from '@/../static/images/common/checked.svg';
@Component({
components: {
CheckedImage,
},
})
export default class ProgressBar extends Vue {
@Prop({ default: false })
public readonly isPaywallEnabled: boolean;
@Prop({ default: false })
public readonly isAddPaymentStep: boolean;
@Prop({ default: false })
public readonly isCreateProjectStep: boolean;
@Prop({ default: false })
public readonly isCreateApiKeyStep: boolean;
@Prop({ default: false })
public readonly isUploadDataStep: boolean;
}
</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 188px;
&__title {
font-family: 'font_regular', sans-serif;
font-size: 10px;
line-height: 15px;
color: rgba(0, 0, 0, 0.4);
text-align: center;
}
}
}
.name-your-project-title {
padding: 0 0 0 10px;
}
.api-key-title {
padding: 0 15px 0 0;
}
.completed-step {
background-color: #2683ff;
}
.completed-font-color {
color: #2683ff;
}
.titles-area-no-paywall {
padding: 0 188px 0 178px;
}
.title-no-paywall {
padding: 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 128px;
}
}
.titles-area-no-paywall {
padding: 0 128px 0 118px;
}
}
</style>

View File

@ -1,385 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-api-key-step">
<h1 class="create-api-key-step__title">Create an API Key</h1>
<p class="create-api-key-step__sub-title">
API keys provide access to the project for creating buckets and uploading objects through the command line
interface. This will be your first API key, and you can always create more keys later on.
</p>
<div class="create-api-key-step__container">
<div class="create-api-key-step__container__title-area">
<h2 class="create-api-key-step__container__title-area__title">Create API Key</h2>
<img
v-if="isLoading"
class="create-api-key-step__container__title-area__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
</div>
<HeaderedInput
label="API Key Name"
placeholder="Enter API Key Name (i.e. Dans Key)"
class="full-input"
width="calc(100% - 4px)"
:error="errorMessage"
@setData="setApiKeyName"
/>
<div class="create-api-key-step__container__create-key-area" v-if="isCreatingState">
<VButton
class="generate-button"
width="100%"
height="40px"
label="Generate API Key"
:is-blue-white="true"
:on-press="createApiKey"
/>
</div>
<div class="create-api-key-step__container__copy-key-area" v-else>
<div class="create-api-key-step__container__copy-key-area__header">
<InfoImage/>
<span class="create-api-key-step__container__copy-key-area__header__title">
API Keys only appear here once. Copy and paste this key to your preferred method of storing secrets.
</span>
</div>
<div class="create-api-key-step__container__copy-key-area__key-container">
<span class="create-api-key-step__container__copy-key-area__key-container__key">{{ key }}</span>
<div class="create-api-key-step__container__copy-key-area__key-container__copy-button">
<VButton
width="81px"
height="40px"
label="Copy"
:is-blue-white="true"
:on-press="onCopyClick"
/>
</div>
</div>
</div>
<p class="create-api-key-step__container__info" v-if="isCopyState">
We dont record your API Keys, which are only displayed once when generated. If you loose this
key, it cannot be recovered but you can always create new API Keys when needed.
</p>
<div class="create-api-key-step__container__blur" v-if="isLoading"/>
</div>
<VButton
class="done-button"
width="156px"
height="48px"
label="Done"
:on-press="onDoneClick"
:is-disabled="isCreatingState"
/>
<SaveApiKeyModal
v-if="isSaveApiKeyModalShown"
@confirmSave="onConfirmClick"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import SaveApiKeyModal from '@/components/onboardingTour/steps/SaveApiKeyModal.vue';
import InfoImage from '@/../static/images/onboardingTour/info.svg';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { ApiKey } from '@/types/apiKeys';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { AddingApiKeyState } from '@/utils/constants/onboardingTourEnums';
const {
CREATE,
FETCH,
} = API_KEYS_ACTIONS;
@Component({
components: {
VButton,
HeaderedInput,
SaveApiKeyModal,
InfoImage,
},
})
export default class CreateApiKeyStep extends Vue {
private name: string = '';
private addingState: number = AddingApiKeyState.CREATE;
private readonly FIRST_PAGE = 1;
public key: string = '';
public errorMessage: string = '';
public isLoading: boolean = false;
/**
* Indicates if save API key modal is shown.
*/
public get isSaveApiKeyModalShown(): boolean {
return this.$store.state.appStateModule.appState.isSaveApiKeyModalShown;
}
/**
* Indicates if view is in creating state.
*/
public get isCreatingState(): boolean {
return this.addingState === AddingApiKeyState.CREATE;
}
/**
* Indicates if view is in copy state.
*/
public get isCopyState(): boolean {
return this.addingState === AddingApiKeyState.COPY;
}
/**
* Indicates view state to copy state.
*/
public setCopyState(): void {
this.addingState = AddingApiKeyState.COPY;
}
/**
* Sets api key name from input value.
*/
public setApiKeyName(value: string): void {
this.name = value.trim();
this.errorMessage = '';
}
/**
* Creates api key and refreshes store.
*/
public async createApiKey(): Promise<void> {
if (this.isLoading) {
return;
}
if (!this.name) {
this.errorMessage = 'API Key name can`t be empty';
return;
}
this.isLoading = true;
let createdApiKey: ApiKey;
try {
createdApiKey = await this.$store.dispatch(CREATE, this.name);
} catch (error) {
await this.$notify.error(error.message);
this.isLoading = false;
return;
}
await this.$notify.success('Successfully created new api key');
this.key = createdApiKey.secret;
this.$segment.track(SegmentEvent.API_KEY_CREATED, {
project_id: this.$store.getters.selectedProject.id,
});
try {
await this.$store.dispatch(FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
}
this.setCopyState();
this.isLoading = false;
}
/**
* Copies api key secret to buffer.
*/
public onCopyClick(): void {
this.$copyText(this.key);
this.$notify.success('Key saved to clipboard');
}
/**
* Toggles save API key modal visibility.
*/
public onDoneClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SAVE_API_KEY_MODAL);
}
/**
* Sets tour state to last step.
*/
public onConfirmClick(): void {
this.onDoneClick();
this.$emit('setUploadDataState');
}
}
</script>
<style scoped lang="scss">
h1,
h2,
p {
margin: 0;
}
.create-api-key-step {
font-family: 'font_regular', sans-serif;
margin-top: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 0 200px;
&__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;
}
&__container {
padding: 50px;
width: calc(100% - 100px);
border-radius: 8px;
background-color: #fff;
position: relative;
margin-bottom: 30px;
&__title-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 10px;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 22px;
line-height: 27px;
color: #354049;
margin-right: 15px;
}
&__loading-image {
width: 18px;
height: 18px;
}
}
&__create-key-area {
width: calc(100% - 90px);
padding: 40px 45px;
margin-top: 30px;
background-color: #0c2546;
border-radius: 8px;
}
&__copy-key-area {
width: 100%;
margin-top: 30px;
&__header {
padding: 10px;
width: calc(100% - 20px);
background-color: #ce3030;
display: flex;
align-items: center;
justify-content: flex-start;
border-radius: 8px 8px 0 0;
&__title {
font-size: 12px;
line-height: 16px;
color: #fff;
}
}
&__key-container {
background-color: #0c2546;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 25px;
width: calc(100% - 50px);
border-radius: 0 0 8px 8px;
&__key {
font-size: 15px;
line-height: 23px;
color: #fff;
margin-right: 50px;
word-break: break-all;
}
&__copy-button {
min-width: 85px;
}
}
}
&__info {
width: 100%;
margin-top: 30px;
font-size: 12px;
line-height: 18px;
text-align: center;
color: #354049;
word-break: break-word;
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
}
.full-input {
width: 100%;
}
.info-svg {
min-width: 18px;
margin-right: 5px;
}
@media screen and (max-width: 1650px) {
.create-api-key-step {
padding: 0 100px;
}
}
@media screen and (max-width: 1450px) {
.create-api-key-step {
padding: 0 60px;
}
}
@media screen and (max-width: 900px) {
.create-api-key-step {
padding: 0 50px;
}
}
</style>

View File

@ -1,280 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="new-project-step">
<h1 class="new-project-step__title">Name Your Project</h1>
<p class="new-project-step__sub-title">
Projects are where buckets are created for storing data. Within a Project, usage is tracked at the bucket
level and aggregated for billing.
</p>
<div class="new-project-step__container">
<div class="new-project-step__container__title-area">
<h2 class="new-project-step__container__title-area__title">Project Details</h2>
<img
v-if="isLoading"
class="new-project-step__container__title-area__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
</div>
<HeaderedInput
label="Project Name"
additional-label="Up To 20 Characters"
placeholder="Enter Project Name"
class="full-input project-name-input"
width="100%"
is-limit-shown="true"
:current-limit="projectName.length"
:max-symbols="20"
:error="nameError"
@setData="setProjectName"
/>
<HeaderedInput
label="Description"
placeholder="Enter Project Description"
additional-label="Optional"
class="full-input"
is-multiline="true"
is-limit-shown="true"
:current-limit="description.length"
:max-symbols="100"
height="60px"
width="calc(100% - 42px)"
@setData="setProjectDescription"
/>
<div class="new-project-step__container__blur" v-if="isLoading"/>
</div>
<VButton
class="create-project-button"
width="156px"
height="48px"
label="Create Project"
:on-press="createProjectClick"
:is-disabled="!projectName"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import { API_KEYS_ACTIONS } from '@/store/modules/apiKeys';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@Component({
components: {
VButton,
HeaderedInput,
},
})
export default class CreateProjectStep extends Vue {
private description: string = '';
public projectName: string = '';
public isLoading: boolean = false;
public nameError: string = '';
/**
* Sets project name from input value.
*/
public setProjectName(value: string): void {
this.projectName = value;
this.nameError = '';
}
/**
* Sets project description from input value.
*/
public setProjectDescription(value: string): void {
this.description = value;
}
/**
* Creates project and refreshes store.
*/
public async createProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
this.projectName = this.projectName.trim();
const project = new ProjectFields(
this.projectName,
this.description,
this.$store.getters.user.id,
);
try {
project.checkName();
} catch (error) {
this.isLoading = false;
this.nameError = error.message;
return;
}
let createdProjectId: string = '';
try {
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: createdProjectId,
});
} catch (error) {
this.isLoading = false;
await this.$notify.error(error.message);
return;
}
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
try {
await this.fetchProjectMembers();
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, createdProjectId);
} catch (error) {
await this.$notify.error(`Unable to create project. ${error.message}`);
}
this.clearApiKeys();
this.clearBucketUsage();
await this.$notify.success('Project created successfully!');
this.isLoading = false;
this.$emit('setApiKeyState');
}
/**
* Clears project members store and fetches new.
*/
private async fetchProjectMembers(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
const fistPage = 1;
await this.$store.dispatch(PM_ACTIONS.FETCH, fistPage);
}
/**
* Clears api keys store.
*/
private clearApiKeys(): void {
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
}
/**
* Clears bucket usage store.
*/
private clearBucketUsage(): void {
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
}
</script>
<style scoped lang="scss">
h1,
h2,
p {
margin: 0;
}
.new-project-step {
font-family: 'font_regular', sans-serif;
margin-top: 75px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 0 200px;
&__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;
}
&__container {
padding: 50px;
width: calc(100% - 100px);
border-radius: 8px;
background-color: #fff;
position: relative;
margin-bottom: 30px;
&__title-area {
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 10px;
&__title {
font-family: 'font_medium', sans-serif;
font-size: 22px;
line-height: 27px;
color: #354049;
margin-right: 15px;
}
&__loading-image {
width: 18px;
height: 18px;
}
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
z-index: 100;
}
}
}
.full-input {
width: 100%;
margin-top: 25px;
}
@media screen and (max-width: 1450px) {
.new-project-step {
padding: 0 150px;
}
}
@media screen and (max-width: 900px) {
.new-project-step {
padding: 0 50px;
}
}
</style>

View File

@ -1,34 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template src="./overviewStepNoPaywall.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import FirstStepIcon from '@/../static/images/common/one.svg';
import ThirdStepIcon from '@/../static/images/common/three.svg';
import SecondStepIcon from '@/../static/images/common/two.svg';
@Component({
components: {
VButton,
FirstStepIcon,
ThirdStepIcon,
SecondStepIcon,
},
})
export default class OverviewStepNoPaywall extends Vue {
/**
* Holds button click logic.
* Sets tour state to adding project state.
*/
public onClick(): void {
this.$emit('setCreateProjectState');
}
}
</script>
<style scoped lang="scss" src="./overviewStepNoPaywall.scss"></style>

View File

@ -1,116 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="save-api-modal">
<div class="save-api-modal__container">
<OrangeExclamation/>
<h1 class="save-api-modal__container__title">Is Your API Key Saved?</h1>
<p class="save-api-modal__container__message">
API Keys are only displayed once when generated. If you havent saved your key, go back to copy and
paste the API key to your preferred method of storing secrets (i.e. TextEdit, Keybase, etc.)
</p>
<div class="save-api-modal__container__buttons-area">
<VButton
class="back-button"
width="186px"
height="45px"
label="Go Back"
:on-press="onBackClick"
:is-blue-white="true"
/>
<VButton
width="186px"
height="45px"
label="Yes, it's Saved!"
:on-press="onConfirmClick"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import OrangeExclamation from '@/../static/images/onboardingTour/orange-exclamation.svg';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
@Component({
components: {
OrangeExclamation,
VButton,
},
})
export default class SaveApiKeyModal extends Vue {
/**
* Toggles modal visibility.
*/
public onBackClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SAVE_API_KEY_MODAL);
}
/**
* Proceeds to tour's last step.
*/
public onConfirmClick(): void {
this.$emit('confirmSave');
}
}
</script>
<style scoped lang="scss">
.save-api-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(9, 21, 35, 0.85);
display: flex;
align-items: center;
justify-content: center;
font-family: 'font_regular', sans-serif;
&__container {
background-color: #fff;
z-index: 1;
padding: 35px;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 6px;
max-width: 460px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 10px 0;
}
&__message {
font-size: 16px;
line-height: 24px;
color: #000;
word-break: break-word;
text-align: center;
margin: 0 0 10px 0;
}
&__buttons-area {
display: flex;
align-items: center;
}
}
}
.back-button {
margin-right: 10px;
}
</style>

View File

@ -1,250 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="upload-data-area">
<div class="upload-data-area__container">
<h1 class="upload-data-area__container__title">Upload Data</h1>
<p class="upload-data-area__container__sub-title">
From here, youll set up Tardigrade to store data for your project using our S3 Gateway, Uplink CLI, or
select from our growing library of connectors to build apps on Tardigrade.
</p>
<div class="upload-data-area__container__docs-area">
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Migrate Data from your Existing AWS buckets
</h2>
<img src="@/../static/images/onboardingTour/s3.png" alt="s3 gateway image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
S3 Gateway
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Make the switch with Tardigrades S3 Gateway.
</p>
<a
class="upload-data-area__container__docs-area__option__link"
href="https://documentation.tardigrade.io/api-reference/s3-gateway"
target="_blank"
rel="noopener noreferrer"
>
S3 Gateway Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option uplink-option">
<h2 class="upload-data-area__container__docs-area__option__title">
Upload Data from Your Local Environment
</h2>
<img src="@/../static/images/onboardingTour/uplinkcli.png" alt="uplink cli image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
Uplink CLI
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Start uploading data from the command line.
</p>
<a
class="upload-data-area__container__docs-area__option__link"
href="https://documentation.tardigrade.io/getting-started/uploading-your-first-object/set-up-uplink-cli"
target="_blank"
rel="noopener noreferrer"
>
Uplink CLI Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Use Tardigrade for your apps storage layer
</h2>
<img src="@/../static/images/onboardingTour/connectors.png" alt="connectors image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
App Connectors
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Integrate Tardigrade into your existing stack.
</p>
<a
class="upload-data-area__container__docs-area__option__link"
href="https://tardigrade.io/connectors/"
target="_blank"
rel="noopener noreferrer"
>
App Connectors
</a>
</div>
</div>
</div>
<VButton
class="go-to-button"
width="276px"
height="40px"
label="Go to Dashboard"
:on-press="onButtonClick"
/>
<span class="upload-data-area__support-info">
Need help?
<a
class="upload-data-area__support-info__link"
href="https://support.tardigrade.io/hc/en-us"
target="_blank"
rel="noopener noreferrer"
>
Contact support
</a>
</span>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import { RouteConfig } from '@/router';
@Component({
components: {
VButton,
},
})
export default class UploadDataStep extends Vue {
/**
* Holds button click logic.
* Ends onboarding tour and redirects to project dashboard.
*/
public onButtonClick(): void {
this.$router.push(RouteConfig.ProjectDashboard.path);
}
}
</script>
<style scoped lang="scss">
h1,
h2,
h3,
p {
margin: 0;
}
.upload-data-area {
display: flex;
flex-direction: column;
align-items: center;
width: auto;
font-family: 'font_regular', sans-serif;
margin-top: 25px;
&__container {
padding: 60px;
display: flex;
flex-direction: column;
align-items: center;
background-color: rgba(255, 255, 255, 0.4);
border-radius: 8px;
margin-bottom: 50px;
&__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;
max-width: 800px;
}
&__docs-area {
display: flex;
align-items: center;
justify-content: space-between;
&__option {
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid rgba(144, 155, 168, 0.4);
border-radius: 8px;
&__title {
font-size: 10px;
line-height: 15px;
text-align: center;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #909ba8;
margin-bottom: 25px;
max-width: 181px;
word-break: break-word;
}
&__sub-title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 26px;
text-align: center;
color: #354049;
margin: 25px 0 5px 0;
}
&__info {
font-size: 14px;
line-height: 17px;
text-align: center;
color: #61666b;
max-width: 181px;
word-break: break-word;
margin-bottom: 15px;
}
&__link {
width: 181px;
height: 40px;
border: 1px solid #2683ff;
border-radius: 6px;
background-color: #fff;
color: #2683ff;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: #2683ff;
color: #fff;
}
}
}
}
}
&__support-info {
margin-top: 50px;
font-family: 'font_bold', sans-serif;
font-size: 15px;
line-height: 22px;
text-align: center;
color: #909ba8;
&__link {
text-decoration: underline;
}
}
}
.uplink-option {
margin: 0 45px;
}
@media screen and (max-width: 1350px) {
.uplink-option {
margin: 0 15px;
}
}
</style>

View File

@ -1,43 +0,0 @@
<!--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. Simply
add a payment method any time before your credit runs out to keep using Storj.
</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

@ -1,107 +0,0 @@
// 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 0 50px;
}
@media screen and (max-width: 1450px) {
.overview-area {
padding: 75px 30px;
}
.second-step {
margin: 0 20px 0 20px;
}
}
@media screen and (max-width: 900px) {
.overview-area {
&__steps-area {
flex-direction: column;
&__step {
margin: 0 0 30px 0;
}
}
}
}

View File

@ -77,6 +77,10 @@ export abstract class RouteConfig {
public static OverviewStep = new NavigationLink('overview', 'Onboarding Overview'); public static OverviewStep = new NavigationLink('overview', 'Onboarding Overview');
public static PaymentStep = new NavigationLink('payment', 'Onboarding Payment'); public static PaymentStep = new NavigationLink('payment', 'Onboarding Payment');
public static AccessGrant = new NavigationLink('access', 'Onboarding Access Grant'); public static AccessGrant = new NavigationLink('access', 'Onboarding Access Grant');
public static AccessGrantName = new NavigationLink('name', 'Onboarding Name Access Grant');
public static AccessGrantPermissions = new NavigationLink('permissions', 'Onboarding Access Grant Permissions');
public static AccessGrantPassphrase = new NavigationLink('create-passphrase', 'Onboarding Access Grant Create Passphrase');
public static AccessGrantResult = new NavigationLink('result', 'Onboarding Access Grant Result');
// TODO: disabled until implementation // TODO: disabled until implementation
// public static Referral = new NavigationLink('referral', 'Referral'); // public static Referral = new NavigationLink('referral', 'Referral');
@ -201,24 +205,24 @@ export const router = new Router({
component: CreateAccessGrantStep, component: CreateAccessGrantStep,
children: [ children: [
{ {
path: RouteConfig.NameStep.path, path: RouteConfig.AccessGrantName.path,
name: RouteConfig.NameStep.name, name: RouteConfig.AccessGrantName.name,
component: NameStep, component: NameStep,
}, },
{ {
path: RouteConfig.PermissionsStep.path, path: RouteConfig.AccessGrantPermissions.path,
name: RouteConfig.PermissionsStep.name, name: RouteConfig.AccessGrantPermissions.name,
component: PermissionsStep, component: PermissionsStep,
props: true, props: true,
}, },
{ {
path: RouteConfig.CreatePassphraseStep.path, path: RouteConfig.AccessGrantPassphrase.path,
name: RouteConfig.CreatePassphraseStep.name, name: RouteConfig.AccessGrantPassphrase.name,
component: CreatePassphraseStep, component: CreatePassphraseStep,
}, },
{ {
path: RouteConfig.ResultStep.path, path: RouteConfig.AccessGrantResult.path,
name: RouteConfig.ResultStep.name, name: RouteConfig.AccessGrantResult.name,
component: ResultStep, component: ResultStep,
}, },
], ],

View File

@ -1,14 +1,6 @@
// Copyright (C) 2020 Storj Labs, Inc. // Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information. // See LICENSE for copying information.
export enum TourState {
DEFAULT = 1,
ADDING_PAYMENT,
PROJECT,
API_KEY,
UPLOAD,
}
export enum AddingPaymentState { export enum AddingPaymentState {
ADD_CARD = 1, ADD_CARD = 1,
ADD_STORJ, ADD_STORJ,
@ -19,8 +11,3 @@ export enum AddingStorjState {
VERIFYING, VERIFYING,
VERIFIED, VERIFIED,
} }
export enum AddingApiKeyState {
CREATE = 1,
COPY,
}

View File

@ -234,7 +234,7 @@ export default class DashboardArea extends Vue {
* Indicates if no paywall info bar is shown. * Indicates if no paywall info bar is shown.
*/ */
public get isNoPaywallInfoBarShown(): boolean { public get isNoPaywallInfoBarShown(): boolean {
const isOnboardingTour: boolean = this.$route.name === RouteConfig.OnboardingTour.name; const isOnboardingTour: boolean = this.$route.path.includes(RouteConfig.OnboardingTour.path);
return !this.isPaywallEnabled && !isOnboardingTour && return !this.isPaywallEnabled && !isOnboardingTour &&
this.$store.state.paymentsModule.balance.coins === 0 && this.$store.state.paymentsModule.balance.coins === 0 &&

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,3 +0,0 @@
<svg class="info-svg" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 -7.86805e-07C4.02944 -1.22135e-06 1.22135e-06 4.02944 7.86805e-07 9C3.52265e-07 13.9706 4.02944 18 9 18C13.9706 18 18 13.9706 18 9C18 4.02944 13.9706 -3.52265e-07 9 -7.86805e-07ZM10 13C10 13.5523 9.55229 14 9 14C8.44772 14 8 13.5523 8 13C8 12.4477 8.44772 12 9 12C9.55229 12 10 12.4477 10 13ZM10 9C10 9.55228 9.55228 10 9 10C8.44771 10 8 9.55228 8 9L8 5C8 4.44771 8.44772 4 9 4C9.55229 4 10 4.44772 10 5L10 9Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 602 B

View File

@ -1,3 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 24C1.15877e-06 10.7452 10.7452 -1.15877e-06 24 0C37.2548 2.09815e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48C10.7452 48 -2.09815e-06 37.2548 0 24ZM26.6669 24.0003C26.6669 25.473 25.473 26.6669 24.0003 26.6669C22.5275 26.6669 21.3336 25.473 21.3336 24.0003L21.3336 13.3336C21.3336 11.8609 22.5275 10.6669 24.0003 10.6669C25.473 10.6669 26.6669 11.8609 26.6669 13.3336L26.6669 24.0003ZM24.0003 37.3335C25.473 37.3335 26.6669 36.1396 26.6669 34.6668C26.6669 33.194 25.473 32.0001 24.0003 32.0001C22.5275 32.0001 21.3336 33.194 21.3336 34.6668C21.3336 36.1396 22.5275 37.3335 24.0003 37.3335Z" fill="#F4B000"/>
</svg>

Before

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -6,6 +6,7 @@ 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 OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import ProjectDashboard from '@/components/project/ProjectDashboard.vue'; import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
import { RouteConfig } from '@/router'; import { RouteConfig } from '@/router';
@ -37,11 +38,14 @@ describe('NavigationArea', () => {
const router = new Router({ const router = new Router({
mode: 'history', mode: 'history',
routes: [{ routes: [{
path: '/', path: '/onboarding-tour',
name: RouteConfig.OnboardingTour.name, name: RouteConfig.OnboardingTour.name,
component: OnboardingTourArea, component: OnboardingTourArea,
}], }],
}); });
router.push('/onboarding-tour');
const wrapper = shallowMount(NavigationArea, { const wrapper = shallowMount(NavigationArea, {
store, store,
localVue, localVue,
@ -67,6 +71,8 @@ describe('NavigationArea', () => {
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);
router.push('/');
const wrapper = shallowMount(NavigationArea, { const wrapper = shallowMount(NavigationArea, {
store, store,
localVue, localVue,

View File

@ -1,76 +0,0 @@
// 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 if paywall is enabled', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isPaywallEnabled: true,
},
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly if paywall is disabled', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isPaywallEnabled: false,
},
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly if add payment step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isAddPaymentStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(1);
expect(wrapper.findAll('.completed-font-color').length).toBe(1);
});
it('renders correctly if create project step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isCreateProjectStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(3);
expect(wrapper.findAll('.completed-font-color').length).toBe(2);
});
it('renders correctly if create api key step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isCreateApiKeyStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(5);
expect(wrapper.findAll('.completed-font-color').length).toBe(3);
});
it('renders correctly if upload data step is completed', (): void => {
const wrapper = mount(ProgressBar, {
propsData: {
isUploadDataStep: true,
isPaywallEnabled: true,
},
});
expect(wrapper.findAll('.completed-step').length).toBe(7);
expect(wrapper.findAll('.completed-font-color').length).toBe(4);
});
});

View File

@ -1,61 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProgressBar.vue renders correctly if paywall is disabled 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 titles-area-no-paywall">
<!----> <span class="progress-bar-container__titles-area__title name-your-project-title title-no-paywall">
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>
`;
exports[`ProgressBar.vue renders correctly if paywall is enabled 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 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">
Add Payment
</span> <span class="progress-bar-container__titles-area__title name-your-project-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

@ -9,13 +9,7 @@ exports[`OnboardingTourArea.vue renders correctly 1`] = `
<closeimage-stub class="tour-area__info-bar__close-img"></closeimage-stub> <closeimage-stub class="tour-area__info-bar__close-img"></closeimage-stub>
</div> </div>
<div class="tour-area__content"> <div class="tour-area__content">
<progressbar-stub ispaywallenabled="true"></progressbar-stub> <router-view-stub name="default"></router-view-stub>
<overviewstep-stub></overviewstep-stub>
<!---->
<!---->
<!---->
<!---->
<!---->
<!----> <!---->
</div> </div>
</div> </div>

View File

@ -6,6 +6,7 @@ import Vuex from 'vuex';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue'; import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import { PaymentsHttpApi } from '@/api/payments'; import { PaymentsHttpApi } from '@/api/payments';
import { router } from '@/router';
import { makePaymentsModule } from '@/store/modules/payments'; import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects'; import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
@ -27,6 +28,7 @@ describe('OnboardingTourArea.vue', () => {
const wrapper = shallowMount(OnboardingTourArea, { const wrapper = shallowMount(OnboardingTourArea, {
store, store,
localVue, localVue,
router,
}); });
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();

View File

@ -1,61 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import sinon from 'sinon';
import Vuex from 'vuex';
import CreateApiKeyStep from '@/components/onboardingTour/steps/CreateApiKeyStep.vue';
import { makeApiKeysModule } from '@/store/modules/apiKeys';
import { appStateModule } from '@/store/modules/appState';
import { makeProjectsModule } from '@/store/modules/projects';
import { ApiKeysPage } from '@/types/apiKeys';
import { Project } from '@/types/projects';
import { NotificatorPlugin } from '@/utils/plugins/notificator';
import { SegmentioPlugin } from '@/utils/plugins/segment';
import { createLocalVue, mount } from '@vue/test-utils';
import { ApiKeysMock } from '../../mock/api/apiKeys';
import { ProjectsApiMock } from '../../mock/api/projects';
const localVue = createLocalVue();
const notificationPlugin = new NotificatorPlugin();
const segmentioPlugin = new SegmentioPlugin();
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const apiKeysApi = new ApiKeysMock();
const apiKeysModule = makeApiKeysModule(apiKeysApi);
apiKeysApi.setMockApiKeysPage(new ApiKeysPage());
const project = new Project('id', 'projectName', 'projectDescription', 'test', 'testOwnerId', true);
projectsApi.setMockProjects([project]);
localVue.use(Vuex);
localVue.use(notificationPlugin);
localVue.use(segmentioPlugin);
const store = new Vuex.Store({ modules: { projectsModule, apiKeysModule, appStateModule }});
describe('CreateApiKeyStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = mount(CreateApiKeyStep, {
store,
localVue,
});
expect(wrapper.findAll('.disabled').length).toBe(1);
expect(wrapper).toMatchSnapshot();
});
it('create api key works correctly correctly', async (): Promise<void> => {
const wrapper = mount(CreateApiKeyStep, {
store,
localVue,
});
await wrapper.vm.setApiKeyName('testName');
await wrapper.vm.createApiKey();
expect(wrapper.findAll('.disabled').length).toBe(0);
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,54 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import sinon from 'sinon';
import Vuex from 'vuex';
import CreateProjectStep from '@/components/onboardingTour/steps/CreateProjectStep.vue';
import { PaymentsHttpApi } from '@/api/payments';
import { makePaymentsModule } from '@/store/modules/payments';
import { makeProjectsModule } from '@/store/modules/projects';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import { ProjectsApiMock } from '../../mock/api/projects';
const localVue = createLocalVue();
localVue.use(Vuex);
const paymentsApi = new PaymentsHttpApi();
const paymentsModule = makePaymentsModule(paymentsApi);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const store = new Vuex.Store({ modules: { paymentsModule, projectsModule }});
describe('CreateProjectStep.vue', () => {
it('renders correctly', (): void => {
const wrapper = shallowMount(CreateProjectStep, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('click works correctly', async (): Promise<void> => {
const clickSpy = sinon.spy();
const wrapper = mount(CreateProjectStep, {
store,
localVue,
});
wrapper.vm.createProjectClick = clickSpy;
expect(wrapper.findAll('.disabled').length).toBe(1);
await wrapper.vm.setProjectName('test');
expect(wrapper.findAll('.disabled').length).toBe(0);
await wrapper.find('.create-project-button').trigger('click');
expect(clickSpy.callCount).toBe(1);
});
});

View File

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

View File

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

View File

@ -1,85 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateApiKeyStep.vue create api key works correctly correctly 1`] = `
<div class="create-api-key-step">
<h1 class="create-api-key-step__title">Create an API Key</h1>
<p class="create-api-key-step__sub-title">
API keys provide access to the project for creating buckets and uploading objects through the command line
interface. This will be your first API key, and you can always create more keys later on.
</p>
<div class="create-api-key-step__container">
<div class="create-api-key-step__container__title-area">
<h2 class="create-api-key-step__container__title-area__title">Create API Key</h2>
<!---->
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">API Key Name</h3>
<h3 class="label-container__main__label add-label"></h3>
<!---->
</div>
<!---->
</div>
<!---->
<!----> <input id="API Key Name" placeholder="Enter API Key Name (i.e. Dans Key)" type="text" class="headered-input" style="height: 48px;">
</div>
<div class="create-api-key-step__container__copy-key-area">
<div class="create-api-key-step__container__copy-key-area__header"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" class="info-svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 -7.86805e-07C4.02944 -1.22135e-06 1.22135e-06 4.02944 7.86805e-07 9C3.52265e-07 13.9706 4.02944 18 9 18C13.9706 18 18 13.9706 18 9C18 4.02944 13.9706 -3.52265e-07 9 -7.86805e-07ZM10 13C10 13.5523 9.55229 14 9 14C8.44772 14 8 13.5523 8 13C8 12.4477 8.44772 12 9 12C9.55229 12 10 12.4477 10 13ZM10 9C10 9.55228 9.55228 10 9 10C8.44771 10 8 9.55228 8 9L8 5C8 4.44771 8.44772 4 9 4C9.55229 4 10 4.44772 10 5L10 9Z" fill="white"></path>
</svg> <span class="create-api-key-step__container__copy-key-area__header__title">
API Keys only appear here once. Copy and paste this key to your preferred method of storing secrets.
</span></div>
<div class="create-api-key-step__container__copy-key-area__key-container"><span class="create-api-key-step__container__copy-key-area__key-container__key">testKey</span>
<div class="create-api-key-step__container__copy-key-area__key-container__copy-button">
<div class="container blue-white" style="width: 81px; height: 40px;"><span class="label">Copy</span></div>
</div>
</div>
</div>
<p class="create-api-key-step__container__info">
We dont record your API Keys, which are only displayed once when generated. If you loose this
key, it cannot be recovered but you can always create new API Keys when needed.
</p>
<!---->
</div>
<div class="done-button container" style="width: 156px; height: 48px;"><span class="label">Done</span></div>
<!---->
</div>
`;
exports[`CreateApiKeyStep.vue renders correctly 1`] = `
<div class="create-api-key-step">
<h1 class="create-api-key-step__title">Create an API Key</h1>
<p class="create-api-key-step__sub-title">
API keys provide access to the project for creating buckets and uploading objects through the command line
interface. This will be your first API key, and you can always create more keys later on.
</p>
<div class="create-api-key-step__container">
<div class="create-api-key-step__container__title-area">
<h2 class="create-api-key-step__container__title-area__title">Create API Key</h2>
<!---->
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">API Key Name</h3>
<h3 class="label-container__main__label add-label"></h3>
<!---->
</div>
<!---->
</div>
<!---->
<!----> <input id="API Key Name" placeholder="Enter API Key Name (i.e. Dans Key)" type="text" class="headered-input" style="height: 48px;">
</div>
<div class="create-api-key-step__container__create-key-area">
<div class="generate-button container blue-white" style="width: 100%; height: 40px;"><span class="label">Generate API Key</span></div>
</div>
<!---->
<!---->
</div>
<div class="done-button container disabled" style="width: 156px; height: 48px;"><span class="label">Done</span></div>
<!---->
</div>
`;

View File

@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateProjectStep.vue renders correctly 1`] = `
<div class="new-project-step">
<h1 class="new-project-step__title">Name Your Project</h1>
<p class="new-project-step__sub-title">
Projects are where buckets are created for storing data. Within a Project, usage is tracked at the bucket
level and aggregated for billing.
</p>
<div class="new-project-step__container">
<div class="new-project-step__container__title-area">
<h2 class="new-project-step__container__title-area__title">Project Details</h2>
<!---->
</div>
<headeredinput-stub label="Project Name" placeholder="Enter Project Name" height="48px" width="100%" error="" maxsymbols="20" initvalue="" additionallabel="Up To 20 Characters" currentlimit="0" islimitshown="true" class="full-input project-name-input"></headeredinput-stub>
<headeredinput-stub label="Description" placeholder="Enter Project Description" height="60px" width="calc(100% - 42px)" error="" maxsymbols="100" initvalue="" additionallabel="Optional" currentlimit="0" islimitshown="true" ismultiline="true" class="full-input"></headeredinput-stub>
<!---->
</div>
<vbutton-stub label="Create Project" width="156px" height="48px" isdisabled="true" onpress="function () { [native code] }" class="create-project-button"></vbutton-stub>
</div>
`;

View File

@ -1,19 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SaveApiKeyModal.vue renders correctly 1`] = `
<div class="save-api-modal">
<div class="save-api-modal__container"><svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 24C1.15877e-06 10.7452 10.7452 -1.15877e-06 24 0C37.2548 2.09815e-06 48 10.7452 48 24C48 37.2548 37.2548 48 24 48C10.7452 48 -2.09815e-06 37.2548 0 24ZM26.6669 24.0003C26.6669 25.473 25.473 26.6669 24.0003 26.6669C22.5275 26.6669 21.3336 25.473 21.3336 24.0003L21.3336 13.3336C21.3336 11.8609 22.5275 10.6669 24.0003 10.6669C25.473 10.6669 26.6669 11.8609 26.6669 13.3336L26.6669 24.0003ZM24.0003 37.3335C25.473 37.3335 26.6669 36.1396 26.6669 34.6668C26.6669 33.194 25.473 32.0001 24.0003 32.0001C22.5275 32.0001 21.3336 33.194 21.3336 34.6668C21.3336 36.1396 22.5275 37.3335 24.0003 37.3335Z" fill="#F4B000"></path>
</svg>
<h1 class="save-api-modal__container__title">Is Your API Key Saved?</h1>
<p class="save-api-modal__container__message">
API Keys are only displayed once when generated. If you havent saved your key, go back to copy and
paste the API key to your preferred method of storing secrets (i.e. TextEdit, Keybase, etc.)
</p>
<div class="save-api-modal__container__buttons-area">
<div class="back-button container blue-white" style="width: 186px; height: 45px;"><span class="label">Go Back</span></div>
<div class="container" style="width: 186px; height: 45px;"><span class="label">Yes, it's Saved!</span></div>
</div>
</div>
</div>
`;

View File

@ -1,59 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UploadDataStep.vue renders correctly 1`] = `
<div class="upload-data-area">
<div class="upload-data-area__container">
<h1 class="upload-data-area__container__title">Upload Data</h1>
<p class="upload-data-area__container__sub-title">
From here, youll set up Tardigrade to store data for your project using our S3 Gateway, Uplink CLI, or
select from our growing library of connectors to build apps on Tardigrade.
</p>
<div class="upload-data-area__container__docs-area">
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Migrate Data from your Existing AWS buckets
</h2> <img src="@/../static/images/onboardingTour/s3.png" alt="s3 gateway image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
S3 Gateway
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Make the switch with Tardigrades S3 Gateway.
</p> <a href="https://documentation.tardigrade.io/api-reference/s3-gateway" target="_blank" rel="noopener noreferrer" class="upload-data-area__container__docs-area__option__link">
S3 Gateway Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option uplink-option">
<h2 class="upload-data-area__container__docs-area__option__title">
Upload Data from Your Local Environment
</h2> <img src="@/../static/images/onboardingTour/uplinkcli.png" alt="uplink cli image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
Uplink CLI
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Start uploading data from the command line.
</p> <a href="https://documentation.tardigrade.io/getting-started/uploading-your-first-object/set-up-uplink-cli" target="_blank" rel="noopener noreferrer" class="upload-data-area__container__docs-area__option__link">
Uplink CLI Docs
</a>
</div>
<div class="upload-data-area__container__docs-area__option">
<h2 class="upload-data-area__container__docs-area__option__title">
Use Tardigrade for your apps storage layer
</h2> <img src="@/../static/images/onboardingTour/connectors.png" alt="connectors image">
<h3 class="upload-data-area__container__docs-area__option__sub-title">
App Connectors
</h3>
<p class="upload-data-area__container__docs-area__option__info">
Integrate Tardigrade into your existing stack.
</p> <a href="https://tardigrade.io/connectors/" target="_blank" rel="noopener noreferrer" class="upload-data-area__container__docs-area__option__link">
App Connectors
</a>
</div>
</div>
</div>
<div class="go-to-button container" style="width: 276px; height: 40px;"><span class="label">Go to Dashboard</span></div> <span class="upload-data-area__support-info">
Need help?
<a href="https://support.tardigrade.io/hc/en-us" target="_blank" rel="noopener noreferrer" class="upload-data-area__support-info__link">
Contact support
</a></span>
</div>
`;