web/satellite: added ability to edit project name

WHAT:
added edit project dropdown to navigation side bar.
edit project details page implemented.
added ability to edit project name.
project details section removed from project dashboard.

WHY:
enable users to change their project name.

Change-Id: I36b6214ffe7adf4a12a1a09530ff1212e926aafe
This commit is contained in:
VitaliiShpital 2020-09-15 15:44:23 +03:00
parent 8182fdad0b
commit 2668ec818e
33 changed files with 801 additions and 332 deletions

View File

@ -2857,9 +2857,9 @@
},
"dependencies": {
"@types/node": {
"version": "14.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz",
"integrity": "sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ=="
"version": "14.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz",
"integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw=="
}
}
},
@ -6562,9 +6562,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.568",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.568.tgz",
"integrity": "sha512-j9MlEwgTHVW/lq93Hw8yhzA886oLjDm3Hz7eDkWP2v4fzLVuqOWhpNluziSnmR/tBqgoYldagbLknrdg+B7Tlw==",
"version": "1.3.570",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.570.tgz",
"integrity": "sha512-Y6OCoVQgFQBP5py6A/06+yWxUZHDlNr/gNDGatjH8AZqXl8X0tE4LfjLJsXGz/JmWJz8a6K7bR1k+QzZ+k//fg==",
"dev": true
},
"elliptic": {
@ -9930,9 +9930,9 @@
"dev": true
},
"klona": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.3.tgz",
"integrity": "sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
"integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==",
"dev": true
},
"known-css-properties": {
@ -10891,9 +10891,9 @@
"dev": true
},
"node-forge": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
"dev": true
},
"node-gyp": {
@ -13617,12 +13617,12 @@
"dev": true
},
"selfsigned": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
"version": "1.10.8",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
"dev": true,
"requires": {
"node-forge": "0.9.0"
"node-forge": "^0.10.0"
}
},
"semver": {
@ -14615,9 +14615,9 @@
},
"dependencies": {
"@types/node": {
"version": "14.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz",
"integrity": "sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ=="
"version": "14.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.2.tgz",
"integrity": "sha512-IzMhbDYCpv26pC2wboJ4MMOa9GKtjplXfcAqrMeNJpUUwpM/2ATt2w1JPUXwS6spu856TvKZL2AOmeU2rAxskw=="
}
}
},

View File

