web/satellite: add modal for creating a project

github issue: https://github.com/storj/storj/issues/4372

Change-Id: I9b8cf64aa76f2d75c740ee8a0158f0382f4a1918
This commit is contained in:
Cameron 2022-07-18 16:05:15 -04:00 committed by Storj Robot
parent 23e5ccb9c9
commit 9cd91c6df8
7 changed files with 284 additions and 4 deletions

View File

@ -4,6 +4,7 @@
<template>
<div>
<CreateProjectPromptModal v-if="isCreateProjectPromptModal" />
<CreateProjectModal v-if="isCreateProjectModal" />
<AddPaymentMethodModal v-if="isAddPMModal" />
<OpenBucketModal v-if="isOpenBucketModal" />
</div>
@ -13,6 +14,7 @@
import { Component, Vue } from 'vue-property-decorator';
import CreateProjectPromptModal from '@/components/modals/CreateProjectPromptModal.vue';
import CreateProjectModal from '@/components/modals/CreateProjectModal.vue'
import AddPaymentMethodModal from "@/components/modals/AddPaymentMethodModal.vue";
import OpenBucketModal from "@/components/modals/OpenBucketModal.vue";
@ -20,6 +22,7 @@ import OpenBucketModal from "@/components/modals/OpenBucketModal.vue";
@Component({
components: {
CreateProjectPromptModal,
CreateProjectModal,
AddPaymentMethodModal,
OpenBucketModal,
},
@ -32,6 +35,13 @@ export default class AllModals extends Vue {
return this.$store.state.appStateModule.appState.isCreateProjectPromptModalShown;
}
/**
* Indicates if create project prompt modal is shown.
*/
public get isCreateProjectModal(): boolean {
return this.$store.state.appStateModule.appState.isCreateProjectModalShown;
}
/**
* Indicates if add payment method modal is shown.
*/

View File

@ -0,0 +1,253 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<img
class="modal__icon"
src="@/../static/images/account/billing/paidTier/prompt.png"
alt="Prompt Image"
>
<h1 class="modal__title" aria-roledescription="modal-title">
Create a Project
</h1>
<VInput
label="Project Name"
additional-label="Up To 20 Characters"
placeholder="Enter Project Name"
class="full-input"
is-limit-shown="true"
:current-limit="projectName.length"
:max-symbols="20"
:error="nameError"
@setData="setProjectName"
/>
<VInput
label="Description"
placeholder="Enter Project Description"
additional-label="Optional"
class="full-input"
is-multiline="true"
height="100px"
is-limit-shown="true"
:current-limit="description.length"
:max-symbols="100"
@setData="setProjectDescription"
/>
<div class="modal__button-container">
<VButton
class="modal__button-container__cancel"
label="Cancel"
width="210px"
height="48px"
:on-press="closeModal"
is-transparent="true"
/>
<VButton
label="Create Project +"
width="210px"
height="48px"
:on-press="onCreateProjectClick"
:is-disabled="!projectName"
/>
</div>
<div v-if="isLoading" class="modal__blur">
<VLoader class="modal__blur__loader" width="50px" height="50px" />
</div>
</div>
</template>
</VModal>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
import VInput from '@/components/common/VInput.vue';
import VLoader from '@/components/common/VLoader.vue';
import { RouteConfig } from '@/router';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectFields } from '@/types/projects';
import { LocalData } from '@/utils/localData';
import { AnalyticsHttpApi } from '@/api/analytics';
// @vue/component
@Component({
components: {
VButton,
VModal,
VInput,
VLoader,
},
})
export default class CreateProjectModal extends Vue {
private description = '';
private createdProjectId = '';
private isLoading = false;
public projectName = '';
public nameError = '';
public readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Sets project name from input value.
*/
public setProjectName(value: string): void {
this.projectName = value;
this.nameError = '';
}
/**
* Sets project description from input value.
*/
public setProjectDescription(value: string): void {
this.description = value;
}
/**
* Creates project and refreshes store.
*/
public async onCreateProjectClick(): Promise<void> {
if (this.isLoading) {
return;
}
this.isLoading = true;
this.projectName = this.projectName.trim();
const project = new ProjectFields(
this.projectName,
this.description,
this.$store.getters.user.id,
);
try {
project.checkName();
} catch (error) {
this.isLoading = false;
this.nameError = error.message;
return;
}
try {
const createdProject = await this.$store.dispatch(PROJECTS_ACTIONS.CREATE, project);
this.createdProjectId = createdProject.id;
} catch (error) {
this.isLoading = false;
return;
}
this.selectCreatedProject();
await this.$notify.success('Project created successfully!');
this.isLoading = false;
this.closeModal()
this.analytics.pageVisit(RouteConfig.ProjectDashboard.path);
await this.$router.push(RouteConfig.ProjectDashboard.path);
}
/**
* Selects just created project.
*/
private selectCreatedProject(): void {
this.$store.dispatch(PROJECTS_ACTIONS.SELECT, this.createdProjectId);
LocalData.setSelectedProjectId(this.createdProjectId);
}
/**
* Holds on button click logic.
* Closes this modal and opens create project modal.
*/
public onClick(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_POPUP);
}
/**
* Closes create project modal.
*/
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_POPUP);
}
}
</script>
<style scoped lang="scss">
.modal {
width: 400px;
padding: 50px 65px 65px;
display: flex;
align-items: center;
flex-direction: column;
font-family: 'font_regular', sans-serif;
&__icon {
max-height: 154px;
max-width: 118px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 34px;
color: #1b2533;
margin-top: 40px;
text-align: center;
}
&__info {
font-family: 'font_regular', sans-serif;
font-size: 16px;
line-height: 21px;
text-align: center;
color: #354049;
margin: 15px 0 45px;
}
&__button-container {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30px;
&__cancel {
margin-right: 20px;
}
}
&__blur {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: rgb(229 229 229 / 20%);
border-radius: 8px;
z-index: 100;
&__loader {
width: 25px;
height: 25px;
position: absolute;
right: 40px;
top: 40px;
}
}
}
.full-input {
margin-top: 20px;
}
</style>

