web/satellite: onboarding tour: api keys and upload data steps
Change-Id: I8ffa6d688a22c1568495a7e0e176096cadcd6eaa
This commit is contained in:
parent
7a83473f00
commit
2284008b8c
@ -61,7 +61,6 @@ import { RouteConfig } from '@/router';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { DateRange } from '@/types/payments';
|
||||
import { Project } from '@/types/projects';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { ProjectOwning } from '@/utils/projectOwning';
|
||||
@ -95,7 +94,7 @@ export default class BillingArea extends Vue {
|
||||
* Fetches billing history and project limits.
|
||||
*/
|
||||
public async beforeMount(): Promise<void> {
|
||||
if (!this.$store.getters.selectedProject.id) {
|
||||
if (this.noProjectOrApiKeys) {
|
||||
await this.$router.push(RouteConfig.OnboardingTour.path);
|
||||
|
||||
return;
|
||||
@ -284,6 +283,13 @@ export default class BillingArea extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if user has no project nor api keys.
|
||||
*/
|
||||
private get noProjectOrApiKeys(): boolean {
|
||||
return !this.$store.getters.selectedProject.id || this.$store.state.apiKeysModule.page.apiKeys.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes buttons styling depends on selected status.
|
||||
* @param event holds click event
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<template>
|
||||
<div class="api-keys-area">
|
||||
<h1 class="api-keys-area__title" v-if="isTitleShown">API Keys</h1>
|
||||
<h1 class="api-keys-area__title" v-if="!isEmpty">API Keys</h1>
|
||||
<div class="api-keys-area__container">
|
||||
<ApiKeysCreationPopup
|
||||
@closePopup="closeNewApiKeyPopup"
|
||||
@ -93,7 +93,6 @@
|
||||
<EmptySearchResultIcon class="empty-search-result-area__image"/>
|
||||
</div>
|
||||
<NoApiKeysArea
|
||||
:class="{ collapsed: isBannerShown }"
|
||||
:on-button-click="onCreateApiKeyClick"
|
||||
v-if="isEmptyStateShown"
|
||||
/>
|
||||
@ -249,7 +248,7 @@ export default class ApiKeysArea extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(FETCH, this.FIRST_PAGE);
|
||||
} catch (error) {
|
||||
await this.notifyFetchError(error);
|
||||
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
|
||||
}
|
||||
|
||||
this.isDeleteClicked = false;
|
||||
@ -291,20 +290,6 @@ export default class ApiKeysArea extends Vue {
|
||||
return this.$store.getters.apiKeys.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if bonus banner should appear if no credit cards is attached to account.
|
||||
*/
|
||||
public get isBannerShown(): boolean {
|
||||
return this.$store.state.paymentsModule.creditCards.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if "Account" title is shown.
|
||||
*/
|
||||
public get isTitleShown(): boolean {
|
||||
return !(this.isBannerShown && this.isEmpty);
|
||||
}
|
||||
|
||||
public get hasSearchQuery(): boolean {
|
||||
return this.$store.state.apiKeysModule.cursor.search;
|
||||
}
|
||||
@ -349,7 +334,7 @@ export default class ApiKeysArea extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(FETCH, index);
|
||||
} catch (error) {
|
||||
await this.notifyFetchError(error);
|
||||
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,7 +349,7 @@ export default class ApiKeysArea extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(FETCH, this.FIRST_PAGE);
|
||||
} catch (error) {
|
||||
await this.notifyFetchError(error);
|
||||
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
|
||||
}
|
||||
|
||||
if (this.totalPageCount > 1) {
|
||||
@ -381,21 +366,13 @@ export default class ApiKeysArea extends Vue {
|
||||
try {
|
||||
await this.$store.dispatch(FETCH, this.FIRST_PAGE);
|
||||
} catch (error) {
|
||||
await this.notifyFetchError(error);
|
||||
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
|
||||
}
|
||||
|
||||
if (this.totalPageCount > 1) {
|
||||
this.$refs.pagination.resetPageIndex();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires UI notification with message.
|
||||
* @param error
|
||||
*/
|
||||
public async notifyFetchError(error: Error): Promise<void> {
|
||||
await this.$notify.error(`Unable to fetch API keys. ${error.message}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -66,7 +66,7 @@ export default class VButton extends Vue {
|
||||
}
|
||||
|
||||
.blue-white {
|
||||
background-color: transparent !important;
|
||||
background-color: #fff !important;
|
||||
border: 2px solid #2683ff !important;
|
||||
|
||||
.label {
|
||||
|
@ -34,11 +34,11 @@ import { ProjectOwning } from '@/utils/projectOwning';
|
||||
export default class NewProjectArea extends Vue {
|
||||
// TODO: temporary solution. Remove when user will be able to create more then one project
|
||||
/**
|
||||
* Life cycle hook after initial render.
|
||||
* Life cycle hook before initial render.
|
||||
* Toggles new project button visibility depending on user having his own project or payment method.
|
||||
*/
|
||||
public beforeMount(): void {
|
||||
if (this.userHasOwnProject || !this.$store.getters.canUserCreateFirstProject || this.isOnboardingTour) {
|
||||
if (this.userHasOwnProject || !this.$store.getters.canUserCreateFirstProject) {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.HIDE_CREATE_PROJECT_BUTTON);
|
||||
|
||||
return;
|
||||
@ -47,6 +47,16 @@ export default class NewProjectArea extends Vue {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.SHOW_CREATE_PROJECT_BUTTON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Life cycle hook after initial render.
|
||||
* Hides new project button visibility if user is on onboarding tour.
|
||||
*/
|
||||
public mounted(): void {
|
||||
if (this.isOnboardingTour) {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.HIDE_CREATE_PROJECT_BUTTON);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens new project creation popup.
|
||||
*/
|
||||
|
@ -4,10 +4,15 @@
|
||||
<template>
|
||||
<div class="project-selection-container" id="projectDropdownButton">
|
||||
<p class="project-selection-container__no-projects-text" v-if="!hasProjects">You have no projects</p>
|
||||
<div class="project-selection-toggle-container" @click="toggleSelection" v-if="hasProjects">
|
||||
<p class="project-selection-toggle-container__common">Project:</p>
|
||||
<div
|
||||
class="project-selection-toggle-container"
|
||||
:class="{ default: isOnboardingTour }"
|
||||
@click="toggleSelection"
|
||||
v-if="hasProjects"
|
||||
>
|
||||
<p class="project-selection-toggle-container__common" :class="{ default: isOnboardingTour }">Project:</p>
|
||||
<h1 class="project-selection-toggle-container__name">{{name}}</h1>
|
||||
<div class="project-selection-toggle-container__expander-area">
|
||||
<div class="project-selection-toggle-container__expander-area" v-if="!isOnboardingTour">
|
||||
<ExpandIcon
|
||||
v-if="!isDropdownShown"
|
||||
alt="Arrow down (expand)"
|
||||
@ -28,6 +33,7 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
import ExpandIcon from '@/../static/images/common/BlueExpand.svg';
|
||||
import HideIcon from '@/../static/images/common/BlueHide.svg';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { Project } from '@/types/projects';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
@ -48,7 +54,7 @@ export default class ProjectSelectionArea extends Vue {
|
||||
* Fetches projects related information and than toggles selection popup.
|
||||
*/
|
||||
public async toggleSelection(): Promise<void> {
|
||||
if (this.isLoading) return;
|
||||
if (this.isLoading || this.isOnboardingTour) return;
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
@ -86,6 +92,13 @@ export default class ProjectSelectionArea extends Vue {
|
||||
public get hasProjects(): boolean {
|
||||
return !!this.$store.state.projectsModule.projects.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if current route is onboarding tour.
|
||||
*/
|
||||
public get isOnboardingTour(): boolean {
|
||||
return this.$route.name === RouteConfig.OnboardingTour.name;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -141,6 +154,10 @@ export default class ProjectSelectionArea extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
.default {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1280px) {
|
||||
|
||||
.project-selection-container {
|
||||
|
@ -5,6 +5,8 @@
|
||||
<div class="tour-area">
|
||||
<ProgressBar
|
||||
:is-create-project-step="isCreateProjectState"
|
||||
:is-create-api-key-step="isCreatApiKeyState"
|
||||
:is-upload-data-step="isUploadDataState"
|
||||
/>
|
||||
<OverviewStep
|
||||
v-if="isDefaultState"
|
||||
@ -16,7 +18,13 @@
|
||||
/>
|
||||
<CreateProjectStep
|
||||
v-if="isCreateProjectState"
|
||||
@setApiKeyState="setCreateApiKeyState"
|
||||
/>
|
||||
<CreateApiKeyStep
|
||||
v-if="isCreatApiKeyState"
|
||||
@setUploadDataState="setUploadDataState"
|
||||
/>
|
||||
<UploadDataStep v-if="isUploadDataState"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -25,8 +33,10 @@ 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 UploadDataStep from '@/components/onboardingTour/steps/UploadDataStep.vue';
|
||||
|
||||
import CheckedImage from '@/../static/images/common/checked.svg';
|
||||
|
||||
@ -35,6 +45,8 @@ import { TourState } from '@/utils/constants/onboardingTourEnums';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
UploadDataStep,
|
||||
CreateApiKeyStep,
|
||||
CreateProjectStep,
|
||||
AddPaymentStep,
|
||||
ProgressBar,
|
||||
@ -51,7 +63,7 @@ export default class OnboardingTourArea extends Vue {
|
||||
* Sets area to needed state.
|
||||
*/
|
||||
public mounted(): void {
|
||||
if (this.$store.state.projectsModule.projects.length > 0) {
|
||||
if (this.userHasProject && this.userHasApiKeys) {
|
||||
try {
|
||||
this.$router.push(RouteConfig.ProjectDashboard.path);
|
||||
} catch (error) {
|
||||
@ -61,8 +73,16 @@ export default class OnboardingTourArea extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.userHasProject && !this.userHasApiKeys) {
|
||||
this.setCreateApiKeyState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$store.state.paymentsModule.creditCards.length > 0) {
|
||||
this.setCreateProjectState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$store.getters.isTransactionProcessing || this.$store.getters.isTransactionCompleted) {
|
||||
@ -91,6 +111,20 @@ export default class OnboardingTourArea extends Vue {
|
||||
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.
|
||||
*/
|
||||
@ -104,6 +138,34 @@ export default class OnboardingTourArea extends Vue {
|
||||
public setCreateProjectState(): void {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if user has at least one project.
|
||||
*/
|
||||
private get userHasProject(): boolean {
|
||||
return this.$store.state.projectsModule.projects.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if user has at least one API key.
|
||||
*/
|
||||
private get userHasApiKeys(): boolean {
|
||||
return this.$store.state.apiKeysModule.page.apiKeys.length > 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -4,24 +4,52 @@
|
||||
<template>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar-container__progress-area">
|
||||
<div class="progress-bar-container__progress-area__circle" :class="{ 'completed-step': isCreateProjectStep }">
|
||||
<div
|
||||
class="progress-bar-container__progress-area__circle"
|
||||
:class="{ 'completed-step': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
|
||||
>
|
||||
<CheckedImage/>
|
||||
</div>
|
||||
<div class="progress-bar-container__progress-area__bar"/>
|
||||
<div class="progress-bar-container__progress-area__circle">
|
||||
<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"/>
|
||||
<div class="progress-bar-container__progress-area__circle">
|
||||
<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">
|
||||
<span class="progress-bar-container__titles-area__title" :class="{ 'completed-font-color': isCreateProjectStep }">
|
||||
<span
|
||||
class="progress-bar-container__titles-area__title"
|
||||
:class="{ 'completed-font-color': isCreateProjectStep || isCreateApiKeyStep || isUploadDataStep }"
|
||||
>
|
||||
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>
|
||||
<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>
|
||||
@ -40,6 +68,10 @@ import CheckedImage from '@/../static/images/common/checked.svg';
|
||||
export default class ProgressBar extends Vue {
|
||||
@Prop({ default: false })
|
||||
public readonly isCreateProjectStep: boolean;
|
||||
@Prop({ default: false })
|
||||
public readonly isCreateApiKeyStep: boolean;
|
||||
@Prop({ default: false })
|
||||
public readonly isUploadDataStep: boolean;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -0,0 +1,362 @@
|
||||
// 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. Dan’s 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 don’t 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"
|
||||
/>
|
||||
</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 InfoImage from '@/../static/images/onboardingTour/info.svg';
|
||||
|
||||
import { ApiKey } from '@/types/apiKeys';
|
||||
import { API_KEYS_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,
|
||||
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 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets tour state to last step.
|
||||
*/
|
||||
public onDoneClick(): void {
|
||||
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;
|
||||
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>
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="new-project-step">
|
||||
<div class="new-project-step" @keyup.enter="createProjectClick">
|
||||
<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
|
||||
@ -57,7 +57,7 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
import HeaderedInput from '@/components/common/HeaderedInput.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import { RouteConfig } from '@/router';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { CreateProjectModel, Project } from '@/types/projects';
|
||||
@ -133,6 +133,12 @@ export default class CreateProjectStep extends Vue {
|
||||
await this.$notify.error(`Unable to get project members. ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
|
||||
} catch (error) {
|
||||
await this.$notify.error(error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BILLING_HISTORY);
|
||||
} catch (error) {
|
||||
@ -159,18 +165,23 @@ export default class CreateProjectStep extends Vue {
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
// TODO: rework after adding third step of onboarding tour
|
||||
await this.$router.push(RouteConfig.ApiKeys.path);
|
||||
this.$emit('setApiKeyState');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input value to satisfy project name rules.
|
||||
*/
|
||||
private isProjectNameValid(): boolean {
|
||||
this.projectName.trim();
|
||||
this.projectName = this.projectName.trim();
|
||||
|
||||
if (!this.projectName) {
|
||||
this.nameError = 'Project name can\'t be empty!';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!anyCharactersButSlash(this.projectName)) {
|
||||
this.nameError = 'Name for project is invalid!';
|
||||
this.nameError = 'Project name can\'t have forward slash';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,242 @@
|
||||
// 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, you’ll 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 Tardigrade’s S3 Gateway.
|
||||
</p>
|
||||
<a
|
||||
class="upload-data-area__container__docs-area__option__link"
|
||||
href="https://documentation.tardigrade.io/api-reference/s3-gateway"
|
||||
target="_blank"
|
||||
>
|
||||
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/api-reference/uplink-cli"
|
||||
target="_blank"
|
||||
>
|
||||
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 app’s 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://documentation.tardigrade.io/concepts/connectors"
|
||||
target="_blank"
|
||||
>
|
||||
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">
|
||||
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>
|
@ -205,7 +205,6 @@ export default class HeaderArea extends Vue {
|
||||
private async setProjectState(): Promise<void> {
|
||||
const projects = await this.$store.dispatch(PROJECTS_ACTIONS.FETCH);
|
||||
if (!projects.length) {
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED_EMPTY);
|
||||
await this.$router.push(RouteConfig.OnboardingTour.path);
|
||||
|
||||
return;
|
||||
|
@ -96,10 +96,10 @@ export function makeApiKeysModule(api: ApiKeysApi): StoreModule<ApiKeysState> {
|
||||
const projectId = rootGetters.selectedProject.id;
|
||||
commit(SET_PAGE_NUMBER, pageNumber);
|
||||
|
||||
const apiKeys = await api.get(projectId, state.cursor);
|
||||
commit(SET_PAGE, apiKeys);
|
||||
const apiKeysPage: ApiKeysPage = await api.get(projectId, state.cursor);
|
||||
commit(SET_PAGE, apiKeysPage);
|
||||
|
||||
return apiKeys;
|
||||
return apiKeysPage;
|
||||
},
|
||||
createApiKey: async function ({commit, rootGetters}: any, name: string): Promise<ApiKey> {
|
||||
const projectId = rootGetters.selectedProject.id;
|
||||
|
@ -4,6 +4,5 @@
|
||||
export enum AppState {
|
||||
LOADING = 1,
|
||||
LOADED,
|
||||
LOADED_EMPTY,
|
||||
ERROR,
|
||||
}
|
||||
|
@ -19,3 +19,8 @@ export enum AddingStorjState {
|
||||
VERIFYING,
|
||||
VERIFIED,
|
||||
}
|
||||
|
||||
export enum AddingApiKeyState {
|
||||
CREATE = 1,
|
||||
COPY,
|
||||
}
|
||||
|
@ -45,7 +45,9 @@ import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
import { ApiKeysPage } from '@/types/apiKeys';
|
||||
import { Project } from '@/types/projects';
|
||||
import { User } from '@/types/users';
|
||||
import { Size } from '@/utils/bytesSize';
|
||||
import {
|
||||
API_KEYS_ACTIONS,
|
||||
@ -82,9 +84,11 @@ export default class DashboardArea extends Vue {
|
||||
* Pre fetches user`s and project information.
|
||||
*/
|
||||
public async mounted(): Promise<void> {
|
||||
let user: User;
|
||||
|
||||
// TODO: combine all project related requests in one
|
||||
try {
|
||||
await this.$store.dispatch(USER_ACTIONS.GET);
|
||||
user = await this.$store.dispatch(USER_ACTIONS.GET);
|
||||
} catch (error) {
|
||||
if (!(error instanceof ErrorUnauthorized)) {
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.ERROR);
|
||||
@ -143,7 +147,7 @@ export default class DashboardArea extends Vue {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
try {
|
||||
await this.$router.push(RouteConfig.OnboardingTour.path);
|
||||
@ -156,7 +160,26 @@ export default class DashboardArea extends Vue {
|
||||
|
||||
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, projects[0].id);
|
||||
|
||||
await this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
|
||||
let apiKeysPage: ApiKeysPage = new ApiKeysPage();
|
||||
|
||||
try {
|
||||
apiKeysPage = await this.$store.dispatch(API_KEYS_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
await this.$notify.error(`Unable to fetch api keys. ${error.message}`);
|
||||
}
|
||||
|
||||
if (projects.length === 1 && projects[0].ownerId === user.id && apiKeysPage.apiKeys.length === 0) {
|
||||
await this.$store.dispatch(APP_STATE_ACTIONS.CHANGE_STATE, AppState.LOADED);
|
||||
|
||||
try {
|
||||
await this.$router.push(RouteConfig.OnboardingTour.path);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
@ -169,12 +192,6 @@ export default class DashboardArea extends Vue {
|
||||
await this.$notify.error(`Unable to fetch project limits. ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(API_KEYS_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
await this.$notify.error(`Unable to fetch api keys. ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch(BUCKET_ACTIONS.FETCH, 1);
|
||||
} catch (error) {
|
||||
|
@ -60,7 +60,7 @@ export default class RegisterArea extends Vue {
|
||||
* Lifecycle hook after initial render.
|
||||
* Sets up variables from route params.
|
||||
*/
|
||||
async mounted(): Promise<void> {
|
||||
public async mounted(): Promise<void> {
|
||||
if (this.$route.query.token) {
|
||||
this.secret = this.$route.query.token.toString();
|
||||
}
|
||||
@ -85,6 +85,16 @@ export default class RegisterArea extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook on component destroy.
|
||||
* Sets view to default state.
|
||||
*/
|
||||
public beforeDestroy(): void {
|
||||
if (this.isRegistrationSuccessful) {
|
||||
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_REGISTRATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if registration successful area shown.
|
||||
*/
|
||||
|
BIN
web/satellite/static/images/onboardingTour/connectors.png
Normal file
BIN
web/satellite/static/images/onboardingTour/connectors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
3
web/satellite/static/images/onboardingTour/info.svg
Normal file
3
web/satellite/static/images/onboardingTour/info.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<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>
|
After Width: | Height: | Size: 602 B |
BIN
web/satellite/static/images/onboardingTour/s3.png
Normal file
BIN
web/satellite/static/images/onboardingTour/s3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 540 B |
BIN
web/satellite/static/images/onboardingTour/uplinkcli.png
Normal file
BIN
web/satellite/static/images/onboardingTour/uplinkcli.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -9,7 +9,7 @@ exports[`ApiKeysArea renders correctly 1`] = `
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<noapikeysarea-stub onbuttonclick="function () { [native code] }" class="collapsed"></noapikeysarea-stub>
|
||||
<noapikeysarea-stub onbuttonclick="function () { [native code] }"></noapikeysarea-stub>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -23,7 +23,7 @@ exports[`ApiKeysArea renders empty screen with add key prompt 1`] = `
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<div class="no-api-keys-area collapsed">
|
||||
<div class="no-api-keys-area">
|
||||
<h1 class="no-api-keys-area__title">Create Your First API Key</h1>
|
||||
<p class="no-api-keys-area__sub-title">API keys give access to the project to create buckets, upload objects</p>
|
||||
<div class="no-api-keys-area__button container" style="width: 180px; height: 48px;"><span class="label">Create API Key</span></div>
|
||||
@ -67,7 +67,7 @@ exports[`ApiKeysArea renders empty search state correctly 1`] = `
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<noapikeysarea-stub onbuttonclick="function () { [native code] }" class="collapsed"></noapikeysarea-stub>
|
||||
<noapikeysarea-stub onbuttonclick="function () { [native code] }"></noapikeysarea-stub>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -18,7 +18,7 @@ export class ApiKeysMock implements ApiKeysApi {
|
||||
}
|
||||
|
||||
create(projectId: string, name: string): Promise<ApiKey> {
|
||||
throw new Error('Method not implemented');
|
||||
return Promise.resolve(new ApiKey('testId', 'testName', 'test', 'testKey'));
|
||||
}
|
||||
|
||||
delete(ids: string[]): Promise<void> {
|
||||
|
@ -11,4 +11,37 @@ describe('ProgressBar.vue', () => {
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly if create project step is completed', (): void => {
|
||||
const wrapper = mount(ProgressBar, {
|
||||
propsData: {
|
||||
isCreateProjectStep: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAll('.completed-step').length).toBe(1);
|
||||
expect(wrapper.findAll('.completed-font-color').length).toBe(1);
|
||||
});
|
||||
|
||||
it('renders correctly if create api key step is completed', (): void => {
|
||||
const wrapper = mount(ProgressBar, {
|
||||
propsData: {
|
||||
isCreateApiKeyStep: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAll('.completed-step').length).toBe(3);
|
||||
expect(wrapper.findAll('.completed-font-color').length).toBe(2);
|
||||
});
|
||||
|
||||
it('renders correctly if upload data step is completed', (): void => {
|
||||
const wrapper = mount(ProgressBar, {
|
||||
propsData: {
|
||||
isUploadDataStep: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.findAll('.completed-step').length).toBe(5);
|
||||
expect(wrapper.findAll('.completed-font-color').length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
@ -17,6 +17,10 @@ exports[`ProgressBar.vue renders correctly 1`] = `
|
||||
</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>
|
||||
</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>
|
||||
`;
|
||||
|
@ -6,5 +6,7 @@ exports[`OnboardingTourArea.vue renders correctly 1`] = `
|
||||
<overviewstep-stub></overviewstep-stub>
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import Vuex from 'vuex';
|
||||
|
||||
import CreateApiKeyStep from '@/components/onboardingTour/steps/CreateApiKeyStep.vue';
|
||||
|
||||
import { makeApiKeysModule } from '@/store/modules/apiKeys';
|
||||
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 }});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
it('done click works correctly correctly', async (): Promise<void> => {
|
||||
const wrapper = mount(CreateApiKeyStep, {
|
||||
store,
|
||||
localVue,
|
||||
});
|
||||
|
||||
await wrapper.find('.done-button').trigger('click');
|
||||
|
||||
expect(wrapper.emitted()).toHaveProperty('setUploadDataState');
|
||||
});
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2020 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import UploadDataStep from '@/components/onboardingTour/steps/UploadDataStep.vue';
|
||||
|
||||
import { router } from '@/router';
|
||||
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();
|
||||
});
|
||||
|
||||
it('button click works correctly', async (): Promise<void> => {
|
||||
const clickSpy = sinon.spy();
|
||||
const wrapper = mount(UploadDataStep, {
|
||||
localVue,
|
||||
router,
|
||||
methods: {
|
||||
onButtonClick: clickSpy,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.find('.go-to-button').trigger('click');
|
||||
|
||||
expect(clickSpy.callCount).toBe(1);
|
||||
});
|
||||
});
|
@ -0,0 +1,77 @@
|
||||
// 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">
|
||||
<!---->
|
||||
<h3 class="label-container__label">API Key Name</h3>
|
||||
<h3 class="label-container__label add-label"></h3>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!----> <input id="API Key Name" placeholder="Enter API Key Name (i.e. Dan’s 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 don’t 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">
|
||||
<!---->
|
||||
<h3 class="label-container__label">API Key Name</h3>
|
||||
<h3 class="label-container__label add-label"></h3>
|
||||
<!---->
|
||||
</div>
|
||||
<!---->
|
||||
<!----> <input id="API Key Name" placeholder="Enter API Key Name (i.e. Dan’s 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>
|
||||
`;
|
@ -0,0 +1,59 @@
|
||||
// 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, you’ll 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 Tardigrade’s S3 Gateway.
|
||||
</p> <a href="https://documentation.tardigrade.io/api-reference/s3-gateway" target="_blank" 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/api-reference/uplink-cli" target="_blank" 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 app’s 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://documentation.tardigrade.io/concepts/connectors" target="_blank" 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" class="upload-data-area__support-info__link">
|
||||
Contact support
|
||||
</a></span>
|
||||
</div>
|
||||
`;
|
Loading…
Reference in New Issue
Block a user