web/satellite: Implement New Create Access Grant Wizard. (#4821)

Created and style modal-added open and close functionality
Added logic to access grants selection type
Changed CLI to API-Changed Types to radio buttons-Added date picker-Added buckets list(not yet populating with buckets)
Added permissions check box logic and new input field for buckets.
Added tooltips and hover logic for the tooltips
Added acknoledgement functionality to encrypt step
Added conditional logic to encrypt access button and the ascknoledgement box - removed blank comments from paymentMethods snapshot
Added logic to retrieve restricted api key, access credentials, and satellite address
Added copy functionality and download functionality for the credentials step

Change-Id: I8c8f02bc1ee38c3df42396cbd9bb3db2e7ff9cc4

Co-authored-by: cl-mitch <mitch.george@compozelabs.com>
This commit is contained in:
hovex023 2022-06-03 13:03:36 -05:00 committed by GitHub
parent 82e3932dbd
commit 688b3907ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 925 additions and 126 deletions

View File

@ -35,14 +35,20 @@
<div class="access-grants__flows-area__title">Access Grant</div>
<div class="access-grants__flows-area__summary">Gives access through native clients such as uplink, libuplink, associate libraries, and bindings. </div>
<div class="access-grants__flows-area__button-container">
<VButton
label="Learn More"
width="auto"
height="30px"
is-transparent="true"
font-size="13px"
class="access-grants__flows-area__learn-button"
/>
<a
href="https://docs.storj.io/dcs/concepts/access/access-grants/"
target="_blank"
rel="noopener noreferrer"
>
<VButton
label="Learn More"
width="auto"
height="30px"
is-transparent="true"
font-size="13px"
class="access-grants__flows-area__learn-button"
/>
</a>
<VButton
label="Create Access Grant"
font-size="13px"
@ -61,14 +67,20 @@
<div class="access-grants__flows-area__summary">Gives access through S3 compatible tools and services via our hosted Gateway MT.</div>
<br>
<div class="access-grants__flows-area__button-container">
<VButton
label="Learn More"
width="auto"
height="30px"
is-transparent="true"
font-size="13px"
class="access-grants__flows-area__learn-button"
/>
<a
href="https://docs.storj.io/dcs/api-reference/s3-compatible-gateway"
target="_blank"
rel="noopener noreferrer"
>
<VButton
label="Learn More"
width="auto"
height="30px"
is-transparent="true"
font-size="13px"
class="access-grants__flows-area__learn-button"
/>
</a>
<VButton
label="Create S3 Credentials"
font-size="13px"
@ -87,14 +99,20 @@
<div class="access-grants__flows-area__summary">Use it for generating S3 credentials and access grants programatically. </div>
<br>
<div class="access-grants__flows-area__button-container">
<VButton
label="Learn More"
width="auto"
height="30px"
is-transparent="true"
font-size="13px"
class="access-grants__flows-area__learn-button"
/>
<a
href="https://docs.storj.io/dcs/getting-started/quickstart-uplink-cli/generate-access-grants-and-tokens/generate-a-token/"
target="_blank"
rel="noopener noreferrer"
>
<VButton
label="Learn More"
width="auto"
height="30px"
is-transparent="true"
font-size="13px"
class="access-grants__flows-area__learn-button"
/>
</a>
<VButton
label="Create Keys for CLI"
font-size="13px"
@ -126,6 +144,7 @@
<VList
:data-set="accessGrantsList"
:item-component="itemComponent2"
@openModal="onDeleteClick"
/>
</div>
<div class="access-grants-items2__footer">
@ -149,11 +168,6 @@
No Results Found
</span>
</div>
<ConfirmDeletePopup
v-if="isDeleteClicked"
@close="onClearSelection"
@reset-pagination="resetPagination"
/>
</div>
<div v-if="!isNewAccessGrantFlow">
<VLoader v-if="areGrantsFetching" width="100px" height="100px" class="grants-loader" />
@ -174,18 +188,27 @@
:on-page-click-callback="onPageClick"
/>
</div>
<EmptyState v-if="!accessGrantsList.length && !areGrantsFetching" />
</div>
<div v-if="!isNewAccessGrantFlow">
<ConfirmDeletePopup
v-if="isDeleteClicked"
@close="onClearSelection"
@reset-pagination="resetPagination"
/>
<CreateAccessModal
v-if="showAccessModal"
:default-type="modalAccessType"
@close-modal="toggleAccessModal"
<EmptyState v-if="!accessGrantsList.length && !areGrantsFetching" />
</div>
<div v-if="isNewAccessGrantFlow">
<ConfirmDeletePopup2
v-if="isDeleteClicked"
@close="onClearSelection"
@resetPagination="resetPagination"
/>
</div>
<CreateAccessModal
v-if="showAccessModal"
:default-type="modalAccessType"
@close-modal="toggleAccessModal"
/>
<router-view />
</div>
</template>
@ -196,6 +219,7 @@ import { MetaUtils } from '@/utils/meta';
import AccessGrantsItem from '@/components/accessGrants/AccessGrantsItem.vue';
import AccessGrantsItem2 from '@/components/accessGrants/AccessGrantsItem2.vue';
import ConfirmDeletePopup from '@/components/accessGrants/ConfirmDeletePopup.vue';
import ConfirmDeletePopup2 from '@/components/accessGrants/ConfirmDeletePopup2.vue';
import EmptyState from '@/components/accessGrants/EmptyState.vue';
import SortAccessGrantsHeader from '@/components/accessGrants/SortingHeader.vue';
import CreateAccessModal from '@/components/accessGrants/CreateAccessModal.vue';
@ -237,6 +261,7 @@ declare interface ResetPagination {
VPagination,
VButton,
ConfirmDeletePopup,
ConfirmDeletePopup2,
VLoader,
CreateAccessModal,
VHeader,
@ -253,6 +278,7 @@ export default class AccessGrants extends Vue {
private modalAccessType = '';
public areGrantsFetching = true;
public $refs!: {
pagination: HTMLElement & ResetPagination;
};
@ -428,10 +454,12 @@ export default class AccessGrants extends Vue {
display: inline-block;
padding: 28px;
width: 26%;
height: 167px;
height: auto;
background: #fff;
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
border-radius: 10px;
min-width: 175px;
margin-bottom: 10px;
}
.access-grants {
@ -465,6 +493,7 @@ export default class AccessGrants extends Vue {
.access-grants__flows-area {
text-align: center;
display: flex;
flex-wrap: wrap;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: justify;
@ -478,17 +507,19 @@ export default class AccessGrants extends Vue {
}
&__learn-button {
margin-right: 2%;
margin: 2px 2% 0 0;
padding: 0 10px;
}
&__create-button {
padding: 0 10px;
margin-top: 2px;
}
&__button-container {
display: flex;
margin-top: 10px;
margin-top: 8px;
flex-wrap: wrap;
}
&__summary {

View File

@ -19,11 +19,18 @@
v-click-outside="popupVisible"
class="popup-menu"
>
<p class="popup-menu__popup-details">
See Details
</p>
<div class="popup-menu__popup-divider" />
<p class="popup-menu__popup-delete">
<p
class="popup-menu__popup-delete"
@mouseenter="isPopupHovered = true"
@mouseleave="isPopupHovered = false"
@click="toggleSelection"
>
<TrashIconWhite
v-if="isPopupHovered"
/>
<TrashIconBlack
v-if="!isPopupHovered"
/>
Delete Access
</p>
</div>
@ -33,31 +40,52 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import TrashIconWhite from '@/../static/images/accessGrants/trashIcon.svg';
import TrashIconBlack from '@/../static/images/accessGrants/trashIcon-black.svg';
import { AccessGrant } from '@/types/accessGrants';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
const {
TOGGLE_SELECTION,
} = ACCESS_GRANTS_ACTIONS;
// @vue/component
@Component({
components: {
TrashIconWhite,
TrashIconBlack,
},
})
export default class AccessGrantsItem extends Vue {
@Prop({ default: new AccessGrant('', '', new Date(), '') })
private readonly itemData: AccessGrant;
private popupVisible = false;
private isPopupHovered = false;
public togglePopupVisibility(): void {
this.popupVisible = !this.popupVisible;
}
/**
* Toggles access grant selection.
*/
public async toggleSelection(): Promise<void> {
await this.$store.dispatch(TOGGLE_SELECTION, this.itemData);
this.$emit('altMethod');
this.togglePopupVisibility();
this.isPopupHovered = false;
}
}
</script>
<style scoped lang="scss">
@mixin popup-menu-button {
padding: 0 15px;
height: 50%;
line-height: 55px;
text-align: left;
height: 100%;
line-height: 50px;
text-align: center;
font-family: 'font_regular', sans-serif;
color: #1b2533;
transition: 100ms;
@ -81,18 +109,6 @@ export default class AccessGrantsItem extends Vue {
}
}
.checkbox-container {
margin-left: 28px;
min-width: 21px;
min-height: 21px;
border-radius: 4px;
border: 1px solid #1b2533;
&__image {
display: none;
}
}
.name-container {
max-width: calc(100% - 131px);
margin-right: 15px;
@ -127,36 +143,19 @@ export default class AccessGrantsItem extends Vue {
.popup-menu {
width: 160px;
height: 100px;
height: 50px;
position: absolute;
right: 70px;
bottom: -90px;
right: 40%;
bottom: -65%;
z-index: 1;
background: #fff;
border-radius: 10px;
box-shadow: 0 20px 34px rgb(10 27 44 / 28%);
&__popup-details {
@include popup-menu-button;
border-radius: 10px 10px 0 0;
&:hover {
background-color: #354049;
cursor: pointer;
color: #fff;
}
}
&__popup-divider {
height: 1px;
background-color: #e5e7eb;
}
&__popup-delete {
@include popup-menu-button;
border-radius: 0 0 10px 10px;
border-radius: 10px;
&:hover {
background-color: #b53737;

View File

@ -0,0 +1,259 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="confirm-delete">
<div class="confirm-delete__container">
<h1 class="confirm-delete__container__title">Delete Access</h1>
<p class="confirm-delete__container__info">
You wont be able to access bucket(s) or object(s) related to the access:
</p>
<div class="confirm-delete__container__list">
<div
v-for="accessGrant in selectedAccessGrants"
:key="accessGrant.id"
class="confirm-delete__container__list__container"
>
<div class="confirm-delete__container__list__container__item">
<p class="confirm-delete__container__list__container__item__name">
{{ accessGrant.name }}
</p>
</div>
</div>
</div>
<div>
<p>This action cannot be undone.</p>
</div>
<headerless-input
placeholder="Type the name of the access"
@setData="setConfirmedInput"
/>
<div class="confirm-delete__container__buttons-area">
<VButton
class="cancel-button"
label="Cancel"
width="70px"
height="44px"
:on-press="onCancelClick"
is-white="true"
:is-disabled="isLoading"
/>
<VButton
label="Delete Access"
width="150px"
height="44px"
:on-press="onDeleteClick"
:is-disabled="isLoading || confirmedInput !== selectedAccessGrants[0].name"
is-solid-delete="true"
has-trash-icon="true"
/>
</div>
<div class="confirm-delete__container__close-cross-container" @click="onCancelClick">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import HeaderlessInput from '@/components/common/HeaderlessInput.vue'
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant } from '@/types/accessGrants';
// @vue/component
@Component({
components: {
VButton,
HeaderlessInput,
CloseCrossIcon,
},
})
export default class ConfirmDeletePopup extends Vue {
private readonly FIRST_PAGE: number = 1;
private isLoading = false;
private confirmedInput = '';
/**
* sets Comfirmed Input property to the given value.
*/
public setConfirmedInput(value: string): void {
this.confirmedInput = value;
}
/**
* Deletes selected access grants, fetches updated list and closes popup.
*/
public async onDeleteClick(): Promise<void> {
if (this.isLoading) return;
this.isLoading = true;
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.DELETE);
await this.$notify.success(`Access Grant deleted successfully`);
} catch (error) {
await this.$notify.error(error.message);
}
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR_SELECTION);
} catch (error) {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`);
}
this.$emit('resetPagination');
this.isLoading = false;
this.onCancelClick();
}
/**
* Closes popup
*/
public onCancelClick(): void {
this.$emit('close');
}
/**
* Returns list of selected access grants from store.
*/
public get selectedAccessGrants(): AccessGrant[] {
return this.$store.getters.selectedAccessGrants;
}
}
</script>
<style scoped lang="scss">
.confirm-delete {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: rgb(27 37 51 / 75%);
display: flex;
align-items: center;
justify-content: center;
font-family: 'font_regular', sans-serif;
font-style: normal;
&__trash-icon {
position: absolute;
left: 57%;
margin-top: -3px;
}
&__text-container {
text-align: left;
}
&__container {
border-radius: 6px;
max-width: 325px;
padding: 40px 30px;
position: relative;
display: flex;
flex-direction: column;
background-color: #fff;
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 28px;
line-height: 34px;
color: #000;
}
&__info {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
margin: 25px 0 10px;
}
&__info-new {
font-weight: normal;
font-size: 16px;
line-height: 21px;
text-align: left;
color: #000;
margin: 20px 0;
}
&__list-label {
font-weight: bold;
font-size: 14px;
line-height: 18px;
color: #e30011;
font-family: 'font_medium', sans-serif;
white-space: nowrap;
margin-bottom: 30px;
}
&__list {
max-height: 255px;
overflow-y: scroll;
border-radius: 6px;
width: 100%;
&__container {
&__item {
padding: 3px 7px;
max-width: fit-content;
background: #d8dee3;
border-radius: 20px;
margin-bottom: 10px;
&__name {
font-family: 'font_medium', sans-serif;
margin: 0;
font-weight: bold;
font-size: 17px;
line-height: 30px;
color: #1b2533;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
&__buttons-area {
width: fit-content;
display: flex;
align-items: center;
margin-top: 30px;
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 30px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
}
.cancel-button {
margin-right: 15px;
}
</style>

View File

@ -124,7 +124,7 @@
<input
:id="`permissions__${item}-check`"
v-model="selectedPermissions"
:value="item"
:value="item"
type="checkbox"
:checked="checkedPermissions.item"
@click="toggleAllPermission(item)"
@ -159,6 +159,7 @@
<DurationSelection
container-style="access-date-container"
text-style="access-date-text"
picker-style="__access-date-container"
/>
</div>
<div
@ -180,21 +181,13 @@
<div class="access-grant__modal-container__divider" />
<div class="access-grant__modal-container__footer-container">
<v-button
label="Learn More"
width="auto"
height="50px"
is-transparent="true"
font-size="16px"
class="access-grant__modal-container__footer-container__learn-more-button"
/>
<v-button
label="Encrypt My Access ⟶"
:label="checkedType === 'api' ? 'Create Keys ⟶' : 'Encrypt My Access ⟶'"
font-size="16px"
width="auto"
height="50px"
class="access-grant__modal-container__footer-container__encrypt-button"
:on-press="encryptClickAction"
:is-disabled="selectedPermissions.length === 0 || accessName === '' || selectedBucketNames.length === 0"
:on-press="checkedType === 'api' ? createAccessGrant : encryptClickAction"
:is-disabled="selectedPermissions.length === 0 || accessName === ''"
/>
</div>
</form>
@ -248,7 +241,6 @@
>
{{ passphrase }}
</div>
<!-- Working Here -->
<input
v-if="encryptSelect === 'create'"
v-model="passphrase"
@ -265,7 +257,7 @@
:is-white-green="isPassphraseCopied ? true : false"
class="access-grant__modal-container__footer-container__copy-button"
font-size="16px"
:on-press="onCopyClick"
:on-press="onCopyPassphraseClick"
:is-disabled="passphrase.length < 1"
/>
<v-button
@ -275,7 +267,7 @@
height="50px"
class="access-grant__modal-container__footer-container__download-button"
:is-green-white="isPassphraseDownloaded ? true : false"
:on-press="downloadText"
:on-press="downloadPassphrase"
:is-disabled="passphrase.length < 1"
/>
</div>
@ -307,10 +299,199 @@
height="50px"
class="access-grant__modal-container__footer-container__download-button"
:is-disabled="!acknowledgementCheck"
:on-press="createAccessGrant"
/>
</div>
</div>
</form>
<!-- ********* Grant Created Modal ********* -->
<form v-if="accessGrantStep === 'grantCreated'">
<div class="access-grant__modal-container__header-container">
<AccessGrantsIcon v-if="checkedType === 'access'" />
<S3Icon v-if="checkedType === 's3'" />
<CLIIcon v-if="checkedType === 'api'" />
<div class="access-grant__modal-container__header-container__close-cross-container" @click="onCloseClick">
<CloseCrossIcon />
</div>
<h2 class="access-grant__modal-container__header-container__title-complete">{{ accessName }} <br> Created</h2>
</div>
<div class="access-grant__modal-container__body-container__created">
<p>Now copy and save the {{ checkedText[checkedType][0] }} will only appear once. Click on the {{ checkedText[checkedType][1] }}</p>
</div>
<div v-if="checkedType === 'access'">
<div class="access-grant__modal-container__generated-credentials__label">
<span class="access-grant__modal-container__generated-credentials__label__text">
Access Grant
</span>
<a
href="https://docs.storj.io/dcs/concepts/access/access-grants/"
target="_blank"
>
<img
class="tooltip-icon"
src="../../../static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div
class="access-grant__modal-container__generated-credentials"
>
<span class="access-grant__modal-container__generated-credentials__text">
{{ access }}
</span>
<img
class="clickable-image"
src="../../../static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(access)"
>
</div>
</div>
<div v-if="checkedType === 's3'">
<div class="access-grant__modal-container__generated-credentials__label">
<span class="access-grant__modal-container__generated-credentials__label__text">
Access Key
</span>
</div>
<div
class="access-grant__modal-container__generated-credentials"
>
<span class="access-grant__modal-container__generated-credentials__text">
{{ gatewayCredentials.accessKeyId }}
</span>
<img
class="clickable-image"
src="../../../static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(gatewayCredentials.accessKeyId)"
>
</div>
<div class="access-grant__modal-container__generated-credentials__label">
<span class="access-grant__modal-container__generated-credentials__label__text">
Secret Key
</span>
</div>
<div
class="access-grant__modal-container__generated-credentials"
>
<span class="access-grant__modal-container__generated-credentials__text">
{{ gatewayCredentials.secretKey }}
</span>
<img
class="clickable-image"
src="../../../static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(gatewayCredentials.secretKey)"
>
</div>
<div class="access-grant__modal-container__generated-credentials__label">
<span class="access-grant__modal-container__generated-credentials__label__text">
Endpoint
</span>
</div>
<div
class="access-grant__modal-container__generated-credentials"
>
<span class="access-grant__modal-container__generated-credentials__text">
{{ gatewayCredentials.endpoint }}
</span>
<img
class="clickable-image"
src="../../../static/images/accessGrants/create-access_copy-icon.png"
target="_blank"
href="https://docs.storj.io/dcs/concepts/satellite/"
@click="onCopyClick(gatewayCredentials.endpoint)"
>
</div>
</div>
<div v-if="checkedType === 'api'">
<div class="access-grant__modal-container__generated-credentials__label">
<span class="access-grant__modal-container__generated-credentials__label__text">
Satellite Address
</span>
<a
href="https://docs.storj.io/dcs/concepts/satellite/"
target="_blank"
>
<img
class="tooltip-icon"
src="../../../static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div
class="access-grant__modal-container__generated-credentials"
>
<span class="access-grant__modal-container__generated-credentials__text">
{{ satelliteAddress }}
</span>
<img
class="clickable-image"
src="../../../static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(satelliteAddress)"
>
</div>
<div class="access-grant__modal-container__generated-credentials__label">
<span class="access-grant__modal-container__generated-credentials__label__text">
API Key
</span>
<a
href="https://docs.storj.io/dcs/concepts/access/access-grants/api-key/"
target="_blank"
>
<img
class="tooltip-icon"
src="../../../static/images/accessGrants/create-access_information.png"
>
</a>
</div>
<div
class="access-grant__modal-container__generated-credentials"
>
<span class="access-grant__modal-container__generated-credentials__text">
{{ restrictedKey }}
</span>
<img
class="clickable-image"
src="../../../static/images/accessGrants/create-access_copy-icon.png"
@click="onCopyClick(restrictedKey)"
>
</div>
</div>
<div v-if="checkedType === 's3'" class="access-grant__modal-container__credential-buttons__container-s3">
<a
v-if="checkedType === 's3'"
href="https://docs.storj.io/dcs/api-reference/s3-compatible-gateway/"
target="_blank"
>
<v-button
label="Learn More"
width="150px"
height="50px"
is-transparent="true"
font-size="16px"
class="access-grant__modal-container__footer-container__learn-more-button"
/>
</a>
<v-button
label="Download .txt"
font-size="16px"
width="182px"
height="50px"
class="access-grant__modal-container__credential-buttons__download-button"
:is-green-white="areCredentialsDownloaded ? true : false"
:on-press="downloadCredentials"
/>
</div>
<div v-if="checkedType !== 's3'" class="access-grant__modal-container__credential-buttons__container">
<v-button
label="Download .txt"
font-size="16px"
width="182px"
height="50px"
class="access-grant__modal-container__credential-buttons__download-button"
:is-green-white="areCredentialsDownloaded ? true : false"
:on-press="downloadCredentials"
/>
</div>
</form>
</div>
</div>
</template>
@ -330,20 +511,32 @@ import PermissionsIcon from '@/../static/images/accessGrants/create-access_permi
import NameIcon from '@/../static/images/accessGrants/create-access_name.svg';
import BucketsIcon from '@/../static/images/accessGrants/create-access_buckets.svg';
import DateIcon from '@/../static/images/accessGrants/create-access_date.svg';
import AccessGrantsIcon from '@/../static/images/accessGrants/accessGrantsIcon.svg';
import CLIIcon from '@/../static/images/accessGrants/cli.svg';
import S3Icon from '@/../static/images/accessGrants/s3.svg';
// for future use when notes is implemented
// import NotesIcon from '@/../static/images/accessGrants/create-access_notes.svg';
import Chevron from '@/../static/images/accessGrants/chevron.svg';
import { Download } from "@/utils/download";
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { generateMnemonic } from "bip39";
import { AccessGrant } from '@/types/accessGrants';
import { Download } from "@/utils/download";
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { BUCKET_ACTIONS } from "@/store/modules/buckets";
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { MetaUtils } from '@/utils/meta';
import { EdgeCredentials } from '@/types/accessGrants';
// @vue/component
@Component({
components: {
VButton,
AccessGrantsIcon,
CLIIcon,
S3Icon,
AccessKeyIcon,
ThumbPrintIcon,
DurationSelection,
@ -368,11 +561,21 @@ export default class CreateAccessModal extends Vue {
private accessGrantList = this.accessGrantsList;
private accessGrantStep = "create";
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
public areKeysVisible = false;
private readonly FIRST_PAGE = 1;
/**
* Stores access type that is selected.
* Stores access type that is selected and text changes based on type.
*/
private checkedType = '';
private checkedText = {access: ['Access Grant as it','information icon to learn more.'], s3: ['S3 credentials as they','Learn More button to access the documentation.'],api: ['Satellite Address and API Key as they','information icons to learn more.']};
private areCredentialsDownloaded = false;
/**
* Global isLoading Variable
**/
private isLoading = false;
/**
* Handles which tooltip is hovered over and set/clear timeout when leaving hover.
@ -384,8 +587,8 @@ export default class CreateAccessModal extends Vue {
* Handles permission types, which have been selected, and determining if all have been selected.
*/
private showAllPermissions = {show: false, position: "up"};
private permissionsList = ["read","write","list","delete"];
private checkedPermissions = {read: false, write: false, list: false, delete: false};
private permissionsList = ["Read","Write","List","Delete"];
private checkedPermissions = {Read: false, Write: false, List: false, Delete: false};
private selectedPermissions : string[] = [];
private allPermissionsClicked = false;
private acknowledgementCheck = false;
@ -403,12 +606,26 @@ export default class CreateAccessModal extends Vue {
public areBucketNamesFetching = true;
private addDateSelected = false;
/**
* Created Access Grant
*/
private createdAccessGrant;
private createdAccessGrantName = "";
private createdAccessGrantSecret = "";
private access = "";
public currentDate = new Date().toISOString();
private worker: Worker;
private restrictedKey = '';
public satelliteAddress: string = MetaUtils.getMetaContent('satellite-nodeurl');
/**
* Checks which type was selected and retrieves buckets on mount.
*/
public async mounted(): Promise<void> {
this.checkedType = this.defaultType;
this.setWorker();
try {
await this.$store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
this.areBucketNamesFetching = false;
@ -417,12 +634,127 @@ export default class CreateAccessModal extends Vue {
}
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
*/
public setWorker(): void {
this.worker = this.$store.state.accessGrantsModule.accessGrantsWebWorker;
this.worker.onerror = (error: ErrorEvent) => {
this.$notify.error(error.message);
};
}
/**
* Creates Access Grant
*/
public async createAccessGrant(): Promise<void> {
if (this.$store.getters.projects.length === 0) {
try {
await this.$store.dispatch(PROJECTS_ACTIONS.CREATE_DEFAULT_PROJECT);
} catch (error) {
this.isLoading = false;
return;
}
}
// creates restricted key
let cleanAPIKey: AccessGrant;
try {
cleanAPIKey = await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.CREATE, this.accessName);
} catch (error) {
await this.$notify.error(error.message);
return;
}
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.FETCH, this.FIRST_PAGE);
} catch (error) {
await this.$notify.error(`Unable to fetch Access Grants. ${error.message}`);
this.isLoading = false;
}
let permissionsMsg = {
'type': 'SetPermission',
'buckets': this.selectedBucketNames,
'apiKey': cleanAPIKey.secret,
'isDownload': this.selectedPermissions.includes('Read'),
'isUpload': this.selectedPermissions.includes('Write'),
'isList': this.selectedPermissions.includes('List'),
'isDelete': this.selectedPermissions.includes('Delete'),
}
if (this.notBeforePermission) permissionsMsg = Object.assign(permissionsMsg, {'notBefore': this.notBeforePermission.toISOString()});
if (this.notAfterPermission) permissionsMsg = Object.assign(permissionsMsg, {'notAfter': this.notAfterPermission.toISOString()});
await this.worker.postMessage(permissionsMsg);
const grantEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (grantEvent.data.error) {
throw new Error(grantEvent.data.error)
}
this.restrictedKey = grantEvent.data.value;
// creates access credentials
const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl');
this.worker.postMessage({
'type': 'GenerateAccess',
'apiKey': this.restrictedKey,
'passphrase': this.passphrase,
'projectID': this.$store.getters.selectedProject.id,
'satelliteNodeURL': satelliteNodeURL,
});
const accessEvent: MessageEvent = await new Promise(resolve => this.worker.onmessage = resolve);
if (accessEvent.data.error) {
await this.$notify.error(accessEvent.data.error);
this.isLoading = false;
return;
}
this.access = accessEvent.data.value;
await this.$notify.success('Access Grant was generated successfully');
if (this.checkedType === 's3') {
try {
await this.$store.dispatch(ACCESS_GRANTS_ACTIONS.GET_GATEWAY_CREDENTIALS, {accessGrant: this.access});
await this.$notify.success('Gateway credentials were generated successfully');
await this.analytics.eventTriggered(AnalyticsEvent.GATEWAY_CREDENTIALS_CREATED);
this.areKeysVisible = true;
} catch (error) {
await this.$notify.error(error.message);
}
}
this.accessGrantStep = 'grantCreated';
}
/**
* Downloads passphrase to .txt file
*/
public downloadText(): void {
public downloadPassphrase(): void {
this.isPassphraseDownloaded = true;
Download.file(this.passphrase, 'sampleText.txt')
Download.file(this.passphrase, `passphrase-${this.currentDate}.txt`)
}
/**
* Downloads credentials to .txt file
*/
public downloadCredentials(): void {
let credentialMap = {
access: [`access grant: ${this.access}`],
s3: [`access key: ${this.gatewayCredentials.accessKeyId}\nsecret key: ${this.gatewayCredentials.secretKey}\nendpoint: ${this.gatewayCredentials.endpoint}`],
api: [`satellite address: ${this.satelliteAddress}\nrestricted key: ${this.restrictedKey}`]
}
this.areCredentialsDownloaded = true;
Download.file(credentialMap[this.checkedType], `${this.checkedType}-credentials-${this.currentDate}.txt`)
}
public onRadioInput(): void {
@ -443,13 +775,19 @@ export default class CreateAccessModal extends Vue {
return
} else if (this.checkedType !== "api") {
this.accessGrantStep = 'encrypt';
}
}
}
public onCopyClick(): void {
public onCopyClick(item): void {
this.$copyText(item);
this.$notify.success(`credential was copied successfully`);
}
public onCopyPassphraseClick(): void {
this.$copyText(this.passphrase);
this.isPassphraseCopied = true;
this.$notify.success('Passphrase was copied successfully');
this.$notify.success(`Passphrase was copied successfully`);
return;
}
public backAction(): void {
@ -505,12 +843,12 @@ export default class CreateAccessModal extends Vue {
if (type === 'all' && this.allPermissionsClicked === false) {
this.allPermissionsClicked = true;
this.selectedPermissions = this.permissionsList;
this.checkedPermissions = {read: true, write: true, list: true, delete: true}
this.checkedPermissions = {Read: true, Write: true, List: true, Delete: true}
return
} else if(type === 'all' && this.allPermissionsClicked === true) {
this.allPermissionsClicked = false;
this.selectedPermissions = []
this.checkedPermissions = {read: false, write: false, list: false, delete: false}
this.checkedPermissions = {Read: false, Write: false, List: false, Delete: false}
return
} else if(this.checkedPermissions[type] === true) {
this.checkedPermissions[type] = false
@ -518,10 +856,11 @@ export default class CreateAccessModal extends Vue {
return
} else {
this.checkedPermissions[type] = true
if(this.checkedPermissions.read === true && this.checkedPermissions.write === true && this.checkedPermissions.list === true && this.checkedPermissions.delete === true) {
if(this.checkedPermissions.Read === true && this.checkedPermissions.Write === true && this.checkedPermissions.List === true && this.checkedPermissions.Delete === true) {
this.allPermissionsClicked = true
return
}
return;
}
}
@ -552,6 +891,13 @@ export default class CreateAccessModal extends Vue {
public get accessGrantsList(): AccessGrant[] {
return this.$store.state.accessGrantsModule.page.accessGrants;
}
/**
* Returns generated gateway credentials from store.
*/
public get gatewayCredentials(): EdgeCredentials {
return this.$store.state.accessGrantsModule.gatewayCredentials;
}
}
</script>
@ -595,6 +941,15 @@ export default class CreateAccessModal extends Vue {
margin-bottom: -20px;
}
@mixin generated-text {
margin-top: 20px;
align-items: center;
padding: 10px 16px;
background: #ebeef1;
border: 1px solid #c8d3de;
border-radius: 7px;
}
p {
font-weight: bold;
padding-bottom: 5px;
@ -618,6 +973,10 @@ export default class CreateAccessModal extends Vue {
background: #d7e8ff;
}
.clickable-image {
cursor: pointer;
}
.access-grant {
position: fixed;
top: 0;
@ -641,18 +1000,59 @@ export default class CreateAccessModal extends Vue {
flex-direction: column;
align-items: flex-start;
position: relative;
padding: 25px;
padding: 25px 40px;
margin-top: 40px;
width: 410px;
height: auto;
&__generated-passphrase {
margin-top: 20px;
align-items: center;
padding: 10px 16px;
background: #ebeef1;
border: 1px solid #c8d3de;
border-radius: 7px;
@include generated-text;
}
&__generated-credentials {
@include generated-text;
margin: 0 0 4px;
display: flex;
justify-content: space-between;
&__text {
width: 90%;
text-overflow: ellipsis;
overflow-x: hidden;
white-space: nowrap;
}
&__label {
display: flex;
margin: 8px 0;
align-items: center;
&__text {
font-family: sans-serif;
font-size: 14px;
font-weight: 700;
line-height: 20px;
letter-spacing: 0;
text-align: left;
padding: 0 6px 0 0;
}
}
}
&__credential-buttons {
&__container-s3 {
display: flex;
justify-content: space-between;
margin: 15px 0;
}
&__container {
display: flex;
justify-content: center;
margin: 15px 0;
}
}
&__header-container {
@ -666,6 +1066,11 @@ export default class CreateAccessModal extends Vue {
grid-column: 1;
}
&__title-complete {
grid-column: 1;
margin-top: 10px;
}
&__close-cross-container {
grid-column: 2;
margin: auto 0 auto auto;
@ -769,6 +1174,24 @@ export default class CreateAccessModal extends Vue {
padding-top: 10px;
}
&__created {
width: 100%;
text-align: left;
display: grid;
margin-top: 15px;
row-gap: 4ch;
padding-top: 10px;
p {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
overflow-wrap: break-word;
text-align: left;
}
}
&__name-icon {
grid-column: 1;
grid-row: 2;
@ -779,16 +1202,25 @@ export default class CreateAccessModal extends Vue {
grid-row: 2;
display: flex;
flex-direction: column;
max-width: 238px;
&__input {
background: #fff;
border: 1px solid #c8d3de;
box-sizing: border-box;
border-radius: 4px;
border-radius: 6px;
height: 40px;
font-size: 17px;
padding: 10px;
}
&__input:focus {
border-color: #2683ff;
}
}
&__input:focus {
border-color: #2683ff;
}
&__permissions-icon {
@ -917,6 +1349,7 @@ export default class CreateAccessModal extends Vue {
}
.tooltip-icon {
display: flex;
width: 14px;
height: 14px;
cursor: pointer;
@ -935,7 +1368,7 @@ export default class CreateAccessModal extends Vue {
.access-tooltip {
top: 52px;
left: 94px;
left: 109px;
@include tooltip-container;
@ -949,7 +1382,7 @@ export default class CreateAccessModal extends Vue {
.s3-tooltip {
top: 158px;
left: 103px;
left: 118px;
@include tooltip-container;
@ -964,7 +1397,7 @@ export default class CreateAccessModal extends Vue {
.api-tooltip {
top: 186px;
left: 78px;
left: 94px;
@include tooltip-container;
@ -977,6 +1410,19 @@ export default class CreateAccessModal extends Vue {
}
}
@media screen and (max-width: 500px) {
.access-grant__modal-container {
width: auto;
max-width: 80vw;
padding: 30px 24px;
&__body-container {
grid-template-columns: 1.2fr 6fr;
}
}
}
@media screen and (max-height: 800px) {
.access-grant {

View File

@ -47,8 +47,8 @@ export default class BucketNameBullet extends Vue {
font-family: 'font_bold', sans-serif;
font-style: normal;
font-weight: bold;
font-size: 9px;
line-height: 12px;
font-size: 13px;
line-height: 15px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;

View File

@ -13,7 +13,10 @@
alt="Arrow down (expand)"
/>
</div>
<BucketsDropdown v-if="isDropdownShown" :show-scrollbar="showScrollbar" />
<BucketsDropdown
v-if="isDropdownShown"
:show-scrollbar="showScrollbar"
/>
</div>
</template>

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<div v-click-outside="closePicker" class="duration-picker">
<div v-click-outside="closePicker" :class="`duration-picker${containerStyle}`">
<div class="duration-picker__list">
<ul class="duration-picker__list__column">
<li class="duration-picker__list__column-item" @click="onForeverClick">Forever</li>
@ -23,7 +23,7 @@
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Component, Vue, Prop } from 'vue-property-decorator';
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { APP_STATE_ACTIONS } from "@/utils/constants/actionNames";
@ -38,6 +38,8 @@ import VDateRangePicker from "@/components/common/VDateRangePicker.vue";
},
})
export default class DurationPicker extends Vue {
@Prop({default: ''})
private readonly containerStyle: string;
/**
* onCustomRangePick holds logic for choosing custom date range.
* @param dateRange
@ -141,7 +143,7 @@ export default class DurationPicker extends Vue {
</script>
<style scoped lang="scss">
.duration-picker {
@mixin date-container {
background: #fff;
width: 600px;
border: 1px solid #384b65;
@ -150,8 +152,19 @@ export default class DurationPicker extends Vue {
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
position: absolute;
z-index: 1;
right: 0;
top: 100%;
}
.duration-picker {
@include date-container;
right: 0;
&__access-date-container {
@include date-container;
right: -88%;
}
&__list {
column-count: 2;

View File

@ -16,6 +16,7 @@
</div>
<DurationPicker
v-if="isDurationPickerVisible"
:container-style="pickerStyle"
@setLabel="setDateRangeLabel"
/>
</div>
@ -42,6 +43,8 @@ export default class DurationSelection extends Vue {
private readonly containerStyle: string;
@Prop({default: ''})
private readonly textStyle: string;
@Prop({default: ''})
private readonly pickerStyle: string;
public dateRangeLabel = 'Forever';

View File

@ -11,18 +11,24 @@
<slot name="icon" />
<div v-if="isWhiteGreen" class="greenCheck">&#x2713;</div>
<div v-if="isGreenWhite" class="whiteCheck">&#x2713;</div>
<div v-if="hasTrashIcon" class="trash-icon"><TrashIcon /></div>
<span class="label" :class="{uppercase: isUppercase}">{{ label }}</span>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import TrashIcon from '@/../static/images/accessGrants/trashIcon.svg';
/**
* Custom button component with label.
*/
// @vue/component
@Component
@Component({
components: {
TrashIcon
},
})
export default class VButton extends Vue {
@Prop({default: 'Default'})
private readonly label: string;
@ -51,6 +57,8 @@ export default class VButton extends Vue {
@Prop({default: false})
private readonly isGreenWhite: boolean;
@Prop({default: false})
private readonly hasTrashIcon: boolean;
@Prop({default: false})
private isDisabled: boolean;
@Prop({default: false})
private readonly isUppercase: boolean;
@ -176,6 +184,10 @@ export default class VButton extends Vue {
background-color: #0149ff;
cursor: pointer;
.trash-icon {
margin-right: 5px;
}
.greenCheck {
color: #00ac26 !important;
margin-right: 5px;

View File

@ -11,6 +11,7 @@
:item-data="item"
:class="{ selected: item.isSelected }"
@click.native="onItemClick(item)"
@altMethod="openDeleteModal"
/>
</div>
</template>
@ -29,6 +30,13 @@ export default class VList extends Vue {
private readonly onItemClick: listItemClickCallback;
@Prop({default: []})
private readonly dataSet: unknown[];
/**
* testMethod
*/
public openDeleteModal() {
this.$emit('openModal')
}
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.5762 3.86476C14.9206 3.86476 15.1998 4.14397 15.1998 4.48838C15.1998 4.8244 14.9341 5.09835 14.6013 5.11151L14.5762 5.11201H13.2896L12.3256 12.9845C12.1864 14.1218 11.2206 14.9766 10.0747 14.9766H6.01806C4.8809 14.9766 3.91958 14.1344 3.77004 13.0071L2.72271 5.11201H1.42343C1.07901 5.11201 0.799805 4.8328 0.799805 4.48838C0.799805 4.15237 1.06556 3.87842 1.39834 3.86526L1.42343 3.86476H14.5762ZM12.0331 5.11201H3.98096L5.00645 12.8431C5.07242 13.3404 5.48952 13.7144 5.98806 13.7289L6.01806 13.7293H10.0747C10.5802 13.7293 11.0079 13.3596 11.0835 12.8628L11.0876 12.8329L12.0331 5.11201ZM7.05993 7.32448L7.06398 7.34966L7.06697 7.37457L7.46392 11.3441C7.49819 11.6868 7.24815 11.9924 6.90544 12.0266C6.57109 12.0601 6.27206 11.8229 6.22585 11.4931L6.22286 11.4682L5.82591 7.49868C5.79164 7.15597 6.04168 6.85037 6.38439 6.8161C6.71016 6.78352 7.00241 7.00785 7.05993 7.32448ZM9.84197 6.81634C10.1763 6.84978 10.4225 7.14147 10.4024 7.47391L10.4004 7.49892L10.0038 11.4654C9.96952 11.8081 9.66392 12.0582 9.32121 12.0239C8.98686 11.9905 8.74072 11.6988 8.76074 11.3663L8.76274 11.3413L9.15939 7.37482C9.19366 7.03211 9.49926 6.78207 9.84197 6.81634ZM10.0407 1.2002C10.3852 1.2002 10.6644 1.4794 10.6644 1.82382C10.6644 2.15983 10.3986 2.43378 10.0658 2.44694L10.0407 2.44744H5.95886C5.61444 2.44744 5.33524 2.16823 5.33524 1.82382C5.33524 1.4878 5.60099 1.21385 5.93378 1.20069L5.95886 1.2002H10.0407Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -21,6 +21,7 @@ exports[`AddStorjForm renders correctly 1`] = `
<div class="add-storj-area__submit-area">
<!---->
<div class="confirm-add-storj-button container" style="width: 251px; height: 48px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Continue to Coin Payments</span>
</div>

View File

@ -41,6 +41,7 @@ exports[`PaymentMethods renders correctly after add STORJ and cancel click 1`] =
<div class="add-storj-area__submit-area">
<!---->
<div class="confirm-add-storj-button container" style="width: 251px; height: 48px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Continue to Coin Payments</span>
</div>
@ -64,6 +65,7 @@ exports[`PaymentMethods renders correctly after add STORJ and cancel click 2`] =
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="add-storj-button container blue-white" style="width: 123px; height: 46px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Add STORJ</span>
</div>
@ -158,6 +160,7 @@ exports[`PaymentMethods renders correctly with card 1`] = `
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="add-storj-button container blue-white" style="width: 123px; height: 46px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Add STORJ</span>
</div>
@ -214,6 +217,7 @@ exports[`PaymentMethods renders correctly without card 1`] = `
<div class="payment-methods-area__functional-area__button-area">
<div class="payment-methods-area__functional-area__button-area__default-buttons">
<div class="add-storj-button container blue-white" style="width: 123px; height: 46px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Add STORJ</span>
</div>

View File

@ -2,6 +2,7 @@
exports[`Button.vue renders correctly 1`] = `
<div class="container" style="width: inherit; height: inherit; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Default</span>
</div>
@ -9,6 +10,7 @@ exports[`Button.vue renders correctly 1`] = `
exports[`Button.vue renders correctly with isDisabled prop 1`] = `
<div class="container disabled" style="width: inherit; height: inherit; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Default</span>
</div>
@ -16,6 +18,7 @@ exports[`Button.vue renders correctly with isDisabled prop 1`] = `
exports[`Button.vue renders correctly with isWhite prop 1`] = `
<div class="container white" style="width: inherit; height: inherit; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Default</span>
</div>

View File

@ -10,6 +10,7 @@ exports[`OverviewStep.vue renders correctly 1`] = `
<h2 class="overview-container__title">Start with web browser</h2>
<p class="overview-container__info">Start uploading files in the browser and instantly see how your data gets distributed over the Storj network around the world.</p>
<div class="container" style="width: 240px; height: 48px; border-radius: 8px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Continue in web -&gt;</span>
</div>
@ -23,6 +24,7 @@ exports[`OverviewStep.vue renders correctly 1`] = `
<h2 class="overview-container__title">Start with Uplink CLI</h2>
<p class="overview-container__info">The Uplink CLI is a command-line interface tool which allows you to upload and download files from the network, manage permissions and share files.</p>
<div class="container" style="width: 240px; height: 48px; border-radius: 8px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Continue in cli -&gt;</span>
</div>

View File

@ -35,10 +35,12 @@ exports[`CreateProject.vue renders correctly 1`] = `
</div>
<div class="create-project__container__button-container">
<div class="create-project__container__button-container__cancel container transparent" style="width: 210px; height: 48px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Cancel</span>
</div>
<div class="container disabled" style="width: 210px; height: 48px; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Create Project +</span>
</div>

View File

@ -10,6 +10,7 @@ exports[`EditProjectDetails.vue editing description works correctly 1`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -19,6 +20,7 @@ exports[`EditProjectDetails.vue editing description works correctly 1`] = `
<!---->
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Save</span>
</div>
@ -39,6 +41,7 @@ exports[`EditProjectDetails.vue editing description works correctly 2`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -48,6 +51,7 @@ exports[`EditProjectDetails.vue editing description works correctly 2`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -69,6 +73,7 @@ exports[`EditProjectDetails.vue editing name works correctly 1`] = `
<!---->
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Save</span>
</div>
@ -77,6 +82,7 @@ exports[`EditProjectDetails.vue editing name works correctly 1`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -98,6 +104,7 @@ exports[`EditProjectDetails.vue editing name works correctly 2`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -107,6 +114,7 @@ exports[`EditProjectDetails.vue editing name works correctly 2`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -128,6 +136,7 @@ exports[`EditProjectDetails.vue renders correctly 1`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>
@ -137,6 +146,7 @@ exports[`EditProjectDetails.vue renders correctly 1`] = `
<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; border-radius: 6px; font-size: 16px;">
<!---->
<!---->
<!----> <span class="label">Edit</span>
</div>