View File

@ -291,7 +291,7 @@ export default class NavigationArea extends Vue {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PROMPT_POPUP);
} else {
this.analytics.pageVisit(RouteConfig.CreateProject.path);
this.$router.push(RouteConfig.CreateProject.path);
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_POPUP);
}
}

View File

@ -237,7 +237,7 @@ export default class ProjectSelection extends Vue {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PROMPT_POPUP);
} else {
this.analytics.pageVisit(RouteConfig.CreateProject.path);
this.$router.push(RouteConfig.CreateProject.path);
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_POPUP);
}
}

View File

@ -53,6 +53,9 @@ import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { LocalData } from '@/utils/localData';
import { AnalyticsHttpApi } from '@/api/analytics';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import { User } from "@/types/users";
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
const {
FETCH_OWNED,
@ -107,8 +110,17 @@ export default class Projects extends Vue {
* Redirects to create project page.
*/
public onCreateClick(): void {
this.analytics.pageVisit(RouteConfig.CreateProject.path);
this.$router.push(RouteConfig.CreateProject.path);
this.analytics.eventTriggered(AnalyticsEvent.NEW_PROJECT_CLICKED);
const user: User = this.$store.getters.user;
const ownProjectsCount: number = this.$store.getters.projectsCount;
if (!user.paidTier && user.projectLimit === ownProjectsCount) {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PROMPT_POPUP);
} else {
this.analytics.pageVisit(RouteConfig.CreateProject.path);
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_POPUP);
}
}
/**

View File

@ -30,6 +30,7 @@ class ViewsState {
public isUploadCancelPopupVisible = false,
public isSuccessfulPasswordResetShown = false,
public isCreateProjectPromptModalShown = false,
public isCreateProjectModalShown = false,
public isAddPMModalShown = false,
public isOpenBucketModalShown = false,
@ -121,6 +122,9 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PROMPT_POPUP](state: State): void {
state.appState.isCreateProjectPromptModalShown = !state.appState.isCreateProjectPromptModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_POPUP](state: State): void {
state.appState.isCreateProjectModalShown = !state.appState.isCreateProjectModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_IS_ADD_PM_MODAL_SHOWN](state: State): void {
state.appState.isAddPMModalShown = !state.appState.isAddPMModalShown;
},

View File

@ -30,6 +30,7 @@ export const APP_STATE_MUTATIONS = {
TOGGLE_CHANGE_PASSWORD_POPUP: 'TOGGLE_CHANGE_PASSWORD_POPUP',
TOGGLE_UPLOAD_CANCEL_POPUP: 'TOGGLE_UPLOAD_CANCEL_POPUP',
TOGGLE_CREATE_PROJECT_PROMPT_POPUP: 'TOGGLE_CREATE_PROJECT_PROMPT_POPUP',
TOGGLE_CREATE_PROJECT_POPUP: 'TOGGLE_CREATE_PROJECT_POPUP',
TOGGLE_IS_ADD_PM_MODAL_SHOWN: 'TOGGLE_IS_ADD_PM_MODAL_SHOWN',
TOGGLE_OPEN_BUCKET_MODAL_SHOWN: 'TOGGLE_OPEN_BUCKET_MODAL_SHOWN',
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',