@ -3,7 +3,7 @@
import { BaseGql } from '@/api/baseGql';
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
import { CreateProjectFields, Project, ProjectLimits, ProjectsApi } from '@/types/projects';
import { Project, ProjectFields, ProjectLimits, ProjectsApi } from '@/types/projects';
import { HttpClient } from '@/utils/httpClient';
export class ProjectsApiGql extends BaseGql implements ProjectsApi {
@ -13,10 +13,10 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
/**
* Creates project.
*
* @param createProjectFields - contains project information
* @param projectFields - contains project information
* @throws Error
*/
public async create(createProjectFields: CreateProjectFields): Promise<Project> {
public async create(projectFields: ProjectFields): Promise<Project> {
const query =
`mutation($name: String!, $description: String!) {
createProject(
@ -28,13 +28,13 @@ export class ProjectsApiGql extends BaseGql implements ProjectsApi {
}`;
const variables = {
name: createProjectFields.name,
description: createProjectFields.description,
name: projectFields.name,
description: projectFields.description,
};
const response = await this.mutate(query, variables);
return new Project(response.data.createProject.id, variables.name, variables.description, '', createProjectFields.ownerId);
return new Project(response.data.createProject.id, variables.name, variables.description, '', projectFields.ownerId);
}
/**

View File

@ -53,7 +53,7 @@
width="205px"
height="48px"
:on-press="onCloseClick"
is-white="true"
is-transparent="true"
/>
<VButton
label="Update"

View File

@ -24,7 +24,7 @@
label='Cancel'
width='205px' height='48px'
:on-press='onCloseClick'
is-white="true"
is-transparent="true"
/>
<VButton
label='Delete'

View File

@ -36,7 +36,7 @@
width="205px"
height="48px"
:on-press="onCloseClick"
is-white="true"
is-transparent="true"
/>
<VButton
label="Update"

View File

@ -46,7 +46,7 @@
label="Cancel"
width="122px"
height="48px"
is-white="true"
is-transparent="true"
:on-press="onClearSelection"
/>
<span class="header-selected-api-keys__info-text" v-if="!isDeleteClicked">

View File

@ -28,6 +28,8 @@ export default class VButton extends Vue {
@Prop({default: false})
private readonly isWhite: boolean;
@Prop({default: false})
private readonly isTransparent: boolean;
@Prop({default: false})
private readonly isDeletion: boolean;
@Prop({default: false})
private readonly isBlueWhite: boolean;
@ -45,6 +47,8 @@ export default class VButton extends Vue {
if (this.isWhite) return 'container white';
if (this.isTransparent) return 'container transparent';
if (this.isDeletion) return 'container red';
if (this.isBlueWhite) return 'container blue-white';
@ -55,7 +59,7 @@ export default class VButton extends Vue {
</script>
<style scoped lang="scss">
.white,
.transparent,
.red {
background-color: transparent !important;
border: 1px solid #afb7c1 !important;
@ -65,6 +69,15 @@ export default class VButton extends Vue {
}
}
.white {
background-color: #fff !important;
border: 1px solid #afb7c1 !important;
.label {
color: #354049 !important;
}
}
.blue-white {
background-color: #fff !important;
border: 2px solid #2683ff !important;
@ -102,8 +115,9 @@ export default class VButton extends Vue {
&:hover {
background-color: #0059d0;
&.white,
&.blue-white {
&.transparent,
&.blue-white,
&.white {
box-shadow: none !important;
background-color: #2683ff !important;
border: 1px solid #2683ff !important;

View File

@ -0,0 +1,134 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="edit-project">
<div class="edit-project__selection-area" :class="{ active: isDropdownShown }" @click.stop.prevent="toggleDropdown">
<h1 class="edit-project__selection-area__name">{{ projectName }}</h1>
<DotsImage class="edit-project__selection-area__image"/>
</div>
<div class="edit-project__dropdown" v-if="isDropdownShown" v-click-outside="closeDropdown">
<div class="edit-project__dropdown__choice" @click.stop.prevent="onEditProjectClick">
<EditImage/>
<p class="edit-project__dropdown__choice__label">Edit Details</p>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import DotsImage from '@/../static/images/navigation/dots.svg';
import EditImage from '@/../static/images/navigation/edit.svg';
import { RouteConfig } from '@/router';
@Component({
components: {
DotsImage,
EditImage,
},
})
export default class EditProjectDropdown extends Vue {
public isDropdownShown: boolean = false;
/**
* Returns selected project's name.
*/
public get projectName(): string {
return this.$store.getters.selectedProject.name;
}
/**
* Redirects to edit project details page.
*/
public onEditProjectClick(): void {
this.closeDropdown();
this.$router.push(RouteConfig.EditProjectDetails.path);
}
/**
* Toggles dropdown visibility.
*/
public toggleDropdown(): void {
this.isDropdownShown = !this.isDropdownShown;
}
/**
* Closes dropdown.
*/
public closeDropdown(): void {
this.isDropdownShown = false;
}
}
</script>
<style scoped lang="scss">
.edit-project {
font-family: 'font_regular', sans-serif;
width: 190px;
position: relative;
margin: 0 0 30px 13px;
&__selection-area {
width: calc(100% - 25px);
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 15px 10px 10px;
border-radius: 6px;
cursor: pointer;
&__name {
font-family: 'font_bold', sans-serif;
font-size: 18px;
line-height: 22px;
color: #000;
margin: 0 5px 0 0;
word-break: break-all;
}
&__image {
min-width: 3px;
}
}
&__dropdown {
position: absolute;
top: calc(100% + 5px);
left: 0;
background-color: #fff;
box-shadow: 0 8px 34px rgba(161, 173, 185, 0.41);
border-radius: 6px;
padding: 5px 0;
width: 190px;
z-index: 1;
&__choice {
background-color: #fff;
width: calc(100% - 32px);
padding: 10px 16px;
cursor: pointer;
display: flex;
align-items: center;
&__label {
margin: 0 0 0 10px;
font-weight: 500;
font-size: 14px;
line-height: 19px;
color: #354049;
}
&:hover {
background-color: #f5f5f7;
}
}
}
}
.active {
background-color: rgba(245, 246, 250, 0.7);
}
</style>

View File

@ -3,6 +3,7 @@
<template>
<div class="navigation-area" v-if="!isOnboardingTour">
<EditProjectDropdown/>
<router-link
:aria-label="navItem.name"
class="navigation-area__item-container"
@ -21,6 +22,8 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import EditProjectDropdown from '@/components/navigation/EditProjectDropdown.vue';
import ApiKeysIcon from '@/../static/images/navigation/apiKeys.svg';
import DashboardIcon from '@/../static/images/navigation/dashboard.svg';
import TeamIcon from '@/../static/images/navigation/team.svg';
@ -33,6 +36,7 @@ import { NavigationLink } from '@/types/navigation';
DashboardIcon,
ApiKeysIcon,
TeamIcon,
EditProjectDropdown,
},
})
export default class NavigationArea extends Vue {

View File

@ -66,7 +66,7 @@ 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 { CreateProjectFields } from '@/types/projects';
import { ProjectFields } from '@/types/projects';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { SegmentEvent } from '@/utils/constants/analyticsEventNames';
@ -109,7 +109,7 @@ export default class CreateProjectStep extends Vue {
this.isLoading = true;
this.projectName = this.projectName.trim();
const project = new CreateProjectFields(
const project = new ProjectFields(
this.projectName,
this.description,
this.$store.getters.user.id,

View File

@ -37,7 +37,7 @@
width="210px"
height="48px"
:on-press="onCancelClick"
is-white="true"
is-transparent="true"
/>
<VButton
label="Create Project +"
@ -69,7 +69,7 @@ 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 { CreateProjectFields } from '@/types/projects';
import { ProjectFields } from '@/types/projects';
import {
APP_STATE_ACTIONS,
PM_ACTIONS,
@ -126,7 +126,7 @@ export default class NewProjectPopup extends Vue {
this.isLoading = true;
this.projectName = this.projectName.trim();
const project = new CreateProjectFields(
const project = new ProjectFields(
this.projectName,
this.description,
this.$store.getters.user.id,

View File

@ -30,7 +30,7 @@
width="205px"
height="48px"
:on-press="onCloseClick"
is-white="true"
is-transparent="true"
/>
<VButton
label="Delete"

View File

@ -0,0 +1,315 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="project-details">
<div class="project-details__wrapper">
<p class="project-details__wrapper__back" @click.stop.prevent="onBackClick"><- Back</p>
<div class="project-details__wrapper__container">
<h1 class="project-details__wrapper__container__title">Project Details</h1>
<p class="project-details__wrapper__container__label">Name</p>
<div class="project-details__wrapper__container__name-area" v-if="!isNameEditing">
<p class="project-details__wrapper__container__name-area__name">{{ storedProject.name }}</p>
<VButton
label="Edit"
width="64px"
height="28px"
:on-press="toggleNameEditing"
is-white="true"
/>
</div>
<div class="project-details__wrapper__container__name-editing" v-if="isNameEditing">
<input
class="project-details__wrapper__container__name-editing__input"
placeholder="Enter a name for your project"
@input="onNameInput"
@change="onNameInput"
v-model="nameValue"
/>
<span class="project-details__wrapper__container__name-editing__limit">{{ nameValue.length }}/{{ nameLength }}</span>
<VButton
class="project-details__wrapper__container__name-editing__save-button"
label="Save"
width="66px"
height="30px"
:on-press="onSaveNameButtonClick"
/>
</div>
<p class="project-details__wrapper__container__label">Description</p>
<div class="project-details__wrapper__container__description-area" v-if="!isDescriptionEditing">
<p class="project-details__wrapper__container__description-area__description">{{ displayedDescription }}</p>
<VButton
label="Edit"
width="64px"
height="28px"
:on-press="toggleDescriptionEditing"
is-white="true"
/>
</div>
<div class="project-details__wrapper__container__description-editing" v-if="isDescriptionEditing">
<input
class="project-details__wrapper__container__description-editing__input"
placeholder="Enter a description for your project"
@input="onDescriptionInput"
@change="onDescriptionInput"
v-model="descriptionValue"
/>
<span class="project-details__wrapper__container__description-editing__limit">{{ descriptionValue.length }}/{{ descriptionLength }}</span>
<VButton
class="project-details__wrapper__container__description-editing__save-button"
label="Save"
width="66px"
height="30px"
:on-press="onSaveDescriptionButtonClick"
/>
</div>
</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 { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, Project, ProjectFields } from '@/types/projects';
@Component({
components: {
VButton,
HeaderedInput,
},
})
export default class EditProjectDetails extends Vue {
public isNameEditing: boolean = false;
public isDescriptionEditing: boolean = false;
public nameValue: string = '';
public descriptionValue: string = '';
public nameLength: number = MAX_NAME_LENGTH;
public descriptionLength: number = MAX_DESCRIPTION_LENGTH;
/**
* Returns selected project from store.
*/
public get storedProject(): Project {
return this.$store.getters.selectedProject;
}
/**
* Returns displayed project description on UI.
*/
public get displayedDescription(): string {
return this.storedProject.description ?
this.storedProject.description :
'No description yet. Please enter some information if any.';
}
/**
* Triggers on name input.
*/
public onNameInput({ target }): void {
if (target.value.length < MAX_NAME_LENGTH) {
this.nameValue = target.value;
return;
}
this.nameValue = target.value.slice(0, MAX_NAME_LENGTH);
}
/**
* Triggers on description input.
*/
public onDescriptionInput({ target }): void {
if (target.value.length < MAX_DESCRIPTION_LENGTH) {
this.descriptionValue = target.value;
return;
}
this.descriptionValue = target.value.slice(0, MAX_DESCRIPTION_LENGTH);
}
/**
* Updates project name.
*/
public async onSaveNameButtonClick(): Promise<void> {
try {
const updatedProject = new ProjectFields(this.nameValue, '');
updatedProject.checkName();
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_NAME, updatedProject);
} catch (error) {
await this.$notify.error(`Unable to update project name. ${error.message}`);
return;
}
this.toggleNameEditing();
await this.$notify.success('Project name updated successfully!');
}
/**
* Updates project description.
*/
public async onSaveDescriptionButtonClick(): Promise<void> {
try {
const updatedProject = new ProjectFields('', this.descriptionValue);
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_DESCRIPTION, updatedProject);
} catch (error) {
await this.$notify.error(`Unable to update project description. ${error.message}`);
return;
}
this.toggleDescriptionEditing();
await this.$notify.success('Project description updated successfully!');
}
/**
* Toggles project name editing state.
*/
public toggleNameEditing(): void {
this.isNameEditing = !this.isNameEditing;
this.nameValue = this.storedProject.name;
}
/**
* Toggles project description editing state.
*/
public toggleDescriptionEditing(): void {
this.isDescriptionEditing = !this.isDescriptionEditing;
this.descriptionValue = this.storedProject.description;
}
/**
* Redirects to previous route.
*/
public onBackClick(): void {
const PREVIOUS_ROUTE_NUMBER = -1;
this.$router.go(PREVIOUS_ROUTE_NUMBER);
}
}
</script>
<style scoped lang="scss">
.project-details {
padding: 45px 0;
font-family: 'font_regular', sans-serif;
display: flex;
align-items: center;
justify-content: center;
&__wrapper {
width: 727px;
&__back {
cursor: pointer;
font-weight: 500;
font-size: 16px;
line-height: 23px;
color: #2582ff;
margin: 0 0 20px 0;
}
&__container {
padding: 50px;
width: calc(100% - 100px);
border-radius: 6px;
background-color: #fff;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 27px;
color: #384b65;
margin: 0 0 35px 0;
}
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 16px;
color: #384b65;
margin: 0 0 15px 0;
}
&__name-area,
&__description-area {
display: flex;
align-items: center;
justify-content: space-between;
padding: 11px 7px 11px 23px;
width: calc(100% - 30px);
background: #f5f6fa;
border-radius: 6px;
&__name,
&__description {
font-weight: normal;
font-size: 16px;
line-height: 19px;
color: #384b65;
margin: 0;
}
&__name {
font-weight: bold;
}
}
&__name-area {
margin-bottom: 35px;
}
&__name-editing,
&__description-editing {
display: flex;
align-items: center;
width: calc(100% - 7px);
border-radius: 6px;
background-color: #f5f6fa;
padding-right: 7px;
&__input {
font-weight: normal;
font-size: 16px;
line-height: 21px;
flex: 1;
height: 48px;
width: available;
text-indent: 20px;
background-color: #f5f6fa;
border-color: #f5f6fa;
border-radius: 6px;
&::placeholder {
opacity: 0.6;
}
}
&__limit {
font-size: 14px;
line-height: 21px;
color: rgba(0, 0, 0, 0.3);
margin: 0 0 0 15px;
min-width: 53px;
text-align: right;
}
&__save-button {
margin-left: 15px;
}
}
&__name-editing {
margin-bottom: 35px;
}
}
}
}
</style>

View File

@ -14,7 +14,6 @@
Request Limit Increase ->
</a>
</div>
<ProjectDetails/>
<ProjectUsage/>
<BucketArea/>
</div>
@ -24,7 +23,6 @@
import { Component, Vue } from 'vue-property-decorator';
import BucketArea from '@/components/project/buckets/BucketArea.vue';
import ProjectDetails from '@/components/project/ProjectDetails.vue';
import ProjectUsage from '@/components/project/usage/ProjectUsage.vue';
import { RouteConfig } from '@/router';
@ -35,7 +33,6 @@ import { MetaUtils } from '@/utils/meta';
@Component({
components: {
BucketArea,
ProjectDetails,
ProjectUsage,
},
})

View File

@ -1,208 +0,0 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="project-details">
<div class="project-details__title-area">
<h1 class="project-details__title-area__title">{{ name }}</h1>
<span class="project-details__title-area__edit" v-if="!isEditing" @click="toggleEditing">
Edit Description
</span>
</div>
<p class="project-details__description" v-if="!isEditing">{{ displayedDescription }}</p>
<div class="project-details__editing" v-else>
<input
class="project-details__editing__input"
placeholder="Enter a description for your project. Descriptions are limited to 100 characters."
@input="onInput"
@change="onInput"
v-model="value"
/>
<span class="project-details__editing__limit">{{value.length}}/{{MAX_SYMBOLS}}</span>
<VButton
class="project-details__editing__cancel-button"
label="Cancel"
width="73px"
height="33px"
:on-press="toggleEditing"
is-white="true"
/>
<VButton
class="project-details__editing__save-button"
label="Save"
width="75px"
height="35px"
:on-press="onSaveButtonClick"
/>
</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 { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { UpdateProjectFields } from '@/types/projects';
@Component({
components: {
VButton,
HeaderedInput,
},
})
export default class ProjectDetails extends Vue {
public readonly MAX_SYMBOLS: number = 100;
public isEditing: boolean = false;
public value: string = '';
/**
* Returns selected project name.
*/
public get name(): string {
return this.$store.getters.selectedProject.name;
}
/**
* Returns selected project description from store.
*/
public get storedDescription(): string {
return this.$store.getters.selectedProject.description;
}
/**
* Returns displayed project description on UI.
*/
public get displayedDescription(): string {
return this.storedDescription ?
this.storedDescription :
'No description yet. Please enter some information about the project if any.';
}
/**
* Triggers on input.
*/
public onInput({ target }): void {
if (target.value.length < this.MAX_SYMBOLS) {
this.value = target.value;
return;
}
this.value = target.value.slice(0, this.MAX_SYMBOLS);
}
/**
* Updates project description.
*/
public async onSaveButtonClick(): Promise<void> {
try {
const updatedProject = new UpdateProjectFields('', this.value);
await this.$store.dispatch(PROJECTS_ACTIONS.UPDATE_DESCRIPTION, updatedProject);
} catch (error) {
await this.$notify.error(`Unable to update project description. ${error.message}`);
return;
}
this.toggleEditing();
await this.$notify.success('Project updated successfully!');
}
/**
* Toggles project description editing state.
*/
public toggleEditing(): void {
this.isEditing = !this.isEditing;
this.value = this.storedDescription;
}
}
</script>
<style scoped lang="scss">
h1,
p {
margin: 0;
}
.project-details {
padding: 35px;
width: calc(100% - 70px);
font-family: 'font_regular', sans-serif;
background-color: #fff;
border-radius: 6px;
margin-bottom: 36px;
&__title-area {
display: flex;
align-items: center;
justify-content: space-between;
&__title {
font-size: 22px;
line-height: 27px;
color: #000;
word-break: break-all;
}
&__edit {
font-size: 14px;
line-height: 14px;
color: #909ba8;
cursor: pointer;
&:hover {
color: #2683ff;
}
}
}
&__description {
margin-top: 30px;
font-size: 16px;
line-height: 24px;
color: #354049;
width: available;
overflow-y: scroll;
word-break: break-word;
max-height: 100px;
}
&__editing {
display: flex;
align-items: center;
margin-top: 20px;
width: calc(100% - 7px);
border-radius: 6px;
background-color: #f5f6fa;
padding-right: 7px;
&__input {
font-weight: normal;
font-size: 16px;
line-height: 21px;
flex: 1;
height: 48px;
width: available;
text-indent: 20px;
background-color: #f5f6fa;
border-color: #f5f6fa;
border-radius: 6px;
}
&__limit {
font-size: 14px;
line-height: 21px;
color: rgba(0, 0, 0, 0.3);
margin: 0 15px;
}
&__save-button {
margin-left: 10px;
}
}
}
</style>

View File

@ -46,7 +46,7 @@
width='205px'
height='48px'
:on-press="onClose"
is-white="true"
is-transparent="true"
/>
<VButton
label='Add Team Members'

View File

@ -38,7 +38,7 @@
label="Cancel"
width="122px"
height="48px"
is-white="true"
is-transparent="true"
:on-press="onClearSelection"
/>
<span class="header-selected-members__info-text"><b>{{selectedProjectMembersCount}}</b> users selected</span>
@ -58,7 +58,7 @@
label="Cancel"
width="122px"
height="48px"
is-white="true"
is-transparent="true"
:on-press="onClearSelection"
/>
</div>

View File

@ -13,6 +13,7 @@ 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 EditProjectDetails from '@/components/project/EditProjectDetails.vue';
import ProjectDashboard from '@/components/project/ProjectDashboard.vue';
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
@ -40,6 +41,7 @@ export abstract class RouteConfig {
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');
public static EditProjectDetails = new NavigationLink('/edit-project-details', 'Edit Project Details');
// child paths
public static Settings = new NavigationLink('settings', 'Settings');
@ -158,6 +160,11 @@ export const router = new Router({
name: RouteConfig.CreateProject.name,
component: CreateProject,
},
{
path: RouteConfig.EditProjectDetails.path,
name: RouteConfig.EditProjectDetails.name,
component: EditProjectDetails,
},
],
},
{

View File

@ -3,11 +3,10 @@
import { StoreModule } from '@/store';
import {
CreateProjectFields,
Project,
ProjectFields,
ProjectLimits,
ProjectsApi,
UpdateProjectFields,
} from '@/types/projects';
export const PROJECTS_ACTIONS = {
@ -101,10 +100,10 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
state.selectedProject = selected;
},
[UPDATE_PROJECT_NAME](state: ProjectsState, fieldsToUpdate: UpdateProjectFields): void {
[UPDATE_PROJECT_NAME](state: ProjectsState, fieldsToUpdate: ProjectFields): void {
state.selectedProject.name = fieldsToUpdate.name;
},
[UPDATE_PROJECT_DESCRIPTION](state: ProjectsState, fieldsToUpdate: UpdateProjectFields): void {
[UPDATE_PROJECT_DESCRIPTION](state: ProjectsState, fieldsToUpdate: ProjectFields): void {
state.selectedProject.description = fieldsToUpdate.description;
},
[REMOVE](state: ProjectsState, projectID: string): void {
@ -131,8 +130,8 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
return projects;
},
[CREATE]: async function ({commit}: any, createProjectModel: CreateProjectFields): Promise<Project> {
const project = await api.create(createProjectModel);
[CREATE]: async function ({commit}: any, createProjectFields: ProjectFields): Promise<Project> {
const project = await api.create(createProjectFields);
commit(ADD, project);
@ -141,12 +140,12 @@ export function makeProjectsModule(api: ProjectsApi): StoreModule<ProjectsState>
[SELECT]: function ({commit}: any, projectID: string): void {
commit(SELECT_PROJECT, projectID);
},
[UPDATE_NAME]: async function ({commit, state}: any, fieldsToUpdate: UpdateProjectFields): Promise<void> {
[UPDATE_NAME]: async function ({commit, state}: any, fieldsToUpdate: ProjectFields): Promise<void> {
await api.update(state.selectedProject.id, fieldsToUpdate.name, state.selectedProject.description);
commit(UPDATE_PROJECT_NAME, fieldsToUpdate);
},
[UPDATE_DESCRIPTION]: async function ({commit, state}: any, fieldsToUpdate: UpdateProjectFields): Promise<void> {
[UPDATE_DESCRIPTION]: async function ({commit, state}: any, fieldsToUpdate: ProjectFields): Promise<void> {
await api.update(state.selectedProject.id, state.selectedProject.name, fieldsToUpdate.description);
commit(UPDATE_PROJECT_DESCRIPTION, fieldsToUpdate);

View File

@ -11,7 +11,7 @@ export interface ProjectsApi {
* @param createProjectFields - contains project information
* @throws Error
*/
create(createProjectFields: CreateProjectFields): Promise<Project>;
create(createProjectFields: ProjectFields): Promise<Project>;
/**
* Fetch projects.
*
@ -46,6 +46,16 @@ export interface ProjectsApi {
getLimits(projectId: string): Promise<ProjectLimits>;
}
/**
* MAX_NAME_LENGTH defines maximum amount of symbols for project name.
*/
export const MAX_NAME_LENGTH = 20;
/**
* MAX_DESCRIPTION_LENGTH defines maximum amount of symbols for project description.
*/
export const MAX_DESCRIPTION_LENGTH = 100;
/**
* Project is a type, used for creating new project in backend.
*/
@ -61,21 +71,9 @@ export class Project {
}
/**
* UpdateProjectFields is a type, used for updating project name or description.
* ProjectFields is a type, used for creating and updating project.
*/
export class UpdateProjectFields {
public constructor(
public name: string,
public description: string,
) {}
}
/**
* CreateProjectFields is a type, used for creating project.
*/
export class CreateProjectFields {
private readonly MAX_NAME_LENGTH = 20;
export class ProjectFields {
public constructor(
public name: string = '',
public description: string = '',
@ -105,7 +103,7 @@ export class CreateProjectFields {
* 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!');
if (this.name.length > MAX_NAME_LENGTH) throw new Error('Name should be less than 21 character!');
}
}

View File

@ -0,0 +1,5 @@
<svg width="3" height="15" viewBox="0 0 3 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.72723 1.36362C2.72723 2.11672 2.11672 2.72723 1.36362 2.72723C0.610511 2.72723 0 2.11672 0 1.36362C0 0.610511 0.610511 0 1.36362 0C2.11672 0 2.72723 0.610511 2.72723 1.36362Z" fill="#2683FF"/>
<path d="M2.72723 7.49922C2.72723 8.25232 2.11672 8.86283 1.36362 8.86283C0.610511 8.86283 0 8.25232 0 7.49922C0 6.74611 0.610511 6.1356 1.36362 6.1356C2.11672 6.1356 2.72723 6.74611 2.72723 7.49922Z" fill="#2683FF"/>
<path d="M1.36362 15C2.11672 15 2.72723 14.3895 2.72723 13.6364C2.72723 12.8833 2.11672 12.2728 1.36362 12.2728C0.610511 12.2728 0 12.8833 0 13.6364C0 14.3895 0.610511 15 1.36362 15Z" fill="#2683FF"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

View File

@ -0,0 +1,3 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.43444 0L12.13 2.69556L3.50422 11.3213L0 12.13L0.808667 8.62578L9.43444 0Z" fill="#2582FF"/>
</svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@ -1,7 +1,7 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { CreateProjectFields, Project, ProjectLimits, ProjectsApi } from '@/types/projects';
import { Project, ProjectFields, ProjectLimits, ProjectsApi } from '@/types/projects';
/**
* Mock for ProjectsApi
@ -18,7 +18,7 @@ export class ProjectsApiMock implements ProjectsApi {
this.mockLimits = mockLimits;
}
create(createProjectFields: CreateProjectFields): Promise<Project> {
create(createProjectFields: ProjectFields): Promise<Project> {
throw new Error('not implemented');
}

View File

@ -0,0 +1,69 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import { VNode } from 'vue';
import { DirectiveBinding } from 'vue/types/options';
import Vuex from 'vuex';
import EditProjectDropdown from '@/components/navigation/EditProjectDropdown.vue';
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
import { Project } from '@/types/projects';
import { createLocalVue, mount } from '@vue/test-utils';
import { ProjectsApiMock } from '../mock/api/projects';
const localVue = createLocalVue();
const projectsApi = new ProjectsApiMock();
const projectsModule = makeProjectsModule(projectsApi);
const store = new Vuex.Store({ modules: { projectsModule }});
const project = new Project('id', 'test', 'test', 'test', 'ownedId', false);
let clickOutsideEvent: EventListener;
localVue.directive('cli' +
'ck-outside', {
bind: function (el: HTMLElement, binding: DirectiveBinding, vnode: VNode) {
clickOutsideEvent = function(event: Event): void {
if (el === event.target) {
return;
}
if (vnode.context) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', clickOutsideEvent);
},
unbind: function(): void {
document.body.removeEventListener('click', clickOutsideEvent);
},
});
localVue.use(Vuex);
store.commit(PROJECTS_MUTATIONS.ADD, project);
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
describe('EditProjectDropdown', () => {
it('renders correctly', (): void => {
const wrapper = mount(EditProjectDropdown, {
store,
localVue,
});
expect(wrapper).toMatchSnapshot();
});
it('dropdown opens correctly', async (): Promise<void> => {
const wrapper = mount(EditProjectDropdown, {
store,
localVue,
});
await wrapper.find('.edit-project__selection-area').trigger('click');
expect(wrapper).toMatchSnapshot();
});
});

View File

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditProjectDropdown dropdown opens correctly 1`] = `
<div class="edit-project">
<div class="edit-project__selection-area active">
<h1 class="edit-project__selection-area__name">test</h1> <svg width="3" height="15" viewBox="0 0 3 15" fill="none" xmlns="http://www.w3.org/2000/svg" class="edit-project__selection-area__image">
<path d="M2.72723 1.36362C2.72723 2.11672 2.11672 2.72723 1.36362 2.72723C0.610511 2.72723 0 2.11672 0 1.36362C0 0.610511 0.610511 0 1.36362 0C2.11672 0 2.72723 0.610511 2.72723 1.36362Z" fill="#2683FF"></path>
<path d="M2.72723 7.49922C2.72723 8.25232 2.11672 8.86283 1.36362 8.86283C0.610511 8.86283 0 8.25232 0 7.49922C0 6.74611 0.610511 6.1356 1.36362 6.1356C2.11672 6.1356 2.72723 6.74611 2.72723 7.49922Z" fill="#2683FF"></path>
<path d="M1.36362 15C2.11672 15 2.72723 14.3895 2.72723 13.6364C2.72723 12.8833 2.11672 12.2728 1.36362 12.2728C0.610511 12.2728 0 12.8833 0 13.6364C0 14.3895 0.610511 15 1.36362 15Z" fill="#2683FF"></path>
</svg>
</div>
<div class="edit-project__dropdown">
<div class="edit-project__dropdown__choice"><svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.43444 0L12.13 2.69556L3.50422 11.3213L0 12.13L0.808667 8.62578L9.43444 0Z" fill="#2582FF"></path>
</svg>
<p class="edit-project__dropdown__choice__label">Edit Details</p>
</div>
</div>
</div>
`;
exports[`EditProjectDropdown renders correctly 1`] = `
<div class="edit-project">
<div class="edit-project__selection-area">
<h1 class="edit-project__selection-area__name">test</h1> <svg width="3" height="15" viewBox="0 0 3 15" fill="none" xmlns="http://www.w3.org/2000/svg" class="edit-project__selection-area__image">
<path d="M2.72723 1.36362C2.72723 2.11672 2.11672 2.72723 1.36362 2.72723C0.610511 2.72723 0 2.11672 0 1.36362C0 0.610511 0.610511 0 1.36362 0C2.11672 0 2.72723 0.610511 2.72723 1.36362Z" fill="#2683FF"></path>
<path d="M2.72723 7.49922C2.72723 8.25232 2.11672 8.86283 1.36362 8.86283C0.610511 8.86283 0 8.25232 0 7.49922C0 6.74611 0.610511 6.1356 1.36362 6.1356C2.11672 6.1356 2.72723 6.74611 2.72723 7.49922Z" fill="#2683FF"></path>
<path d="M1.36362 15C2.11672 15 2.72723 14.3895 2.72723 13.6364C2.72723 12.8833 2.11672 12.2728 1.36362 12.2728C0.610511 12.2728 0 12.8833 0 13.6364C0 14.3895 0.610511 15 1.36362 15Z" fill="#2683FF"></path>
</svg>
</div>
<!---->
</div>
`;

View File

@ -4,6 +4,7 @@ exports[`NavigationArea snapshot not changed during onboarding tour 1`] = ``;
exports[`NavigationArea snapshot not changed with project 1`] = `
<div class="navigation-area">
<editprojectdropdown-stub></editprojectdropdown-stub>
<router-link-stub to="/project-dashboard" tag="a" ariacurrentvalue="page" event="click" aria-label="Dashboard" class="navigation-area__item-container">
<div class="navigation-area__item-container__link">
<anonymous-stub></anonymous-stub>

View File

@ -3,7 +3,7 @@
import Vuex from 'vuex';
import ProjectDetails from '@/components/project/ProjectDetails.vue';
import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
import { makeProjectsModule, PROJECTS_MUTATIONS } from '@/store/modules/projects';
import { Project } from '@/types/projects';
@ -24,12 +24,12 @@ const projectsModule = makeProjectsModule(projectsApi);
const store = new Vuex.Store({ modules: { projectsModule }});
const project = new Project('id', 'test', 'test', 'test', 'ownedId', false);
describe('ProjectDetails.vue', () => {
describe('EditProjectDetails.vue', () => {
it('renders correctly', (): void => {
store.commit(PROJECTS_MUTATIONS.ADD, project);
store.commit(PROJECTS_MUTATIONS.SELECT_PROJECT, project.id);
const wrapper = mount(ProjectDetails, {
const wrapper = mount(EditProjectDetails, {
store,
localVue,
});
@ -37,20 +37,41 @@ describe('ProjectDetails.vue', () => {
expect(wrapper).toMatchSnapshot();
});
it('editing works correctly', async (): Promise<void> => {
const wrapper = mount(ProjectDetails, {
it('editing name works correctly', async (): Promise<void> => {
const wrapper = mount(EditProjectDetails, {
store,
localVue,
});
await wrapper.vm.toggleEditing();
await wrapper.vm.toggleNameEditing();
expect(wrapper).toMatchSnapshot();
wrapper.vm.$data.value = 'new description';
await wrapper.vm.onSaveButtonClick();
const newName = 'new name';
wrapper.vm.$data.nameValue = newName;
await wrapper.vm.onSaveNameButtonClick();
expect(wrapper).toMatchSnapshot();
await expect(wrapper.vm.storedDescription).toMatch('new description');
await expect(store.getters.selectedProject.name).toMatch(newName);
});
it('editing description works correctly', async (): Promise<void> => {
const wrapper = mount(EditProjectDetails, {
store,
localVue,
});
await wrapper.vm.toggleDescriptionEditing();
expect(wrapper).toMatchSnapshot();
const newDescription = 'new description';
wrapper.vm.$data.descriptionValue = newDescription;
await wrapper.vm.onSaveDescriptionButtonClick();
expect(wrapper).toMatchSnapshot();
await expect(store.getters.selectedProject.description).toMatch(newDescription);
});
});

View File

@ -31,7 +31,7 @@ exports[`CreateProject.vue renders correctly 1`] = `
<!---->
</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 transparent" 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>
<!---->

View File

@ -0,0 +1,114 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditProjectDetails.vue editing description works correctly 1`] = `
<div class="project-details">
<div class="project-details__wrapper">
<p class="project-details__wrapper__back">&lt;- Back</p>
<div class="project-details__wrapper__container">
<h1 class="project-details__wrapper__container__title">Project Details</h1>
<p class="project-details__wrapper__container__label">Name</p>
<div class="project-details__wrapper__container__name-area">
<p class="project-details__wrapper__container__name-area__name">new name</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
<p class="project-details__wrapper__container__label">Description</p>
<!---->
<div class="project-details__wrapper__container__description-editing"><input placeholder="Enter a description for your project" class="project-details__wrapper__container__description-editing__input"> <span class="project-details__wrapper__container__description-editing__limit">4/100</span>
<div class="project-details__wrapper__container__description-editing__save-button container" style="width: 66px; height: 30px;"><span class="label">Save</span></div>
</div>
</div>
</div>
</div>
`;
exports[`EditProjectDetails.vue editing description works correctly 2`] = `
<div class="project-details">
<div class="project-details__wrapper">
<p class="project-details__wrapper__back">&lt;- Back</p>
<div class="project-details__wrapper__container">
<h1 class="project-details__wrapper__container__title">Project Details</h1>
<p class="project-details__wrapper__container__label">Name</p>
<div class="project-details__wrapper__container__name-area">
<p class="project-details__wrapper__container__name-area__name">new name</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
<p class="project-details__wrapper__container__label">Description</p>
<div class="project-details__wrapper__container__description-area">
<p class="project-details__wrapper__container__description-area__description">new description</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
</div>
</div>
</div>
`;
exports[`EditProjectDetails.vue editing name works correctly 1`] = `
<div class="project-details">
<div class="project-details__wrapper">
<p class="project-details__wrapper__back">&lt;- Back</p>
<div class="project-details__wrapper__container">
<h1 class="project-details__wrapper__container__title">Project Details</h1>
<p class="project-details__wrapper__container__label">Name</p>
<!---->
<div class="project-details__wrapper__container__name-editing"><input placeholder="Enter a name for your project" class="project-details__wrapper__container__name-editing__input"> <span class="project-details__wrapper__container__name-editing__limit">4/20</span>
<div class="project-details__wrapper__container__name-editing__save-button container" style="width: 66px; height: 30px;"><span class="label">Save</span></div>
</div>
<p class="project-details__wrapper__container__label">Description</p>
<div class="project-details__wrapper__container__description-area">
<p class="project-details__wrapper__container__description-area__description">test</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
</div>
</div>
</div>
`;
exports[`EditProjectDetails.vue editing name works correctly 2`] = `
<div class="project-details">
<div class="project-details__wrapper">
<p class="project-details__wrapper__back">&lt;- Back</p>
<div class="project-details__wrapper__container">
<h1 class="project-details__wrapper__container__title">Project Details</h1>
<p class="project-details__wrapper__container__label">Name</p>
<div class="project-details__wrapper__container__name-area">
<p class="project-details__wrapper__container__name-area__name">new name</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
<p class="project-details__wrapper__container__label">Description</p>
<div class="project-details__wrapper__container__description-area">
<p class="project-details__wrapper__container__description-area__description">test</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
</div>
</div>
</div>
`;
exports[`EditProjectDetails.vue renders correctly 1`] = `
<div class="project-details">
<div class="project-details__wrapper">
<p class="project-details__wrapper__back">&lt;- Back</p>
<div class="project-details__wrapper__container">
<h1 class="project-details__wrapper__container__title">Project Details</h1>
<p class="project-details__wrapper__container__label">Name</p>
<div class="project-details__wrapper__container__name-area">
<p class="project-details__wrapper__container__name-area__name">test</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
<p class="project-details__wrapper__container__label">Description</p>
<div class="project-details__wrapper__container__description-area">
<p class="project-details__wrapper__container__description-area__description">test</p>
<div class="container white" style="width: 64px; height: 28px;"><span class="label">Edit</span></div>
</div>
<!---->
</div>
</div>
</div>
`;

View File

@ -7,7 +7,6 @@ exports[`ProjectDashboard.vue renders correctly 1`] = `
Request Limit Increase -&gt;
</a>
</div>
<projectdetails-stub></projectdetails-stub>
<projectusage-stub></projectusage-stub>
<bucketarea-stub></bucketarea-stub>
</div>

View File

@ -1,36 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ProjectDetails.vue editing works correctly 1`] = `
<div class="project-details">
<div class="project-details__title-area">
<h1 class="project-details__title-area__title">test</h1>
<!---->
</div>
<div class="project-details__editing"><input placeholder="Enter a description for your project. Descriptions are limited to 100 characters." class="project-details__editing__input"> <span class="project-details__editing__limit">4/100</span>
<div class="project-details__editing__cancel-button container white" style="width: 73px; height: 33px;"><span class="label">Cancel</span></div>
<div class="project-details__editing__save-button container" style="width: 75px; height: 35px;"><span class="label">Save</span></div>
</div>
</div>
`;
exports[`ProjectDetails.vue editing works correctly 2`] = `
<div class="project-details">
<div class="project-details__title-area">
<h1 class="project-details__title-area__title">test</h1> <span class="project-details__title-area__edit">
Edit Description
</span>
</div>
<p class="project-details__description">new description</p>
</div>
`;
exports[`ProjectDetails.vue renders correctly 1`] = `
<div class="project-details">
<div class="project-details__title-area">
<h1 class="project-details__title-area__title">test</h1> <span class="project-details__title-area__edit">
Edit Description
</span>
</div>
<p class="project-details__description">test</p>
</div>
`;

View File

@ -38,7 +38,7 @@ exports[`Team HeaderArea renders correctly with 1 selected user and delete click
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>1</b> user?</span>
<div class="header-after-delete-click__button-area">
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" istransparent="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
</div>
</div>
</vheader-stub>
@ -64,7 +64,7 @@ exports[`Team HeaderArea renders correctly with 2 selected users and delete clic
<div class="header-after-delete-click"><span class="header-after-delete-click__delete-confirmation">Are you sure you want to delete <b>2</b> users?</span>
<div class="header-after-delete-click__button-area">
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" istransparent="true" onpress="function () { [native code] }" class="button"></vbutton-stub>
</div>
</div>
</vheader-stub>
@ -111,7 +111,7 @@ exports[`Team HeaderArea renders correctly with selected users 1`] = `
<!---->
<div class="header-selected-members">
<vbutton-stub label="Delete" width="122px" height="48px" onpress="function () { [native code] }" class="button deletion"></vbutton-stub>
<vbutton-stub label="Cancel" width="122px" height="48px" iswhite="true" onpress="function () { [native code] }" class="button"></vbutton-stub> <span class="header-selected-members__info-text"><b>2</b> users selected</span>
<vbutton-stub label="Cancel" width="122px" height="48px" istransparent="true" onpress="function () { [native code] }" class="button"></vbutton-stub> <span class="header-selected-members__info-text"><b>2</b> users selected</span>
</div>
<!---->
</vheader-stub>

View File

@ -5,7 +5,7 @@ import Vuex from 'vuex';
import { ProjectsApiGql } from '@/api/projects';
import { makeProjectsModule, PROJECTS_ACTIONS, PROJECTS_MUTATIONS } from '@/store/modules/projects';
import { Project, ProjectLimits, UpdateProjectFields } from '@/types/projects';
import { Project, ProjectFields, ProjectLimits } from '@/types/projects';
import { createLocalVue } from '@vue/test-utils';
const Vue = createLocalVue();
@ -237,7 +237,7 @@ describe('actions', () => {
state.projects = projects;
const newName = 'newName';
const fieldsToUpdate = new UpdateProjectFields(newName, state.projects[0].description);
const fieldsToUpdate = new ProjectFields(newName, state.projects[0].description);
await store.dispatch(UPDATE_NAME, fieldsToUpdate);
@ -251,7 +251,7 @@ describe('actions', () => {
state.projects = projects;
const newDescription = 'newDescription1';
const fieldsToUpdate = new UpdateProjectFields(state.projects[0].name, newDescription);
const fieldsToUpdate = new ProjectFields(state.projects[0].name, newDescription);
await store.dispatch(UPDATE_DESCRIPTION, fieldsToUpdate);
@ -263,7 +263,7 @@ describe('actions', () => {
state.projects = projects;
const newDescription = 'newDescription2';
const fieldsToUpdate = new UpdateProjectFields(state.projects[0].name, newDescription);
const fieldsToUpdate = new ProjectFields(state.projects[0].name, newDescription);
try {
await store.dispatch(UPDATE_DESCRIPTION, fieldsToUpdate);