web/satellite: create project popup replaced with separate page

WHAT:
create project popup removed. Separate page implemented instead.

WHY:
to fulfill multiple project state requirements

change-Id: Iab65981aef1d9b0caceca94b1109fa63f8f22891
This commit is contained in:
VitaliiShpital 2020-08-31 14:54:40 +03:00
parent dc48197bd8
commit c1e089b226
28 changed files with 477 additions and 971 deletions

View File

@ -65,6 +65,7 @@ import {PaymentsHistoryItemType} from "@/types/payments";
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
import { PaymentAmountOption, PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { ProjectOwning } from '@/utils/projectOwning';
@ -114,9 +115,11 @@ export default class TokenDepositSelection extends Vue {
* Indicates if dropdown expands top.
*/
public get isExpandingTop(): boolean {
return !this.$store.state.paymentsModule.paymentsHistory.some((item: PaymentsHistoryItem) => {
return item.type === PaymentsHistoryItemType.Transaction || item.type === PaymentsHistoryItemType.DepositBonus;
});
const hasNoTransactionsOrDepositBonuses: boolean =
!this.$store.state.paymentsModule.paymentsHistory.some((item: PaymentsHistoryItem) => item.isTransactionOrDeposit(),
);
return hasNoTransactionsOrDepositBonuses && !this.isOnboardingTour;
}
/**
@ -191,6 +194,13 @@ export default class TokenDepositSelection extends Vue {
private get noCreditCards(): boolean {
return this.$store.state.paymentsModule.creditCards.length === 0;
}
/**
* Indicates if app state is in onboarding tour state.
*/
private get isOnboardingTour(): boolean {
return this.$route.name === RouteConfig.OnboardingTour.name;
}
}
</script>

View File

@ -8,7 +8,6 @@ import { Component, Vue } from 'vue-property-decorator';
import NewProjectArea from '@/components/header/NewProjectArea.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import LogoIcon from '@/../static/images/header/logo.svg';
import NavigationCloseIcon from '@/../static/images/header/navigationClose.svg';
@ -18,7 +17,6 @@ import AccountButton from './AccountButton.vue';
@Component({
components: {
ProjectCreationSuccessPopup,
NewProjectArea,
AccountButton,
NavigationArea,

View File

@ -6,12 +6,11 @@
<div
v-if="isButtonShown && !isOnboardingTour"
class="new-project-button-container"
@click="toggleSelection"
@click="onCreateProjectClick"
id="newProjectButton"
>
<h1 class="new-project-button-container__label">+ Create Project</h1>
</div>
<NewProjectPopup v-if="isPopupShown"/>
</div>
</template>
@ -19,7 +18,6 @@
import { Component, Vue } from 'vue-property-decorator';
import VInfo from '@/components/common/VInfo.vue';
import NewProjectPopup from '@/components/project/NewProjectPopup.vue';
import { RouteConfig } from '@/router';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
@ -29,7 +27,6 @@ import { ProjectOwning } from '@/utils/projectOwning';
@Component({
components: {
VInfo,
NewProjectPopup,
},
})
export default class NewProjectArea extends Vue {
@ -49,17 +46,10 @@ export default class NewProjectArea extends Vue {
}
/**
* Opens new project creation popup.
* Redirects to create project page.
*/
public toggleSelection(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
/**
* Indicates if new project creation popup should be rendered.
*/
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isNewProjectPopupShown;
public onCreateProjectClick(): void {
this.$router.push(RouteConfig.CreateProject.path);
}
/**

View File

@ -21,7 +21,6 @@
<NewProjectArea class="dashboard-header-container__right-area__new-project"/>
<AccountButton class="dashboard-header-container__right-area__account-button"/>
</div>
<ProjectCreationSuccessPopup/>
<NavigationArea
class="adapted-navigation"
v-if="isNavigationVisible"

View File

@ -252,7 +252,7 @@ export default class OnboardingTourArea extends Vue {
&__tardigrade {
position: absolute;
left: 50%;
bottom: 50px;
bottom: 0;
transform: translate(-50%);
}
}

View File

@ -62,13 +62,13 @@ 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 { CreateProjectModel, Project } from '@/types/projects';
import { CreateProjectModel } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { Validator } from '@/utils/validation';
@Component({
components: {
@ -107,9 +107,19 @@ export default class CreateProjectStep extends Vue {
}
this.isLoading = true;
this.projectName = this.projectName.trim();
if (!this.isProjectNameValid()) {
const project = new CreateProjectModel(
this.projectName,
this.description,
this.$store.getters.user.id,
);
try {
project.checkName();
} catch (error) {
this.isLoading = false;
this.nameError = error.message;
return;
}
@ -117,12 +127,11 @@ export default class CreateProjectStep extends Vue {
let createdProjectId: string = '';
try {
const project = await this.createProject();
createdProjectId = project.id;
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: createdProjectId,
});
await this.$notify.success('Project created successfully!');
} catch (error) {
this.isLoading = false;
await this.$notify.error(error.message);
@ -133,85 +142,47 @@ export default class CreateProjectStep extends Vue {
await this.$store.dispatch(PROJECTS_ACTIONS.SELECT, createdProjectId);
try {
await this.$store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
await this.$store.dispatch(PM_ACTIONS.FETCH, 1);
} catch (error) {
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.fetchProjectMembers();
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PAYMENTS_HISTORY);
} catch (error) {
await this.$notify.error(`Unable to get billing history. ${error.message}`);
}
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_BALANCE);
} catch (error) {
await this.$notify.error(`Unable to get account balance. ${error.message}`);
}
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
} catch (error) {
await this.$notify.error(`Unable to get project usage and charges for current rollup. ${error.message}`);
}
try {
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, createdProjectId);
} catch (error) {
await this.$notify.error(`Unable to get project limits. ${error.message}`);
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');
}
/**
* Validates input value to satisfy project name rules.
* Clears project members store and fetches new.
*/
private isProjectNameValid(): boolean {
this.projectName = this.projectName.trim();
if (!this.projectName) {
this.nameError = 'Project name can\'t be empty!';
return false;
}
if (!Validator.anyCharactersButSlash(this.projectName)) {
this.nameError = 'Project name can\'t have forward slash';
return false;
}
if (this.projectName.length > 20) {
this.nameError = 'Name should be less than 21 character!';
return false;
}
return true;
private async fetchProjectMembers(): Promise<void> {
await this.$store.dispatch(PM_ACTIONS.CLEAR);
const fistPage = 1;
await this.$store.dispatch(PM_ACTIONS.FETCH, fistPage);
}
/**
* Makes create project request.
* Clears api keys store.
*/
private async createProject(): Promise<Project> {
const project: CreateProjectModel = {
name: this.projectName,
description: this.description,
ownerId: this.$store.getters.user.id,
};
private clearApiKeys(): void {
this.$store.dispatch(API_KEYS_ACTIONS.CLEAR);
}
return await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
/**
* Clears bucket usage store.
*/
private clearBucketUsage(): void {
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
}
</script>

View File

@ -0,0 +1,274 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-project-area">
<div class="create-project-area__container">
<img src="@/../static/images/project/createProject.png" alt="create project image">
<h2 class="create-project-area__title">Create a Project</h2>
<HeaderedInput
label="Project Name"
additional-label="Up To 20 Characters"
placeholder="Enter Project Name"
class="full-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"
height="60px"
width="calc(100% - 42px)"
is-limit-shown="true"
:current-limit="description.length"
:max-symbols="100"
@setData="setProjectDescription"
/>
<div class="create-project-area__container__button-container">
<VButton
label="Cancel"
width="210px"
height="48px"
:on-press="onCancelClick"
is-white="true"
/>
<VButton
label="Create Project +"
width="210px"
height="48px"
:on-press="onCreateProjectClick"
:is-disabled="!projectName"
/>
</div>
<div class="create-project-area__container__blur" v-if="isLoading">
<img
class="create-project-area__container__blur__loading-image"
src="@/../static/images/account/billing/loading.gif"
alt="loading gif"
>
</div>
</div>
</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 { RouteConfig } from '@/router';
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 { CreateProjectModel } from '@/types/projects';
import {
APP_STATE_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { MetaUtils } from '@/utils/meta';
import { ProjectOwning } from '@/utils/projectOwning';
@Component({
components: {
HeaderedInput,
VButton,
},
})
export default class NewProjectPopup extends Vue {
private description: string = '';
private createdProjectId: string = '';
private isLoading: boolean = false;
public projectName: string = '';
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;
}
/**
* Redirects to previous route.
*/
public onCancelClick(): void {
const PREVIOUS_ROUTE_NUMBER = -1;
this.$router.go(PREVIOUS_ROUTE_NUMBER);
}
/**
* Creates project and refreshes store.
*/
public async onCreateProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
this.projectName = this.projectName.trim();
const project = new CreateProjectModel(
this.projectName,
this.description,
this.$store.getters.user.id,
);
try {
project.checkName();
} catch (error) {
this.isLoading = false;
this.nameError = error.message;
return;
}
try {
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
this.createdProjectId = createdProject.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: this.createdProjectId,
});
} catch (error) {
this.isLoading = false;
await this.$notify.error(error.message);
return;
}
this.selectCreatedProject();
try {
await this.fetchProjectMembers();
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PROJECTS_ACTIONS.GET_LIMITS, this.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;
await this.$router.push(RouteConfig.ProjectDashboard.path);
}
/**
* Selects just created project.
*/
private selectCreatedProject(): void {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.createdProjectId);
const defaultProjectLimit: number = parseInt(MetaUtils.getMetaContent('default-project-limit'));
if (new ProjectOwning(this.$store).usersProjectsCount() >= defaultProjectLimit) {
this.$store.dispatch(APP_STATE_ACTIONS.HIDE_CREATE_PROJECT_BUTTON);
}
}
/**
* 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">
.full-input {
width: 100%;
}
.create-project-area {
display: flex;
align-items: center;
justify-content: center;
width: calc(100% - 40px);
padding: 100px 20px 70px 20px;
font-family: 'font_regular', sans-serif;
&__container {
width: 440px;
background-color: #fff;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
padding: 70px 50px 55px 50px;
position: relative;
&__title {
font-size: 28px;
line-height: 34px;
color: #384b65;
font-family: 'font_bold', sans-serif;
}
&__button-container {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30px;
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgba(229, 229, 229, 0.2);
border-radius: 8px;
z-index: 100;
&__loading-image {
width: 25px;
height: 25px;
position: absolute;
right: 40px;
top: 40px;
}
}
}
}
</style>

View File

@ -1,391 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div v-if="isPopupShown" class="new-project-popup-container" @keyup.enter="createProjectClick" @keyup.esc="onCloseClick">
<div class="new-project-popup" id="newProjectPopup" >
<img class="new-project-popup__image" src="@/../static/images/project/createProject.jpg" alt="create project image">
<div class="new-project-popup__form-container">
<div class="new-project-popup__form-container__success-title-area">
<SuccessIcon/>
<p class="new-project-popup__form-container__success-title-area__title">Payment Method Added</p>
</div>
<h2 class="new-project-popup__form-container__main-title">Next, lets create a project.</h2>
<HeaderedInput
label="Project Name"
additional-label="Up To 20 Characters"
placeholder="Enter Project Name"
class="full-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"
height="60px"
width="calc(100% - 42px)"
is-limit-shown="true"
:current-limit="description.length"
:max-symbols="100"
@setData="setProjectDescription"
/>
<div class="new-project-popup__form-container__button-container">
<VButton
label="Back to Billing"
width="205px"
height="48px"
:on-press="onCloseClick"
is-white="true"
/>
<VButton
label="Next"
width="205px"
height="48px"
:on-press="createProjectClick"
/>
</div>
</div>
<div class="new-project-popup__close-cross-container" @click="onCloseClick">
<CloseCrossIcon/>
</div>
</div>
</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 CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import SuccessIcon from '@/../static/images/project/success.svg';
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 { CreateProjectModel, Project } from '@/types/projects';
import {
APP_STATE_ACTIONS,
PM_ACTIONS,
} from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
import { MetaUtils } from '@/utils/meta';
import { ProjectOwning } from '@/utils/projectOwning';
@Component({
components: {
HeaderedInput,
VButton,
CloseCrossIcon,
SuccessIcon,
},
})
export default class NewProjectPopup extends Vue {
private projectName: string = '';
private description: string = '';
private createdProjectId: string = '';
private isLoading: boolean = false;
public nameError: string = '';
/**
* Indicates if popup is shown.
*/
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isNewProjectPopupShown;
}
/**
* 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;
}
/**
* Closes popup.
*/
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
}
/**
* Creates project and refreshes store.
*/
public async createProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
if (!this.validateProjectName()) {
this.isLoading = false;
return;
}
try {
const project = await this.createProject();
this.createdProjectId = project.id;
this.$segment.track(SegmentEvent.PROJECT_CREATED, {
project_id: this.createdProjectId,
});
} catch (error) {
this.isLoading = false;
await this.$notify.error(error.message);
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
return;
}
this.selectCreatedProject();
try {
await this.fetchProjectMembers();
} catch (error) {
await this.$notify.error(error.message);
}
try {
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, this.createdProjectId);
} catch (error) {
await this.$notify.error(error.message);
}
this.clearApiKeys();
this.clearBucketUsage();
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
this.checkIfUsersFirstProject();
this.isLoading = false;
}
/**
* Validates input value to satisfy project name rules.
*/
private validateProjectName(): boolean {
this.projectName = this.projectName.trim();
const rgx = /^[^/]+$/;
if (!rgx.test(this.projectName)) {
this.nameError = 'Name for project is invalid!';
return false;
}
if (this.projectName.length > 20) {
this.nameError = 'Name should be less than 21 character!';
return false;
}
return true;
}
/**
* Makes create project request.
*/
private async createProject(): Promise<Project> {
const project: CreateProjectModel = {
name: this.projectName,
description: this.description,
ownerId: this.$store.getters.user.id,
};
return await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
}
/**
* Selects just created project.
*/
private selectCreatedProject(): void {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.createdProjectId);
const defaultProjectLimit: number = parseInt(MetaUtils.getMetaContent('default-project-limit'));
if (new ProjectOwning(this.$store).usersProjectsCount() >= defaultProjectLimit) {
this.$store.dispatch(APP_STATE_ACTIONS.HIDE_CREATE_PROJECT_BUTTON);
}
}
/**
* Indicates if user created his first project.
*/
private checkIfUsersFirstProject(): void {
const usersProjects: Project[] = this.$store.getters.projects.filter((project: Project) => project.ownerId === this.$store.getters.user.id);
const isUsersFirstProject = usersProjects.length === 1;
isUsersFirstProject
? this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP)
: this.$notify.success('Project created successfully!');
}
/**
* 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.SET_SEARCH, '');
this.$store.dispatch(BUCKET_ACTIONS.CLEAR);
}
}
</script>
<style scoped lang="scss">
.new-project-popup-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(134, 134, 148, 0.4);
z-index: 1121;
display: flex;
justify-content: center;
align-items: center;
}
.input-container.full-input {
width: 100%;
}
.new-project-popup {
max-width: 970px;
height: auto;
background-color: #fff;
border-radius: 6px;
display: flex;
flex-direction: row;
align-items: center;
position: relative;
justify-content: center;
padding: 70px 80px 70px 50px;
&__image {
min-height: 400px;
min-width: 500px;
}
&__info-panel-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin-right: 55px;
height: 535px;
&__main-label-text {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 39px;
color: #384b65;
margin-bottom: 60px;
margin-top: 50px;
}
}
&__form-container {
width: 100%;
max-width: 520px;
&__success-title-area {
display: flex;
align-items: center;
justify-content: flex-start;
&__title {
font-size: 20px;
line-height: 24px;
color: #34bf89;
margin: 0 0 0 5px;
}
}
&__main-title {
font-size: 32px;
line-height: 39px;
color: #384b65;
}
&__button-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 30px;
}
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 40px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
@media screen and (max-width: 720px) {
.new-project-popup {
&__info-panel-container {
display: none;
}
&__form-container {
&__button-container {
width: 100%;
}
}
}
}
</style>

View File

@ -1,35 +0,0 @@
<!--Copyright (C) 2019 Storj Labs, Inc.-->
<!--See LICENSE for copying information.-->
<div v-if="isPopupShown" class="project-creation-success-popup-container">
<div class="project-creation-success-popup" id='successfulProjectCreationPopup'>
<div class="project-creation-success-popup__info-panel-container">
<ProjectCreationSuccessIcon/>
<p class="project-creation-success-popup__info-panel-container__title">Project Created!</p>
</div>
<div class="project-creation-success-popup__form-container">
<h2 class="project-creation-success-popup__form-container__main-label-text">Congrats!</h2>
<p class="project-creation-success-popup__form-container__confirmation-text">You just created your project. Next, we recommend you create your first API Key for this project. API Keys allow developers to manage their projects and build applications on top of the Storj network through our
<a class="project-creation-success-popup__form-container__confirmation-text__link" href="https://documentation.tardigrade.io/api-reference/uplink-cli" target="_blank" rel="noopener noreferrer">Uplink CLI.</a>
</p>
<div class="project-creation-success-popup__form-container__button-container">
<VButton
label="I will do it later"
width="214px"
height="50px"
:on-press="onCloseClick"
is-white="true"
/>
<VButton
label="Create an API Key"
width="214px"
height="50px"
:on-press="onCreateAPIKeyClick"
/>
</div>
</div>
<div class="project-creation-success-popup__close-cross-container" @click="onCloseClick">
<CloseCrossIcon/>
</div>
</div>
</div>

View File

@ -1,119 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
p {
margin: 0;
}
.project-creation-success-popup-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(134, 134, 148, 0.4);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.project-creation-success-popup {
width: auto;
background-color: #fff;
border-radius: 6px;
display: flex;
flex-direction: row;
align-items: flex-start;
position: relative;
justify-content: center;
padding: 140px 120px;
&__info-panel-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
margin-right: 80px;
&__title {
font-family: 'font_regular', sans-serif;
color: #34bf89;
font-size: 46px;
line-height: 56px;
width: auto;
max-width: 290px;
margin-top: 20px;
font-weight: 600;
}
}
&__form-container {
width: 100%;
max-width: 450px;
&__main-label-text {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 42px;
color: #384b65;
margin: 0;
max-width: 320px;
}
&__button-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
&__confirmation-text {
font-family: 'font_regular', sans-serif;
font-size: 16px;
line-height: 28px;
color: #373737;
margin: 19px 0 0 0;
&__link {
font-family: 'font_bold', sans-serif;
color: #2683ff;
}
}
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 40px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
@media screen and (max-width: 960px) {
.project-creation-success-popup {
&__info-panel-container {
display: none;
}
&__form-container {
&__button-container {
width: 100%;
}
}
}
}

View File

@ -1,49 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template src="./ProjectCreationSuccessPopup.html"></template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import ProjectCreationSuccessIcon from '@/../static/images/project/projectCreationSuccess.svg';
import { RouteConfig } from '@/router';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
@Component({
components: {
VButton,
ProjectCreationSuccessIcon,
CloseCrossIcon,
},
})
export default class ProjectCreationSuccessPopup extends Vue {
/**
* Closes popup.
*/
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP);
}
/**
* Changes location to api keys route.
*/
public onCreateAPIKeyClick(): void {
this.$router.push(RouteConfig.ApiKeys.path);
this.onCloseClick();
}
/**
* Indicates if component should be rendered.
*/
public get isPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isSuccessfulProjectCreationPopupShown;
}
}
</script>
<style src="./ProjectCreationSuccessPopup.scss" scoped lang="scss"></style>

View File

@ -12,6 +12,7 @@ import SettingsArea from '@/components/account/SettingsArea.vue';
import ApiKeysArea from '@/components/apiKeys/ApiKeysArea.vue';
import Page404 from '@/components/errors/Page404.vue';
import OnboardingTourArea from '@/components/onboardingTour/OnboardingTourArea.vue';
import CreateProject from '@/components/project/CreateProject.vue';
import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
@ -38,6 +39,7 @@ export abstract class RouteConfig {
public static Team = new NavigationLink('/project-members', 'Members');
public static ApiKeys = new NavigationLink('/api-keys', 'API Keys');
public static OnboardingTour = new NavigationLink('/onboarding-tour', 'Onboarding Tour');
public static CreateProject = new NavigationLink('/create-project', 'Create Project');
// child paths
public static Settings = new NavigationLink('settings', 'Settings');
@ -151,6 +153,11 @@ export const router = new Router({
name: RouteConfig.OnboardingTour.name,
component: OnboardingTourArea,
},
{
path: RouteConfig.CreateProject.path,
name: RouteConfig.CreateProject.name,
component: CreateProject,
},
],
},
{

View File

@ -12,13 +12,11 @@ export const appStateModule = {
appState: {
fetchState: AppState.LOADING,
isAddTeamMembersPopupShown: false,
isNewProjectPopupShown: false,
isAccountDropdownShown: false,
isDeleteProjectPopupShown: false,
isDeleteAccountPopupShown: false,
isSortProjectMembersByPopupShown: false,
isSuccessfulRegistrationShown: false,
isSuccessfulProjectCreationPopupShown: false,
isEditProfilePopupShown: false,
isChangePasswordPopupShown: false,
isPaymentSelectionShown: false,
@ -33,11 +31,6 @@ export const appStateModule = {
state.appState.isAddTeamMembersPopupShown = !state.appState.isAddTeamMembersPopupShown;
},
// Mutation changing new project popup visibility
[APP_STATE_MUTATIONS.TOGGLE_NEW_PROJECT_POPUP](state: any): void {
state.appState.isNewProjectPopupShown = !state.appState.isNewProjectPopupShown;
},
// Mutation changing save api key modal visibility
[APP_STATE_MUTATIONS.TOGGLE_SAVE_API_KEY_MODAL](state: any): void {
state.appState.isSaveApiKeyModalShown = !state.appState.isSaveApiKeyModalShown;
@ -64,10 +57,6 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_REGISTRATION](state: any): void {
state.appState.isSuccessfulRegistrationShown = !state.appState.isSuccessfulRegistrationShown;
},
// Mutation changing 'successful project creation' popup visibility.
[APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP](state: any): void {
state.appState.isSuccessfulProjectCreationPopupShown = !state.appState.isSuccessfulProjectCreationPopupShown;
},
[APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_POPUP](state: any): void {
state.appState.isChangePasswordPopupShown = !state.appState.isChangePasswordPopupShown;
},
@ -112,13 +101,6 @@ export const appStateModule = {
commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAMMEMBER_POPUP);
},
[APP_STATE_ACTIONS.TOGGLE_NEW_PROJ]: function ({commit, state}: any): void {
if (!state.appState.isNewProjectPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_NEW_PROJECT_POPUP);
},
[APP_STATE_ACTIONS.TOGGLE_SAVE_API_KEY_MODAL]: function ({commit, state}: any): void {
if (!state.appState.isSaveApiKeyModalShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
@ -161,13 +143,6 @@ export const appStateModule = {
commit(APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_REGISTRATION);
},
[APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP]: function ({commit, state}: any): void {
if (!state.appState.isSuccessfulProjectCreationPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP);
},
[APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP]: function ({commit}: any): void {
commit(APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
},

View File

@ -163,6 +163,13 @@ export class PaymentsHistoryItem {
return `<a class="download-link" target="_blank" href="${this.link}">${downloadLabel}</a>`;
}
/**
* isTransactionOrDeposit indicates if payments history item type is transaction or deposit bonus.
*/
public isTransactionOrDeposit(): boolean {
return this.type === PaymentsHistoryItemType.Transaction || this.type === PaymentsHistoryItemType.DepositBonus;
}
}
/**

View File

@ -76,16 +76,59 @@ export class UpdateProjectModel {
* CreateProjectModel is a type, used for creating project.
*/
export class CreateProjectModel {
public name: string;
public description: string;
public ownerId: string;
private readonly MAX_NAME_LENGTH = 20;
public constructor(
public name: string = '',
public description: string = '',
public ownerId: string = '',
) {}
/**
* checkName checks if project name is valid.
*/
public checkName(): void {
try {
this.nameIsNotEmpty();
this.nameHasNoSlashes();
this.nameHasLessThenTwentySymbols();
} catch (error) {
throw new Error(error.message);
}
}
/**
* nameHasNoSlashes checks if project name has any characters but 'slash'.
*/
private nameHasNoSlashes(): void {
const rgx = /^[^\/]+$/;
if (!rgx.test(this.name)) throw new Error('Project name can\'t have slashes!');
}
/**
* nameIsNotEmpty checks if project name is not empty.
*/
private nameIsNotEmpty(): void {
if (this.name.length === 0) throw new Error('Project name can\'t be empty!');
}
/**
* nameHasLessThenTwentySymbols checks if project name has less then 20 symbols.
*/
private nameHasLessThenTwentySymbols(): void {
if (this.name.length > this.MAX_NAME_LENGTH) throw new Error('Name should be less than 21 character!');
}
}
/**
* ProjectLimits is a type, used for describing project limits.
*/
export class ProjectLimits {
constructor(
public bandwidthLimit = 0,
public bandwidthUsed = 0,
public storageLimit = 0,
public storageUsed = 0,
public constructor(
public bandwidthLimit: number = 0,
public bandwidthUsed: number = 0,
public storageLimit: number = 0,
public storageUsed: number = 0,
) {}
}

View File

@ -21,13 +21,4 @@ export class Validator {
public static password(password: string): boolean {
return typeof password !== 'undefined' && password.length >= 6;
}
/**
* Checks string to not include slash.
*/
public static anyCharactersButSlash(string: string): boolean {
const rgx = /^[^\/]+$/;
return rgx.test(string);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,5 +0,0 @@
<svg width="123" height="123" viewBox="0 0 123 123" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="61.5" cy="61.5" r="59.5" fill="white" stroke="#34BF89" stroke-width="4"/>
<path d="M28.9292 66.1529C25.9126 63.1363 25.9126 58.2453 28.9292 55.2287C31.9459 52.212 36.8368 52.212 39.8535 55.2287L58.0606 73.4358C61.0773 76.4524 61.0773 81.3434 58.0606 84.3601C55.044 87.3767 50.153 87.3767 47.1363 84.3601L28.9292 66.1529Z" fill="#34BF89"/>
<path d="M58.0606 84.3601C55.044 87.3767 50.153 87.3767 47.1363 84.3601C44.1197 81.3434 44.1197 76.4524 47.1363 73.4358L79.9092 40.663C82.9258 37.6463 87.8168 37.6463 90.8334 40.663C93.8501 43.6796 93.8501 48.5706 90.8334 51.5872L58.0606 84.3601Z" fill="#34BF89"/>
</svg>

Before

Width:  |  Height:  |  Size: 735 B

View File

@ -1,4 +0,0 @@
<svg width="18" height="13" viewBox="0 0 18 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.375 7.375C0.615609 6.61561 0.615609 5.38439 1.375 4.625C2.13439 3.86561 3.36561 3.86561 4.12501 4.625L8.70834 9.20834C9.46774 9.96773 9.46774 11.1989 8.70834 11.9583C7.94895 12.7177 6.71773 12.7177 5.95834 11.9583L1.375 7.375Z" fill="#34BF89"/>
<path d="M8.70834 11.9583C7.94895 12.7177 6.71773 12.7177 5.95834 11.9583C5.19895 11.1989 5.19895 9.96773 5.95834 9.20834L14.2084 0.958327C14.9677 0.198935 16.199 0.198935 16.9584 0.958327C17.7177 1.71772 17.7177 2.94894 16.9584 3.70833L8.70834 11.9583Z" fill="#34BF89"/>
</svg>

Before

Width:  |  Height:  |  Size: 640 B

View File

@ -5,7 +5,6 @@ exports[`NewProjectArea renders correctly without projects and with credit card
<div id="newProjectButton" class="new-project-button-container">
<h1 class="new-project-button-container__label">+ Create Project</h1>
</div>
<!---->
</div>
`;
@ -14,58 +13,11 @@ exports[`NewProjectArea renders correctly without projects and with credit card
<div id="newProjectButton" class="new-project-button-container">
<h1 class="new-project-button-container__label">+ Create Project</h1>
</div>
<div class="new-project-popup-container">
<div id="newProjectPopup" class="new-project-popup"><img src="@/../static/images/project/createProject.jpg" alt="create project image" class="new-project-popup__image">
<div class="new-project-popup__form-container">
<div class="new-project-popup__form-container__success-title-area"><svg width="18" height="13" viewBox="0 0 18 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.375 7.375C0.615609 6.61561 0.615609 5.38439 1.375 4.625C2.13439 3.86561 3.36561 3.86561 4.12501 4.625L8.70834 9.20834C9.46774 9.96773 9.46774 11.1989 8.70834 11.9583C7.94895 12.7177 6.71773 12.7177 5.95834 11.9583L1.375 7.375Z" fill="#34BF89"></path>
<path d="M8.70834 11.9583C7.94895 12.7177 6.71773 12.7177 5.95834 11.9583C5.19895 11.1989 5.19895 9.96773 5.95834 9.20834L14.2084 0.958327C14.9677 0.198935 16.199 0.198935 16.9584 0.958327C17.7177 1.71772 17.7177 2.94894 16.9584 3.70833L8.70834 11.9583Z" fill="#34BF89"></path>
</svg>
<p class="new-project-popup__form-container__success-title-area__title">Payment Method Added</p>
</div>
<h2 class="new-project-popup__form-container__main-title">Next, lets create a project.</h2>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">Project Name</h3>
<h3 class="label-container__main__label add-label">Up To 20 Characters</h3>
<!---->
</div>
<h3 class="label-container__limit">0/20</h3>
</div>
<!---->
<!----> <input id="Project Name" placeholder="Enter Project Name" type="text" class="headered-input" style="width: 100%; height: 48px;">
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">Description</h3>
<h3 class="label-container__main__label add-label">Optional</h3>
<!---->
</div>
<h3 class="label-container__limit">0/100</h3>
</div>
<!----> <textarea id="Description" placeholder="Enter Project Description" rows="5" cols="40" wrap="hard" class="headered-textarea" style="height: 60px;"></textarea>
<!---->
</div>
<div class="new-project-popup__form-container__button-container">
<div class="container white" style="width: 205px; height: 48px;"><span class="label">Back to Billing</span></div>
<div class="container" style="width: 205px; height: 48px;"><span class="label">Next</span></div>
</div>
</div>
<div class="new-project-popup__close-cross-container"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L15.7071 1.70711ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L14.2929 15.7071ZM14.2929 0.292893L0.292893 14.2929L1.70711 15.7071L15.7071 1.70711L14.2929 0.292893ZM0.292893 1.70711L14.2929 15.7071L15.7071 14.2929L1.70711 0.292893L0.292893 1.70711Z" fill="#384B65" class="close-cross-svg-path"></path>
</svg></div>
</div>
</div>
</div>
`;
exports[`NewProjectArea renders correctly without projects and without payment methods 1`] = `
<div class="new-project-container">
<!---->
<!---->
</div>
`;

View File

@ -0,0 +1,43 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import CreateProject from '@/components/project/CreateProject.vue';
import { makeProjectsModule } from '@/store/modules/projects';
import { NotificatorPlugin } from '@/utils/plugins/notificator';
import { createLocalVue, mount } from '@vue/test-utils';
import { ProjectsApiMock } from '../mock/api/projects';
const notificationPlugin = new NotificatorPlugin();
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(notificationPlugin);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const store = new Vuex.Store({ modules: { projectsModule }});
describe('CreateProject.vue', (): void => {
it('renders correctly', (): void => {
const wrapper = mount(CreateProject, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('renders correctly with project name', async (): Promise<void> => {
const wrapper = mount(CreateProject, {
store,
localVue,
});
await wrapper.vm.setProjectName('testName');
expect(wrapper.findAll('.disabled').length).toBe(0);
});
});

View File

@ -1,54 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import NewProjectPopup from '@/components/project/NewProjectPopup.vue';
import { appStateModule } from '@/store/modules/appState';
import { makeProjectsModule } from '@/store/modules/projects';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { NotificatorPlugin } from '@/utils/plugins/notificator';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import { ProjectsApiMock } from '../mock/api/projects';
const notificationPlugin = new NotificatorPlugin();
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.use(notificationPlugin);
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const store = new Vuex.Store({ modules: { appStateModule, projectsModule }});
describe('NewProjectPopup.vue', () => {
it('renders correctly', async (): Promise<void> => {
await store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
const wrapper = mount(NewProjectPopup, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
await wrapper.vm.setProjectName('testName');
await wrapper.vm.createProjectClick();
expect(wrapper).toMatchSnapshot();
});
it('closes correctly', async (): Promise<void> => {
await store.dispatch(APP_STATE_ACTIONS.TOGGLE_NEW_PROJ);
const wrapper = shallowMount(NewProjectPopup, {
store,
localVue,
});
await wrapper.find('.new-project-popup__close-cross-container').trigger('click');
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -1,39 +0,0 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import ProjectCreationSuccessPopup from '@/components/project/ProjectCreationSuccessPopup.vue';
import { appStateModule } from '@/store/modules/appState';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
const localVue = createLocalVue();
localVue.use(Vuex);
const store = new Vuex.Store({ modules: { appStateModule }});
describe('ProjectCreationSuccessPopup.vue', () => {
it('renders correctly', async (): Promise<void> => {
await store.dispatch(APP_STATE_ACTIONS.TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP);
const wrapper = shallowMount(ProjectCreationSuccessPopup, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('closes correctly', async (): Promise<void> => {
const wrapper = shallowMount(ProjectCreationSuccessPopup, {
store,
localVue,
});
await wrapper.find('.project-creation-success-popup__close-cross-container').trigger('click');
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CreateProject.vue renders correctly 1`] = `
<div class="create-project-area">
<div class="create-project-area__container"><img src="@/../static/images/project/createProject.png" alt="create project image">
<h2 class="create-project-area__title">Create a Project</h2>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">Project Name</h3>
<h3 class="label-container__main__label add-label">Up To 20 Characters</h3>
<!---->
</div>
<h3 class="label-container__limit">0/20</h3>
</div>
<!---->
<!----> <input id="Project Name" placeholder="Enter Project Name" type="text" class="headered-input" style="width: 100%; height: 48px;">
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">Description</h3>
<h3 class="label-container__main__label add-label">Optional</h3>
<!---->
</div>
<h3 class="label-container__limit">0/100</h3>
</div>
<!----> <textarea id="Description" placeholder="Enter Project Description" rows="5" cols="40" wrap="hard" class="headered-textarea" style="height: 60px;"></textarea>
<!---->
</div>
<div class="create-project-area__container__button-container">
<div class="container white" style="width: 210px; height: 48px;"><span class="label">Cancel</span></div>
<div class="container disabled" style="width: 210px; height: 48px;"><span class="label">Create Project +</span></div>
</div>
<!---->
</div>
</div>
`;

View File

@ -1,54 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`NewProjectPopup.vue closes correctly 1`] = ``;
exports[`NewProjectPopup.vue renders correctly 1`] = `
<div class="new-project-popup-container">
<div id="newProjectPopup" class="new-project-popup"><img src="@/../static/images/project/createProject.jpg" alt="create project image" class="new-project-popup__image">
<div class="new-project-popup__form-container">
<div class="new-project-popup__form-container__success-title-area"><svg width="18" height="13" viewBox="0 0 18 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.375 7.375C0.615609 6.61561 0.615609 5.38439 1.375 4.625C2.13439 3.86561 3.36561 3.86561 4.12501 4.625L8.70834 9.20834C9.46774 9.96773 9.46774 11.1989 8.70834 11.9583C7.94895 12.7177 6.71773 12.7177 5.95834 11.9583L1.375 7.375Z" fill="#34BF89"></path>
<path d="M8.70834 11.9583C7.94895 12.7177 6.71773 12.7177 5.95834 11.9583C5.19895 11.1989 5.19895 9.96773 5.95834 9.20834L14.2084 0.958327C14.9677 0.198935 16.199 0.198935 16.9584 0.958327C17.7177 1.71772 17.7177 2.94894 16.9584 3.70833L8.70834 11.9583Z" fill="#34BF89"></path>
</svg>
<p class="new-project-popup__form-container__success-title-area__title">Payment Method Added</p>
</div>
<h2 class="new-project-popup__form-container__main-title">Next, lets create a project.</h2>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">Project Name</h3>
<h3 class="label-container__main__label add-label">Up To 20 Characters</h3>
<!---->
</div>
<h3 class="label-container__limit">0/20</h3>
</div>
<!---->
<!----> <input id="Project Name" placeholder="Enter Project Name" type="text" class="headered-input" style="width: 100%; height: 48px;">
</div>
<div class="input-container full-input">
<div class="label-container">
<div class="label-container__main">
<!---->
<h3 class="label-container__main__label">Description</h3>
<h3 class="label-container__main__label add-label">Optional</h3>
<!---->
</div>
<h3 class="label-container__limit">0/100</h3>
</div>
<!----> <textarea id="Description" placeholder="Enter Project Description" rows="5" cols="40" wrap="hard" class="headered-textarea" style="height: 60px;"></textarea>
<!---->
</div>
<div class="new-project-popup__form-container__button-container">
<div class="container white" style="width: 205px; height: 48px;"><span class="label">Back to Billing</span></div>
<div class="container" style="width: 205px; height: 48px;"><span class="label">Next</span></div>
</div>
</div>
<div class="new-project-popup__close-cross-container"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7071 1.70711C16.0976 1.31658 16.0976 0.683417 15.7071 0.292893C15.3166 -0.0976311 14.6834 -0.0976311 14.2929 0.292893L15.7071 1.70711ZM0.292893 14.2929C-0.0976311 14.6834 -0.0976311 15.3166 0.292893 15.7071C0.683417 16.0976 1.31658 16.0976 1.70711 15.7071L0.292893 14.2929ZM1.70711 0.292893C1.31658 -0.0976311 0.683417 -0.0976311 0.292893 0.292893C-0.0976311 0.683417 -0.0976311 1.31658 0.292893 1.70711L1.70711 0.292893ZM14.2929 15.7071C14.6834 16.0976 15.3166 16.0976 15.7071 15.7071C16.0976 15.3166 16.0976 14.6834 15.7071 14.2929L14.2929 15.7071ZM14.2929 0.292893L0.292893 14.2929L1.70711 15.7071L15.7071 1.70711L14.2929 0.292893ZM0.292893 1.70711L14.2929 15.7071L15.7071 14.2929L1.70711 0.292893L0.292893 1.70711Z" fill="#384B65" class="close-cross-svg-path"></path>
</svg></div>
</div>
</div>
`;
exports[`NewProjectPopup.vue renders correctly 2`] = ``;

View File

@ -1,26 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProjectCreationSuccessPopup.vue closes correctly 1`] = ``;
exports[`ProjectCreationSuccessPopup.vue renders correctly 1`] = `
<div class="project-creation-success-popup-container">
<div id="successfulProjectCreationPopup" class="project-creation-success-popup">
<div class="project-creation-success-popup__info-panel-container">
<projectcreationsuccessicon-stub></projectcreationsuccessicon-stub>
<p class="project-creation-success-popup__info-panel-container__title">Project Created!</p>
</div>
<div class="project-creation-success-popup__form-container">
<h2 class="project-creation-success-popup__form-container__main-label-text">Congrats!</h2>
<p class="project-creation-success-popup__form-container__confirmation-text">You just created your project. Next, we recommend you create your first API Key for this project. API Keys allow developers to manage their projects and build applications on top of the Storj network through our
<a href="https://documentation.tardigrade.io/api-reference/uplink-cli" target="_blank" rel="noopener noreferrer" class="project-creation-success-popup__form-container__confirmation-text__link">Uplink CLI.</a></p>
<div class="project-creation-success-popup__form-container__button-container">
<vbutton-stub label="I will do it later" width="214px" height="50px" iswhite="true" onpress="function () { [native code] }"></vbutton-stub>
<vbutton-stub label="Create an API Key" width="214px" height="50px" onpress="function () { [native code] }"></vbutton-stub>
</div>
</div>
<div class="project-creation-success-popup__close-cross-container">
<closecrossicon-stub></closecrossicon-stub>
</div>
</div>
</div>
`;

View File

@ -39,22 +39,4 @@ describe('validation', (): void => {
expect(Validator.email(testString6)).toBe(false);
expect(Validator.email(testString7)).toBe(true);
});
it('anyCharactersButSlash regex works correctly', () => {
const testString1 = 'tGDFst/';
const testString2 = '/ ';
const testString3 = 'tes/t@';
const testString4 = 'test./test';
const testString5 = '3gGD!@#$%^&*()-=+.,';
const testString6 = ' /';
const testString7 = '/teSTt1123';
expect(Validator.anyCharactersButSlash(testString1)).toBe(false);
expect(Validator.anyCharactersButSlash(testString2)).toBe(false);
expect(Validator.anyCharactersButSlash(testString3)).toBe(false);
expect(Validator.anyCharactersButSlash(testString4)).toBe(false);
expect(Validator.anyCharactersButSlash(testString5)).toBe(true);
expect(Validator.anyCharactersButSlash(testString6)).toBe(false);
expect(Validator.anyCharactersButSlash(testString7)).toBe(false);
});
});