web/satellite: refactored modals to use common VModal component

Refactored MFA, project members, profile and change password modals to use common VModal component.
Didn't touch access grant modals.

Change-Id: I9af5277496a99ec907fbee57a84127064ca9c02b
This commit is contained in:
Vitalii 2022-08-03 16:29:58 +03:00 committed by Storj Robot
parent 1be5277c2a
commit 27f6fbdeda
20 changed files with 831 additions and 1137 deletions

View File

@ -16,7 +16,7 @@
</div>
<EditIcon
class="edit-svg"
@click="toggleEditProfilePopup"
@click="toggleEditProfileModal"
/>
</div>
<div class="settings__secondary-container">
@ -30,7 +30,7 @@
</div>
<EditIcon
class="edit-svg"
@click="toggleChangePasswordPopup"
@click="toggleChangePasswordModal"
/>
</div>
<div class="settings__secondary-container__email-container">
@ -65,7 +65,7 @@
label="Disable 2FA"
width="173px"
height="44px"
:on-press="toggleDisableMFAPopup"
:on-press="toggleDisableMFAModal"
is-deletion="true"
/>
<VButton
@ -79,32 +79,22 @@
</div>
</div>
</div>
<ChangePasswordPopup v-if="isChangePasswordPopupShown" />
<EditProfilePopup v-if="isEditProfilePopupShown" />
<EnableMFAPopup v-if="isEnableMFAPopup" :toggle-modal="toggleEnableMFAPopup" />
<DisableMFAPopup v-if="isDisableMFAPopup" :toggle-modal="toggleDisableMFAPopup" />
<MFARecoveryCodesPopup v-if="isMFACodesPopup" :toggle-modal="toggleMFACodesPopup" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ChangePasswordPopup from '@/components/account/ChangePasswordPopup.vue';
import EditProfilePopup from '@/components/account/EditProfilePopup.vue';
import DisableMFAPopup from '@/components/account/mfa/DisableMFAPopup.vue';
import EnableMFAPopup from '@/components/account/mfa/EnableMFAPopup.vue';
import MFARecoveryCodesPopup from '@/components/account/mfa/MFARecoveryCodesPopup.vue';
import { USER_ACTIONS } from '@/store/modules/users';
import { User } from '@/types/users';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import VButton from '@/components/common/VButton.vue';
import ChangePasswordIcon from '@/../static/images/account/profile/changePassword.svg';
import EmailIcon from '@/../static/images/account/profile/email.svg';
import EditIcon from '@/../static/images/common/edit.svg';
import { USER_ACTIONS } from '@/store/modules/users';
import { User } from '@/types/users';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
// @vue/component
@Component({
components: {
@ -112,18 +102,10 @@ import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
ChangePasswordIcon,
EmailIcon,
VButton,
ChangePasswordPopup,
EditProfilePopup,
EnableMFAPopup,
DisableMFAPopup,
MFARecoveryCodesPopup,
},
})
export default class SettingsArea extends Vue {
public isLoading = false;
public isEnableMFAPopup = false;
public isDisableMFAPopup = false;
public isMFACodesPopup = false;
/**
* Lifecycle hook after initial render where user info is fetching.
@ -142,7 +124,7 @@ export default class SettingsArea extends Vue {
try {
await this.$store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_SECRET);
this.toggleEnableMFAPopup();
this.toggleEnableMFAModal();
} catch (error) {
await this.$notify.error(error.message);
}
@ -160,7 +142,7 @@ export default class SettingsArea extends Vue {
try {
await this.$store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_RECOVERY_CODES);
this.toggleMFACodesPopup();
this.toggleMFACodesModal();
} catch (error) {
await this.$notify.error(error.message);
}
@ -169,38 +151,38 @@ export default class SettingsArea extends Vue {
}
/**
* Toggles enable MFA popup visibility.
* Toggles enable MFA modal visibility.
*/
public toggleEnableMFAPopup(): void {
this.isEnableMFAPopup = !this.isEnableMFAPopup;
public toggleEnableMFAModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_ENABLE_MFA_MODAL_SHOWN);
}
/**
* Toggles disable MFA popup visibility.
* Toggles disable MFA modal visibility.
*/
public toggleDisableMFAPopup(): void {
this.isDisableMFAPopup = !this.isDisableMFAPopup;
public toggleDisableMFAModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_DISABLE_MFA_MODAL_SHOWN);
}
/**
* Toggles MFA recovery codes popup visibility.
* Toggles MFA recovery codes modal visibility.
*/
public toggleMFACodesPopup(): void {
this.isMFACodesPopup = !this.isMFACodesPopup;
public toggleMFACodesModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_MFA_RECOVERY_MODAL_SHOWN);
}
/**
* Opens change password popup.
*/
public toggleChangePasswordPopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
public toggleChangePasswordModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN);
}
/**
* Opens edit account info popup.
* Opens edit account info modal.
*/
public toggleEditProfilePopup(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
public toggleEditProfileModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_MODAL_SHOWN);
}
/**
@ -210,20 +192,6 @@ export default class SettingsArea extends Vue {
return this.$store.getters.user;
}
/**
* Indicates if edit user info popup is shown.
*/
public get isEditProfilePopupShown(): boolean {
return this.$store.state.appStateModule.appState.isEditProfilePopupShown;
}
/**
* Indicates if change password popup is shown.
*/
public get isChangePasswordPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isChangePasswordPopupShown;
}
/**
* Returns first letter of user name.
*/

View File

@ -13,6 +13,7 @@
class="confirm-mfa__input"
:placeholder="isRecovery ? 'Code' : '000000'"
:type="isRecovery ? 'text' : 'number'"
autofocus
@input="event => onInput(event.target.value)"
>
</div>

View File

@ -1,347 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="enable-mfa">
<div class="enable-mfa__container">
<h1 class="enable-mfa__container__title">Two-Factor Authentication</h1>
<p v-if="isScan" class="enable-mfa__container__subtitle">
Scan this QR code in your favorite TOTP app to get started.
</p>
<p v-if="isEnable" class="enable-mfa__container__subtitle max-width">
Enter the authentication code generated in your TOTP app to confirm your account is connected.
</p>
<p v-if="isCodes" class="enable-mfa__container__subtitle">
Save recovery codes.
</p>
<div v-if="isScan" class="enable-mfa__container__scan">
<h2 class="enable-mfa__container__scan__title">Scan this QR Code</h2>
<p class="enable-mfa__container__scan__subtitle">Scan the following QR code in your OTP app.</p>
<div class="enable-mfa__container__scan__qr">
<canvas ref="canvas" class="enable-mfa__container__scan__qr__canvas" />
</div>
<p class="enable-mfa__container__scan__subtitle">Unable to scan? Use the following code instead:</p>
<p class="enable-mfa__container__scan__secret">{{ userMFASecret }}</p>
</div>
<div v-if="isEnable" class="enable-mfa__container__confirm">
<h2 class="enable-mfa__container__confirm__title">Confirm Authentication Code</h2>
<ConfirmMFAInput :on-input="onConfirmInput" :is-error="isError" />
</div>
<div v-if="isCodes" class="enable-mfa__container__codes">
<h2 class="enable-mfa__container__codes__title max-width">
Please save these codes somewhere to be able to recover access to your account.
</h2>
<p
v-for="(code, index) in userMFARecoveryCodes"
:key="index"
>
{{ code }}
</p>
</div>
<div class="enable-mfa__container__buttons">
<VButton
class="cancel-button"
label="Cancel"
width="50%"
height="44px"
is-white="true"
:on-press="toggleModal"
/>
<VButton
v-if="isScan"
label="Continue"
width="50%"
height="44px"
:on-press="showEnable"
/>
<VButton
v-if="isEnable"
label="Enable"
width="50%"
height="44px"
:on-press="enable"
:is-disabled="!confirmPasscode || isLoading"
/>
<VButton
v-if="isCodes"
label="Done"
width="50%"
height="44px"
:on-press="toggleModal"
/>
</div>
<div class="enable-mfa__container__close-container" @click="toggleModal">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
<script lang="ts">
import QRCode from 'qrcode';
import { Component, Prop, Vue } from 'vue-property-decorator';
import ConfirmMFAInput from '@/components/account/mfa/ConfirmMFAInput.vue';
import VButton from '@/components/common/VButton.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import { USER_ACTIONS } from '@/store/modules/users';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
// @vue/component
@Component({
components: {
ConfirmMFAInput,
CloseCrossIcon,
VButton,
},
})
export default class EnableMFAPopup extends Vue {
@Prop({default: () => () => false})
public readonly toggleModal: () => void;
public readonly qrLink =
`otpauth://totp/${encodeURIComponent(this.$store.getters.user.email)}?secret=${this.userMFASecret}&issuer=${encodeURIComponent(`STORJ ${this.satellite}`)}&algorithm=SHA1&digits=6&period=30`;
public isScan = true;
public isEnable = false;
public isCodes = false;
public isError = false;
public isLoading = false;
public confirmPasscode = '';
public $refs!: {
canvas: HTMLCanvasElement;
};
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Mounted lifecycle hook after initial render.
* Renders QR code.
*/
public async mounted(): Promise<void> {
await QRCode.toCanvas(this.$refs.canvas, this.qrLink);
}
/**
* Toggles view to Enable MFA state.
*/
public showEnable(): void {
this.isScan = false;
this.isEnable = true;
}
/**
* Toggles view to MFA Recovery Codes state.
*/
public async showCodes(): Promise<void> {
await this.$store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_RECOVERY_CODES);
this.isEnable = false;
this.isCodes = true;
}
/**
* Sets confirmation passcode value from input.
*/
public onConfirmInput(value: string): void {
this.isError = false;
this.confirmPasscode = value;
}
/**
* Enables user MFA and sets view to Recovery Codes state.
*/
public async enable(): Promise<void> {
if (!this.confirmPasscode || this.isLoading || this.isError) return;
this.isLoading = true;
try {
await this.$store.dispatch(USER_ACTIONS.ENABLE_USER_MFA, this.confirmPasscode);
await this.$store.dispatch(USER_ACTIONS.GET);
await this.showCodes();
this.analytics.eventTriggered(AnalyticsEvent.MFA_ENABLED);
await this.$notify.success('MFA was enabled successfully');
} catch (error) {
await this.$notify.error(error.message);
this.isError = true;
}
this.isLoading = false;
}
/**
* Returns satellite name from store.
*/
private get satellite(): string {
return this.$store.state.appStateModule.satelliteName;
}
/**
* Returns pre-generated MFA secret from store.
*/
private get userMFASecret(): string {
return this.$store.state.usersModule.userMFASecret;
}
/**
* Returns user MFA recovery codes from store.
*/
private get userMFARecoveryCodes(): string[] {
return this.$store.state.usersModule.userMFARecoveryCodes;
}
}
</script>
<style scoped lang="scss">
.enable-mfa {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
justify-content: center;
z-index: 1000;
background: rgb(27 37 51 / 75%);
&__container {
padding: 60px;
height: fit-content;
margin-top: 100px;
position: relative;
background: #fff;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 34px;
text-align: center;
color: #000;
margin: 0 0 30px;
}
&__subtitle {
font-size: 16px;
line-height: 21px;
text-align: center;
color: #000;
margin: 0 0 45px;
}
&__scan {
padding: 25px;
background: #f5f6fa;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
width: calc(100% - 50px);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
text-align: center;
color: #000;
margin: 0 0 30px;
}
&__subtitle {
font-size: 14px;
line-height: 25px;
text-align: center;
color: #000;
}
&__qr {
margin: 30px 0;
background: #fff;
border-radius: 6px;
padding: 10px;
&__canvas {
height: 200px !important;
width: 200px !important;
}
}
&__secret {
margin: 5px 0 0;
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 25px;
text-align: center;
color: #000;
}
}
&__confirm,
&__codes {
padding: 25px;
background: #f5f6fa;
border-radius: 6px;
width: calc(100% - 50px);
display: flex;
flex-direction: column;
align-items: center;
&__title {
font-size: 16px;
line-height: 19px;
text-align: center;
color: #000;
margin-bottom: 20px;
}
}
&__buttons {
display: flex;
align-items: center;
width: 100%;
margin-top: 30px;
}
&__close-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;
}
.max-width {
max-width: 485px;
}
@media screen and (max-height: 900px) {
.enable-mfa {
padding-bottom: 20px;
overflow-y: scroll;
}
}
</style>

View File

@ -1,141 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="mfa-codes">
<div class="mfa-codes__container">
<h1 class="mfa-codes__container__title">Two-Factor Authentication</h1>
<div class="mfa-codes__container__codes">
<p class="mfa-codes__container__codes__subtitle">
Please save these codes somewhere to be able to recover access to your account.
</p>
<p
v-for="(code, index) in userMFARecoveryCodes"
:key="index"
>
{{ code }}
</p>
</div>
<VButton
class="done-button"
label="Done"
width="100%"
height="44px"
:on-press="toggleModal"
/>
<div class="mfa-codes__container__close-container" @click="toggleModal">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
// @vue/component
@Component({
components: {
CloseCrossIcon,
VButton,
},
})
export default class MFARecoveryCodesPopup extends Vue {
@Prop({default: () => () => false})
public readonly toggleModal: () => void;
/**
* Returns MFA recovery codes from store.
*/
public get userMFARecoveryCodes(): string[] {
return this.$store.state.usersModule.userMFARecoveryCodes;
}
}
</script>
<style scoped lang="scss">
.mfa-codes {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
justify-content: center;
z-index: 1000;
background: rgb(27 37 51 / 75%);
&__container {
padding: 60px;
height: fit-content;
margin-top: 100px;
position: relative;
background: #fff;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 34px;
text-align: center;
color: #000;
margin: 0 0 30px;
}
&__codes {
padding: 25px;
background: #f5f6fa;
border-radius: 6px;
width: calc(100% - 50px);
display: flex;
flex-direction: column;
align-items: center;
&__subtitle {
font-size: 16px;
line-height: 21px;
text-align: center;
color: #000;
margin: 0 0 30px;
max-width: 485px;
}
}
&__close-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;
}
}
}
}
.done-button {
margin-top: 30px;
}
@media screen and (max-height: 750px) {
.mfa-codes {
padding-bottom: 20px;
overflow-y: scroll;
}
}
</style>

View File

@ -1,9 +1,10 @@
// Copyright (C) 2019 Storj Labs, Inc.
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="add-user-container" @keyup.enter="onAddUsersClick" @keyup.esc="onClose">
<div id="addTeamMemberPopup" class="add-user">
<VModal :on-close="closeModal">
<template #content>
<div class="add-user">
<div class="add-user__main">
<div class="add-user__info-panel-container">
<h2 class="add-user__info-panel-container__main-label-text">Add Team Member</h2>
@ -47,7 +48,7 @@
label="Cancel"
width="205px"
height="48px"
:on-press="onClose"
:on-press="closeModal"
is-transparent="true"
/>
<VButton
@ -59,9 +60,6 @@
/>
</div>
</div>
<div class="add-user__close-cross-container" @click="onClose">
<CloseCrossIcon />
</div>
</div>
<div class="notification-wrap">
<AddMemberNotificationIcon class="notification-wrap__image" />
@ -76,37 +74,39 @@
</div>
</div>
</div>
</div>
</template>
</VModal>
</template>
<script lang='ts'>
import { Component, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import { RouteConfig } from '@/router';
import { EmailInput } from '@/types/EmailInput';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { Validator } from '@/utils/validation';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import ErrorIcon from '@/../static/images/register/ErrorInfo.svg';
import AddFieldIcon from '@/../static/images/team/addField.svg';
import AddMemberNotificationIcon from '@/../static/images/team/addMemberNotification.svg';
import DeleteFieldIcon from '@/../static/images/team/deleteField.svg';
import { RouteConfig } from '@/router';
import { EmailInput } from '@/types/EmailInput';
import { APP_STATE_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import { Validator } from '@/utils/validation';
// @vue/component
@Component({
components: {
VButton,
VModal,
ErrorIcon,
DeleteFieldIcon,
AddFieldIcon,
CloseCrossIcon,
AddMemberNotificationIcon,
},
})
export default class AddUserPopup extends Vue {
export default class AddTeamMemberModal extends Vue {
/**
* Initial empty inputs set.
*/
@ -196,7 +196,7 @@ export default class AddUserPopup extends Vue {
await this.$notify.error(`Unable to fetch project members. ${error.message}`);
}
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
this.closeModal();
this.isLoading = false;
}
@ -224,10 +224,10 @@ export default class AddUserPopup extends Vue {
}
/**
* Closes popup.
* Closes modal.
*/
public onClose(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
}
/**
@ -284,100 +284,19 @@ export default class AddUserPopup extends Vue {
</script>
<style scoped lang='scss'>
.add-user-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgb(134 134 148 / 40%);
z-index: 1121;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: 'font_regular', sans-serif;
}
.add-user-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 80px 0 50px;
&__item {
display: flex;
align-items: center;
justify-content: space-between;
&__image {
margin-right: 20px;
}
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
margin-left: 0;
padding-left: 0;
margin-block-start: 0;
margin-block-end: 0;
}
&:first-child {
cursor: pointer;
}
}
}
.inactive-label {
cursor: default;
color: #dadde5;
}
.error-input {
border: 1px solid red !important;
}
.inactive-image {
cursor: default;
.add-user-row__item__image {
&__rect {
fill: #dadde5;
}
&__path {
fill: #acb0bc;
}
}
}
.input-container.full-input {
width: 100%;
}
.red {
background-color: #eb5757;
}
.add-user {
width: 100%;
max-width: 1200px;
height: auto;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
justify-content: center;
font-family: 'font_regular', sans-serif;
&__main {
border-top-left-radius: 6px;
border-top-right-radius: 6px;
display: flex;
flex-direction: row;
align-items: flex-start;
position: relative;
justify-content: center;
background-color: #fff;
padding: 80px 20px 80px 30px;
@ -475,6 +394,7 @@ export default class AddUserPopup extends Vue {
font-size: 16px;
line-height: 25px;
padding-left: 50px;
text-align: left;
}
&__button-container {
@ -486,24 +406,70 @@ export default class AddUserPopup extends Vue {
padding: 0 80px 0 50px;
}
}
}
&__close-cross-container {
.add-user-row {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 40px;
height: 24px;
width: 24px;
cursor: pointer;
justify-content: space-between;
padding: 0 80px 0 50px;
&:hover .close-cross-svg-path {
fill: #2683ff;
&__item {
display: flex;
align-items: center;
justify-content: space-between;
&__image {
margin-right: 20px;
}
&__label {
font-family: 'font_medium', sans-serif;
font-size: 16px;
margin-left: 0;
padding-left: 0;
margin-block-start: 0;
margin-block-end: 0;
}
&:first-child {
cursor: pointer;
}
}
}
.inactive-label {
cursor: default;
color: #dadde5;
}
.error-input {
border: 1px solid red !important;
}
.inactive-image {
cursor: default;
.add-user-row__item__image {
&__rect {
fill: #dadde5;
}
&__path {
fill: #acb0bc;
}
}
}
.input-container.full-input {
width: 100%;
}
.red {
background-color: #eb5757;
}
.notification-wrap {
background-color: rgb(194 214 241 / 100%);
height: 98px;
@ -526,6 +492,7 @@ export default class AddUserPopup extends Vue {
&__text {
font-family: 'font_medium', sans-serif;
font-size: 16px;
text-align: left;
}
}
}
@ -533,38 +500,4 @@ export default class AddUserPopup extends Vue {
.scrollable {
overflow-y: scroll;
}
@media screen and (max-width: 1025px) {
.add-user {
padding: 10px;
max-width: 1000px;
&__main {
width: 100%;
padding-right: 0;
padding-left: 0;
}
&__info-panel-container {
display: none;
}
&__form-container {
max-width: 800px;
}
&-row__item {
width: 80%;
}
}
#add-user-button {
justify-content: flex-start;
.add-user-row__item__image {
padding-right: 20px;
}
}
}
</style>

View File

@ -7,6 +7,12 @@
<CreateProjectModal v-if="isCreateProjectModal" />
<AddPaymentMethodModal v-if="isAddPMModal" />
<OpenBucketModal v-if="isOpenBucketModal" />
<MFARecoveryCodesModal v-if="isMFARecoveryModal" />
<EnableMFAModal v-if="isEnableMFAModal" />
<DisableMFAModal v-if="isDisableMFAModal" />
<EditProfileModal v-if="isEditProfileModal" />
<ChangePasswordModal v-if="isChangePasswordModal" />
<AddTeamMemberModal v-if="isAddTeamMembersModal" />
</div>
</template>
@ -17,6 +23,12 @@ import CreateProjectPromptModal from '@/components/modals/CreateProjectPromptMod
import CreateProjectModal from '@/components/modals/CreateProjectModal.vue'
import AddPaymentMethodModal from "@/components/modals/AddPaymentMethodModal.vue";
import OpenBucketModal from "@/components/modals/OpenBucketModal.vue";
import MFARecoveryCodesModal from "@/components/modals/MFARecoveryCodesModal.vue";
import EnableMFAModal from "@/components/modals/EnableMFAModal.vue";
import DisableMFAModal from "@/components/modals/DisableMFAModal.vue";
import EditProfileModal from "@/components/modals/EditProfileModal.vue";
import ChangePasswordModal from "@/components/modals/ChangePasswordModal.vue";
import AddTeamMemberModal from "@/components/modals/AddTeamMemberModal.vue";
// @vue/component
@Component({
@ -25,6 +37,12 @@ import OpenBucketModal from "@/components/modals/OpenBucketModal.vue";
CreateProjectModal,
AddPaymentMethodModal,
OpenBucketModal,
MFARecoveryCodesModal,
EnableMFAModal,
DisableMFAModal,
EditProfileModal,
ChangePasswordModal,
AddTeamMemberModal,
},
})
export default class AllModals extends Vue {
@ -55,5 +73,47 @@ export default class AllModals extends Vue {
public get isOpenBucketModal(): boolean {
return this.$store.state.appStateModule.appState.isOpenBucketModalShown;
}
/**
* Indicates if MFA recovery modal is shown.
*/
public get isMFARecoveryModal(): boolean {
return this.$store.state.appStateModule.appState.isMFARecoveryModalShown;
}
/**
* Indicates if enable MFA modal is shown.
*/
public get isEnableMFAModal(): boolean {
return this.$store.state.appStateModule.appState.isEnableMFAModalShown;
}
/**
* Indicates if disable MFA modal is shown.
*/
public get isDisableMFAModal(): boolean {
return this.$store.state.appStateModule.appState.isDisableMFAModalShown;
}
/**
* Indicates if edit profile modal is shown.
*/
public get isEditProfileModal(): boolean {
return this.$store.state.appStateModule.appState.isEditProfileModalShown;
}
/**
* Indicates if change password modal is shown.
*/
public get isChangePasswordModal(): boolean {
return this.$store.state.appStateModule.appState.isChangePasswordModalShown;
}
/**
* Indicates if add team members modal is shown.
*/
public get isAddTeamMembersModal(): boolean {
return this.$store.state.appStateModule.appState.isAddTeamMembersModalShown;
}
}
</script>

View File

@ -2,12 +2,12 @@
// See LICENSE for copying information.
<template>
<div class="change-password-popup-container">
<div class="change-password-popup">
<div class="change-password-popup__form-container">
<div class="change-password-row-container">
<ChangePasswordIcon class="change-password-popup__form-container__svg" />
<h2 class="change-password-popup__form-container__main-label-text">Change Password</h2>
<VModal :on-close="closeModal">
<template #content>
<div class="change-password">
<div class="change-password__row">
<ChangePasswordIcon />
<h2 class="change-password__row__label">Change Password</h2>
</div>
<VInput
class="full-input"
@ -41,12 +41,12 @@
:error="confirmationPasswordError"
@setData="setPasswordConfirmation"
/>
<div class="change-password-popup__form-container__button-container">
<div class="change-password__buttons">
<VButton
label="Cancel"
width="205px"
height="48px"
:on-press="onCloseClick"
:on-press="closeModal"
is-transparent="true"
/>
<VButton
@ -57,30 +57,26 @@
/>
</div>
</div>
<div class="change-password-popup__close-cross-container" @click="onCloseClick">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
</VModal>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import VInput from '@/components/common/VInput.vue';
import PasswordStrength from '@/components/common/PasswordStrength.vue';
import VButton from '@/components/common/VButton.vue';
import ChangePasswordIcon from '@/../static/images/account/changePasswordPopup/changePassword.svg';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import { AuthHttpApi } from '@/api/auth';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { Validator } from '@/utils/validation';
import { RouteConfig } from "@/router";
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import PasswordStrength from '@/components/common/PasswordStrength.vue';
import VInput from '@/components/common/VInput.vue';
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
import ChangePasswordIcon from '@/../static/images/account/changePasswordPopup/changePassword.svg';
const DELAY_BEFORE_REDIRECT = 2000; // 2 sec
@ -88,13 +84,13 @@ const DELAY_BEFORE_REDIRECT = 2000; // 2 sec
@Component({
components: {
ChangePasswordIcon,
CloseCrossIcon,
VInput,
VButton,
VModal,
PasswordStrength,
},
})
export default class ChangePasswordPopup extends Vue {
export default class ChangePasswordModal extends Vue {
private oldPassword = '';
private newPassword = '';
private confirmationPassword = '';
@ -112,24 +108,39 @@ export default class ChangePasswordPopup extends Vue {
*/
public isPasswordStrengthShown = false;
/**
* Enables password strength info container.
*/
public showPasswordStrength(): void {
this.isPasswordStrengthShown = true;
}
/**
* Disables password strength info container.
*/
public hidePasswordStrength(): void {
this.isPasswordStrengthShown = false;
}
/**
* Sets old password from input.
*/
public setOldPassword(value: string): void {
this.oldPassword = value;
this.oldPasswordError = '';
}
/**
* Sets new password from input.
*/
public setNewPassword(value: string): void {
this.newPassword = value;
this.newPasswordError = '';
}
/**
* Sets password confirmation from input.
*/
public setPasswordConfirmation(value: string): void {
this.confirmationPassword = value;
this.confirmationPasswordError = '';
@ -184,106 +195,46 @@ export default class ChangePasswordPopup extends Vue {
this.analytics.eventTriggered(AnalyticsEvent.PASSWORD_CHANGED);
await this.$notify.success('Password successfully changed!');
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
this.closeModal();
}
/**
* Closes popup.
*/
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN);
}
}
</script>
<style scoped lang="scss">
.change-password-popup-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgb(134 134 148 / 40%);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
font-family: 'font_regular', sans-serif;
}
.full-input {
margin-bottom: 15px;
}
.change-password-row-container {
width: 100%;
display: flex;
flex-direction: row;
align-content: center;
justify-content: flex-start;
margin-bottom: 40px;
}
.change-password-popup {
width: 100%;
max-width: 440px;
max-height: 470px;
.change-password {
background-color: #fff;
border-radius: 6px;
display: flex;
flex-direction: row;
align-items: flex-start;
position: relative;
justify-content: center;
padding: 80px;
&__info-panel-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
padding: 48px;
&__row {
display: flex;
align-items: center;
margin-right: 100px;
margin-top: 20px;
}
margin-bottom: 20px;
&__form-container {
width: 100%;
max-width: 440px;
&__main-label-text {
&__label {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 60px;
color: #384b65;
margin-bottom: 0;
margin-top: 0;
margin-left: 32px;
margin: 0 0 0 32px;
}
}
&__button-container {
&__buttons {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 32px;
}
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 40px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
column-gap: 20px;
}
}
@ -292,20 +243,7 @@ export default class ChangePasswordPopup extends Vue {
width: 100%;
}
@media screen and (max-width: 720px) {
.change-password-popup {
&__info-panel-container {
display: none;
}
&__form-container {
&__button-container {
width: 100%;
}
}
}
.full-input {
margin-bottom: 15px;
}
</style>

View File

@ -186,7 +186,7 @@ export default class CreateProjectModal extends Vue {
<style scoped lang="scss">
.modal {
width: 400px;
padding: 50px 65px 65px;
padding: 54px 48px 51px;
display: flex;
align-items: center;
flex-direction: column;

View File

@ -2,30 +2,31 @@
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="disable-mfa">
<div class="disable-mfa__container">
<h1 class="disable-mfa__container__title">Two-Factor Authentication</h1>
<p class="disable-mfa__container__subtitle">
<h1 class="disable-mfa__title">Two-Factor Authentication</h1>
<p class="disable-mfa__subtitle">
Enter code from your favorite TOTP app to disable 2FA.
</p>
<div class="disable-mfa__container__confirm">
<h2 class="disable-mfa__container__confirm__title">Confirm Authentication Code</h2>
<div class="disable-mfa__confirm">
<h2 class="disable-mfa__confirm__title">Confirm Authentication Code</h2>
<ConfirmMFAInput ref="mfaInput" :on-input="onConfirmInput" :is-error="isError" :is-recovery="isRecoveryCodeState" />
<span class="disable-mfa__container__confirm__toggle" @click="toggleRecoveryCodeState">
<span class="disable-mfa__confirm__toggle" @click="toggleRecoveryCodeState">
Or use {{ isRecoveryCodeState ? '2FA code' : 'recovery code' }}
</span>
</div>
<p class="disable-mfa__container__info">
<p class="disable-mfa__info">
After disabling 2FA, remove the authentication code from your TOTP app.
</p>
<div class="disable-mfa__container__buttons">
<div class="disable-mfa__buttons">
<VButton
class="cancel-button"
class="disable-mfa__buttons__cancel-button"
label="Cancel"
width="50%"
height="44px"
is-white="true"
:on-press="toggleModal"
:on-press="closeModal"
/>
<VButton
label="Disable 2FA"
@ -35,23 +36,21 @@
:is-disabled="!(request.recoveryCode || request.passcode) || isLoading"
/>
</div>
<div class="disable-mfa__container__close-container" @click="toggleModal">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
</VModal>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import ConfirmMFAInput from '@/components/account/mfa/ConfirmMFAInput.vue';
import VButton from '@/components/common/VButton.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import { Component, Vue } from 'vue-property-decorator';
import { USER_ACTIONS } from '@/store/modules/users';
import { DisableMFARequest } from '@/types/users';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import ConfirmMFAInput from '@/components/account/mfa/ConfirmMFAInput.vue';
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
interface ClearInput {
clearInput(): void;
@ -61,14 +60,11 @@ interface ClearInput {
@Component({
components: {
ConfirmMFAInput,
CloseCrossIcon,
VButton,
VModal,
},
})
export default class DisableMFAPopup extends Vue {
@Prop({default: () => () => {}})
public readonly toggleModal: () => void;
export default class DisableMFAModal extends Vue {
public isError = false;
public isLoading = false;
public request = new DisableMFARequest();
@ -78,6 +74,13 @@ export default class DisableMFAPopup extends Vue {
mfaInput: ConfirmMFAInput & ClearInput;
}
/**
* Closes disable MFA modal.
*/
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_DISABLE_MFA_MODAL_SHOWN);
}
/**
* Sets confirmation passcode value from input.
*/
@ -110,7 +113,7 @@ export default class DisableMFAPopup extends Vue {
await this.$notify.success('MFA was disabled successfully');
this.toggleModal();
this.closeModal();
} catch (error) {
await this.$notify.error(error.message);
this.isError = true;
@ -123,21 +126,7 @@ export default class DisableMFAPopup extends Vue {
<style scoped lang="scss">
.disable-mfa {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
justify-content: center;
z-index: 1000;
background: rgb(27 37 51 / 75%);
&__container {
padding: 60px;
height: fit-content;
margin-top: 100px;
position: relative;
background: #fff;
border-radius: 6px;
display: flex;
@ -202,35 +191,10 @@ export default class DisableMFAPopup extends Vue {
align-items: center;
width: 100%;
margin-top: 30px;
}
&__close-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 {
&__cancel-button {
margin-right: 15px;
}
@media screen and (max-height: 750px) {
.disable-mfa {
padding-bottom: 20px;
overflow-y: scroll;
}
}
</style>

View File

@ -2,14 +2,14 @@
// See LICENSE for copying information.
<template>
<div class="edit-profile-popup-container">
<div class="edit-profile-popup">
<div class="edit-profile-popup__form-container">
<div class="edit-profile-row-container">
<div class="edit-profile-popup__form-container__avatar">
<h1 class="edit-profile-popup__form-container__avatar__letter">{{ avatarLetter }}</h1>
<VModal :on-close="closeModal">
<template #content>
<div class="edit-profile">
<div class="edit-profile__row">
<div class="edit-profile__row__avatar">
<h1 class="edit-profile__row__avatar__letter">{{ avatarLetter }}</h1>
</div>
<h2 class="edit-profile-popup__form-container__main-label-text">Edit Profile</h2>
<h2 class="edit-profile__row__label">Edit Profile</h2>
</div>
<VInput
label="Full Name"
@ -18,27 +18,24 @@
:init-value="userInfo.fullName"
@setData="setFullName"
/>
<div class="edit-profile-popup__form-container__button-container">
<div class="edit-profile__buttons">
<VButton
label="Cancel"
width="205px"
width="100%"
height="48px"
:on-press="onCloseClick"
:on-press="closeModal"
is-transparent="true"
/>
<VButton
label="Update"
width="205px"
width="100%"
height="48px"
:on-press="onUpdateClick"
/>
</div>
</div>
<div class="edit-profile-popup__close-cross-container" @click="onCloseClick">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
</VModal>
</template>
<script lang="ts">
@ -46,37 +43,38 @@ import { Component, Vue } from 'vue-property-decorator';
import VInput from '@/components/common/VInput.vue';
import VButton from '@/components/common/VButton.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
import VModal from '@/components/common/VModal.vue';
import { USER_ACTIONS } from '@/store/modules/users';
import { UpdatedUser } from '@/types/users';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
// @vue/component
@Component({
components: {
CloseCrossIcon,
VInput,
VButton,
VModal,
},
})
export default class EditProfilePopup extends Vue {
export default class EditProfileModal extends Vue {
private fullNameError = '';
private readonly userInfo: UpdatedUser =
new UpdatedUser(this.$store.getters.user.fullName, this.$store.getters.user.shortName);
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Set full name value from input.
*/
public setFullName(value: string): void {
this.userInfo.setFullName(value);
this.fullNameError = '';
}
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Validates name and tries to update user info and close popup.
*/
@ -99,14 +97,14 @@ export default class EditProfilePopup extends Vue {
await this.$notify.success('Account info successfully updated!');
await this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
this.closeModal();
}
/**
* Closes popup.
* Closes modal.
*/
public onCloseClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP);
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_MODAL_SHOWN);
}
/**
@ -119,54 +117,17 @@ export default class EditProfilePopup extends Vue {
</script>
<style scoped lang="scss">
.edit-profile-row-container {
width: 100%;
display: flex;
flex-direction: row;
align-content: center;
justify-content: flex-start;
margin-bottom: 40px;
}
.edit-profile-popup-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgb(134 134 148 / 40%);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
font-family: 'font_regular', sans-serif;
}
.edit-profile-popup {
width: 100%;
max-width: 440px;
.edit-profile {
background-color: #fff;
border-radius: 6px;
display: flex;
flex-direction: row;
align-items: flex-start;
position: relative;
justify-content: center;
padding: 80px;
&__info-panel-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
margin-right: 100px;
margin-top: 20px;
}
padding: 48px;
&__form-container {
width: 100%;
max-width: 440px;
margin-top: 10px;
&__row {
display: flex;
align-items: center;
margin-bottom: 30px;
&__avatar {
width: 60px;
@ -186,55 +147,21 @@ export default class EditProfilePopup extends Vue {
}
}
&__main-label-text {
&__label {
font-family: 'font_bold', sans-serif;
font-size: 32px;
line-height: 60px;
color: #384b65;
margin-top: 0;
}
}
&__button-container {
&__buttons {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 40px;
}
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 40px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
@media screen and (max-width: 720px) {
.edit-profile-popup {
&__info-panel-container {
display: none;
}
&__form-container {
&__button-container {
width: 100%;
}
}
column-gap: 20px;
}
}
</style>

View File

@ -0,0 +1,311 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="enable-mfa">
<h1 class="enable-mfa__title">Two-Factor Authentication</h1>
<p v-if="isScan" class="enable-mfa__subtitle">
Scan this QR code in your favorite TOTP app to get started.
</p>
<p v-if="isEnable" class="enable-mfa__subtitle max-width">
Enter the authentication code generated in your TOTP app to confirm your account is connected.
</p>
<p v-if="isCodes" class="enable-mfa__subtitle">
Save recovery codes.
</p>
<div v-if="isScan" class="enable-mfa__scan">
<h2 class="enable-mfa__scan__title">Scan this QR Code</h2>
<p class="enable-mfa__scan__subtitle">Scan the following QR code in your OTP app.</p>
<div class="enable-mfa__scan__qr">
<canvas ref="canvas" class="enable-mfa__scan__qr__canvas" />
</div>
<p class="enable-mfa__scan__subtitle">Unable to scan? Use the following code instead:</p>
<p class="enable-mfa__scan__secret">{{ userMFASecret }}</p>
</div>
<div v-if="isEnable" class="enable-mfa__confirm">
<h2 class="enable-mfa__confirm__title">Confirm Authentication Code</h2>
<ConfirmMFAInput :on-input="onConfirmInput" :is-error="isError" />
</div>
<div v-if="isCodes" class="enable-mfa__codes">
<h2 class="enable-mfa__codes__title max-width">
Please save these codes somewhere to be able to recover access to your account.
</h2>
<p
v-for="(code, index) in userMFARecoveryCodes"
:key="index"
>
{{ code }}
</p>
</div>
<div class="enable-mfa__buttons">
<VButton
v-if="!isCodes"
class="cancel-button"
label="Cancel"
width="100%"
height="44px"
is-white="true"
:on-press="closeModal"
/>
<VButton
v-if="isScan"
label="Continue"
width="100%"
height="44px"
:on-press="showEnable"
/>
<VButton
v-if="isEnable"
label="Enable"
width="100%"
height="44px"
:on-press="enable"
:is-disabled="!confirmPasscode || isLoading"
/>
<VButton
v-if="isCodes"
label="Done"
width="100%"
height="44px"
:on-press="closeModal"
/>
</div>
</div>
</template>
</VModal>
</template>
<script lang="ts">
import QRCode from 'qrcode';
import { Component, Vue } from 'vue-property-decorator';
import { USER_ACTIONS } from '@/store/modules/users';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import ConfirmMFAInput from '@/components/account/mfa/ConfirmMFAInput.vue';
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
// @vue/component
@Component({
components: {
ConfirmMFAInput,
VButton,
VModal,
},
})
export default class EnableMFAModal extends Vue {
public readonly qrLink =
`otpauth://totp/${encodeURIComponent(this.$store.getters.user.email)}?secret=${this.userMFASecret}&issuer=${encodeURIComponent(`STORJ ${this.satellite}`)}&algorithm=SHA1&digits=6&period=30`;
public isScan = true;
public isEnable = false;
public isCodes = false;
public isError = false;
public isLoading = false;
public confirmPasscode = '';
public $refs!: {
canvas: HTMLCanvasElement;
};
private readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Mounted lifecycle hook after initial render.
* Renders QR code.
*/
public async mounted(): Promise<void> {
await QRCode.toCanvas(this.$refs.canvas, this.qrLink);
}
/**
* Toggles view to Enable MFA state.
*/
public showEnable(): void {
this.isScan = false;
this.isEnable = true;
}
/**
* Closes enable MFA modal.
*/
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_ENABLE_MFA_MODAL_SHOWN);
}
/**
* Toggles view to MFA Recovery Codes state.
*/
public async showCodes(): Promise<void> {
await this.$store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_RECOVERY_CODES);
this.isEnable = false;
this.isCodes = true;
}
/**
* Sets confirmation passcode value from input.
*/
public onConfirmInput(value: string): void {
this.isError = false;
this.confirmPasscode = value;
}
/**
* Enables user MFA and sets view to Recovery Codes state.
*/
public async enable(): Promise<void> {
if (!this.confirmPasscode || this.isLoading || this.isError) return;
this.isLoading = true;
try {
await this.$store.dispatch(USER_ACTIONS.ENABLE_USER_MFA, this.confirmPasscode);
await this.$store.dispatch(USER_ACTIONS.GET);
await this.showCodes();
this.analytics.eventTriggered(AnalyticsEvent.MFA_ENABLED);
await this.$notify.success('MFA was enabled successfully');
} catch (error) {
await this.$notify.error(error.message);
this.isError = true;
}
this.isLoading = false;
}
/**
* Returns satellite name from store.
*/
private get satellite(): string {
return this.$store.state.appStateModule.satelliteName;
}
/**
* Returns pre-generated MFA secret from store.
*/
private get userMFASecret(): string {
return this.$store.state.usersModule.userMFASecret;
}
/**
* Returns user MFA recovery codes from store.
*/
private get userMFARecoveryCodes(): string[] {
return this.$store.state.usersModule.userMFARecoveryCodes;
}
}
</script>
<style scoped lang="scss">
.enable-mfa {
padding: 60px;
background: #fff;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 34px;
text-align: center;
color: #000;
margin: 0 0 30px;
}
&__subtitle {
font-size: 16px;
line-height: 21px;
text-align: center;
color: #000;
margin: 0 0 45px;
}
&__scan {
padding: 25px;
background: #f5f6fa;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
width: calc(100% - 50px);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
text-align: center;
color: #000;
margin: 0 0 30px;
}
&__subtitle {
font-size: 14px;
line-height: 25px;
text-align: center;
color: #000;
}
&__qr {
margin: 30px 0;
background: #fff;
border-radius: 6px;
padding: 10px;
&__canvas {
height: 200px !important;
width: 200px !important;
}
}
&__secret {
margin: 5px 0 0;
font-family: 'font_medium', sans-serif;
font-size: 14px;
line-height: 25px;
text-align: center;
color: #000;
}
}
&__confirm,
&__codes {
padding: 25px;
background: #f5f6fa;
border-radius: 6px;
width: calc(100% - 50px);
display: flex;
flex-direction: column;
align-items: center;
&__title {
font-size: 16px;
line-height: 19px;
text-align: center;
color: #000;
margin-bottom: 20px;
}
}
&__buttons {
display: flex;
align-items: center;
width: 100%;
margin-top: 30px;
}
}
.cancel-button {
margin-right: 15px;
}
.max-width {
max-width: 485px;
}
</style>

View File

@ -0,0 +1,106 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="recovery">
<h1 class="recovery__title">Two-Factor Authentication</h1>
<div class="recovery__codes">
<p class="recovery__codes__subtitle">
Please save these codes somewhere to be able to recover access to your account.
</p>
<p
v-for="(code, index) in userMFARecoveryCodes"
:key="index"
>
{{ code }}
</p>
</div>
<VButton
class="recovery__done-button"
label="Done"
width="100%"
height="44px"
:on-press="closeModal"
/>
</div>
</template>
</VModal>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import VButton from '@/components/common/VButton.vue';
import VModal from "@/components/common/VModal.vue";
// @vue/component
@Component({
components: {
VButton,
VModal,
},
})
export default class MFARecoveryCodesModal extends Vue {
/**
* Closes modal.
*/
public closeModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_MFA_RECOVERY_MODAL_SHOWN);
}
/**
* Returns MFA recovery codes from store.
*/
public get userMFARecoveryCodes(): string[] {
return this.$store.state.usersModule.userMFARecoveryCodes;
}
}
</script>
<style scoped lang="scss">
.recovery {
padding: 60px;
background: #fff;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'font_regular', sans-serif;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;
line-height: 34px;
text-align: center;
color: #000;
margin: 0 0 30px;
}
&__codes {
padding: 25px;
background: #f5f6fa;
border-radius: 6px;
width: calc(100% - 50px);
display: flex;
flex-direction: column;
align-items: center;
&__subtitle {
font-size: 16px;
line-height: 21px;
text-align: center;
color: #000;
margin: 0 0 30px;
max-width: 485px;
}
}
&__done-button {
margin-top: 30px;
}
}
</style>

View File

@ -28,7 +28,7 @@
label="+ Add"
width="122px"
height="48px"
:on-press="onAddUsersClick"
:on-press="toggleTeamMembersModal"
:is-disabled="isAddButtonDisabled"
/>
</div>
@ -74,7 +74,6 @@
<div v-if="isDeleteClicked" class="blur-content" />
<div v-if="isDeleteClicked" class="blur-search" />
</div>
<AddUserPopup v-if="isAddTeamMembersPopupShown" />
</div>
</template>
@ -84,7 +83,6 @@ import { Component, Prop, Vue } from 'vue-property-decorator';
import VButton from '@/components/common/VButton.vue';
import VHeader from '@/components/common/VHeader.vue';
import VInfo from '@/components/common/VInfo.vue';
import AddUserPopup from '@/components/team/AddUserPopup.vue';
import InfoIcon from '@/../static/images/team/infoTooltip.svg';
@ -92,9 +90,9 @@ import { RouteConfig } from '@/router';
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
import { ProjectMemberHeaderState } from '@/types/projectMembers';
import { Project } from '@/types/projects';
import { APP_STATE_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
import { PM_ACTIONS } from '@/utils/constants/actionNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
declare interface ClearSearch {
clearSearch(): void;
@ -105,7 +103,6 @@ declare interface ClearSearch {
components: {
VButton,
VHeader,
AddUserPopup,
VInfo,
InfoIcon,
},
@ -145,10 +142,10 @@ export default class HeaderArea extends Vue {
}
/**
* Opens add team members popup.
* Opens add team members modal.
*/
public onAddUsersClick(): void {
this.$store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
public toggleTeamMembersModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
}
public onFirstDeleteClick(): void {
@ -197,13 +194,6 @@ export default class HeaderArea extends Vue {
}
}
/**
* Indicates if add team member popup should be rendered.
*/
public get isAddTeamMembersPopupShown(): boolean {
return this.$store.state.appStateModule.appState.isAddTeamMembersPopupShown;
}
public get isDefaultState(): boolean {
return this.headerState === 0;
}

View File

@ -10,7 +10,7 @@ import { AppState } from '@/utils/constants/appStateEnum';
class ViewsState {
constructor(
public fetchState = AppState.LOADING,
public isAddTeamMembersPopupShown = false,
public isAddTeamMembersModalShown = false,
public isAccountDropdownShown = false,
public isSelectProjectDropdownShown = false,
public isResourcesDropdownShown = false,
@ -24,8 +24,8 @@ class ViewsState {
public isAGDatePickerShown = false,
public isChartsDatePickerShown = false,
public isPermissionsDropdownShown = false,
public isEditProfilePopupShown = false,
public isChangePasswordPopupShown = false,
public isEditProfileModalShown = false,
public isChangePasswordModalShown = false,
public isPaymentSelectionShown = false,
public isUploadCancelPopupVisible = false,
public isSuccessfulPasswordResetShown = false,
@ -33,6 +33,9 @@ class ViewsState {
public isCreateProjectModalShown = false,
public isAddPMModalShown = false,
public isOpenBucketModalShown = false,
public isMFARecoveryModalShown = false,
public isEnableMFAModalShown = false,
public isDisableMFAModalShown = false,
public onbAGStepBackRoute = "",
public onbAPIKeyStepBackRoute = "",
@ -65,8 +68,8 @@ interface AppContext {
export const appStateModule = {
state: new State(),
mutations: {
[APP_STATE_MUTATIONS.TOGGLE_ADD_TEAMMEMBER_POPUP](state: State): void {
state.appState.isAddTeamMembersPopupShown = !state.appState.isAddTeamMembersPopupShown;
[APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL](state: State): void {
state.appState.isAddTeamMembersModalShown = !state.appState.isAddTeamMembersModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_ACCOUNT_DROPDOWN](state: State): void {
state.appState.isAccountDropdownShown = !state.appState.isAccountDropdownShown;
@ -110,11 +113,11 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET](state: State): void {
state.appState.isSuccessfulPasswordResetShown = !state.appState.isSuccessfulPasswordResetShown;
},
[APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_POPUP](state: State): void {
state.appState.isChangePasswordPopupShown = !state.appState.isChangePasswordPopupShown;
[APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN](state: State): void {
state.appState.isChangePasswordModalShown = !state.appState.isChangePasswordModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_POPUP](state: State): void {
state.appState.isEditProfilePopupShown = !state.appState.isEditProfilePopupShown;
[APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_MODAL_SHOWN](state: State): void {
state.appState.isEditProfileModalShown = !state.appState.isEditProfileModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_UPLOAD_CANCEL_POPUP](state: State): void {
state.appState.isUploadCancelPopupVisible = !state.appState.isUploadCancelPopupVisible;
@ -131,6 +134,15 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.TOGGLE_OPEN_BUCKET_MODAL_SHOWN](state: State): void {
state.appState.isOpenBucketModalShown = !state.appState.isOpenBucketModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_MFA_RECOVERY_MODAL_SHOWN](state: State): void {
state.appState.isMFARecoveryModalShown = !state.appState.isMFARecoveryModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_ENABLE_MFA_MODAL_SHOWN](state: State): void {
state.appState.isEnableMFAModalShown = !state.appState.isEnableMFAModalShown;
},
[APP_STATE_MUTATIONS.TOGGLE_DISABLE_MFA_MODAL_SHOWN](state: State): void {
state.appState.isDisableMFAModalShown = !state.appState.isDisableMFAModalShown;
},
[APP_STATE_MUTATIONS.SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP](state: State, id: string): void {
state.appState.setDefaultPaymentMethodID = id;
},
@ -197,13 +209,6 @@ export const appStateModule = {
},
},
actions: {
[APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS]: function ({commit, state}: AppContext): void {
if (!state.appState.isAddTeamMembersPopupShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
}
commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAMMEMBER_POPUP);
},
[APP_STATE_ACTIONS.TOGGLE_ACCOUNT]: function ({commit, state}: AppContext): void {
if (!state.appState.isAccountDropdownShown) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);
@ -309,15 +314,9 @@ export const appStateModule = {
commit(APP_STATE_MUTATIONS.TOGGLE_SUCCESSFUL_PASSWORD_RESET);
},
[APP_STATE_ACTIONS.TOGGLE_CHANGE_PASSWORD_POPUP]: function ({commit}: AppContext): void {
commit(APP_STATE_MUTATIONS.TOGGLE_CHANGE_PASSWORD_POPUP);
},
[APP_STATE_ACTIONS.TOGGLE_UPLOAD_CANCEL_POPUP]: function ({commit}: AppContext): void {
commit(APP_STATE_MUTATIONS.TOGGLE_UPLOAD_CANCEL_POPUP);
},
[APP_STATE_ACTIONS.TOGGLE_EDIT_PROFILE_POPUP]: function ({commit}: AppContext): void {
commit(APP_STATE_MUTATIONS.TOGGLE_EDIT_PROFILE_POPUP);
},
[APP_STATE_ACTIONS.SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP]: function ({commit, state}: AppContext, methodID: string): void {
if (!state.appState.setDefaultPaymentMethodID) {
commit(APP_STATE_MUTATIONS.CLOSE_ALL);

View File

@ -10,7 +10,7 @@ export const NOTIFICATION_MUTATIONS = {
};
export const APP_STATE_MUTATIONS = {
TOGGLE_ADD_TEAMMEMBER_POPUP: 'TOGGLE_ADD_TEAMMEMBER_POPUP',
TOGGLE_ADD_TEAM_MEMBERS_MODAL: 'TOGGLE_ADD_TEAM_MEMBERS_MODAL',
TOGGLE_ACCOUNT_DROPDOWN: 'TOGGLE_ACCOUNT_DROPDOWN',
TOGGLE_SELECT_PROJECT_DROPDOWN: 'TOGGLE_SELECT_PROJECT_DROPDOWN',
TOGGLE_RESOURCES_DROPDOWN: 'TOGGLE_RESOURCES_DROPDOWN',
@ -26,13 +26,16 @@ export const APP_STATE_MUTATIONS = {
TOGGLE_PERMISSIONS_DROPDOWN: 'TOGGLE_PERMISSIONS_DROPDOWN',
TOGGLE_SUCCESSFUL_PASSWORD_RESET: 'TOGGLE_SUCCESSFUL_PASSWORD_RESET',
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP',
TOGGLE_EDIT_PROFILE_POPUP: 'TOGGLE_EDIT_PROFILE_POPUP',
TOGGLE_CHANGE_PASSWORD_POPUP: 'TOGGLE_CHANGE_PASSWORD_POPUP',
TOGGLE_EDIT_PROFILE_MODAL_SHOWN: 'TOGGLE_EDIT_PROFILE_MODAL_SHOWN',
TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN: 'TOGGLE_CHANGE_PASSWORD_MODAL_SHOWN',
TOGGLE_UPLOAD_CANCEL_POPUP: 'TOGGLE_UPLOAD_CANCEL_POPUP',
TOGGLE_CREATE_PROJECT_PROMPT_POPUP: 'TOGGLE_CREATE_PROJECT_PROMPT_POPUP',
TOGGLE_CREATE_PROJECT_POPUP: 'TOGGLE_CREATE_PROJECT_POPUP',
TOGGLE_IS_ADD_PM_MODAL_SHOWN: 'TOGGLE_IS_ADD_PM_MODAL_SHOWN',
TOGGLE_OPEN_BUCKET_MODAL_SHOWN: 'TOGGLE_OPEN_BUCKET_MODAL_SHOWN',
TOGGLE_MFA_RECOVERY_MODAL_SHOWN: 'TOGGLE_MFA_RECOVERY_MODAL_SHOWN',
TOGGLE_ENABLE_MFA_MODAL_SHOWN: 'TOGGLE_ENABLE_MFA_MODAL_SHOWN',
TOGGLE_DISABLE_MFA_MODAL_SHOWN: 'TOGGLE_DISABLE_MFA_MODAL_SHOWN',
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',
SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP',
CLOSE_ALL: 'CLOSE_ALL',

View File

@ -2,7 +2,6 @@
// See LICENSE for copying information.
export const APP_STATE_ACTIONS = {
TOGGLE_TEAM_MEMBERS: 'toggleAddTeamMembersPopup',
TOGGLE_ACCOUNT: 'toggleAccountDropdown',
TOGGLE_SELECT_PROJECT_DROPDOWN: 'toggleSelectProjectDropdown',
TOGGLE_RESOURCES_DROPDOWN: 'toggleResourcesDropdown',
@ -18,8 +17,6 @@ export const APP_STATE_ACTIONS = {
TOGGLE_PERMISSIONS_DROPDOWN: 'togglePermissionsDropdown',
TOGGLE_SUCCESSFUL_PASSWORD_RESET: 'TOGGLE_SUCCESSFUL_PASSWORD_RESET',
TOGGLE_SUCCESSFUL_PROJECT_CREATION_POPUP: 'toggleSuccessfulProjectCreationPopup',
TOGGLE_EDIT_PROFILE_POPUP: 'toggleEditProfilePopup',
TOGGLE_CHANGE_PASSWORD_POPUP: 'toggleChangePasswordPopup',
TOGGLE_UPLOAD_CANCEL_POPUP: 'toggleUploadCancelPopup',
SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'showSetDefaultPaymentMethodPopup',
CLOSE_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'closeSetDefaultPaymentMethodPopup',

View File

@ -32,7 +32,6 @@
</div>
</div>
</div>
<MFARecoveryCodesPopup v-if="isMFACodesPopup" :toggle-modal="toggleMFACodesPopup" />
<AllModals />
</div>
</template>
@ -44,7 +43,6 @@ import AllModals from "@/components/modals/AllModals.vue";
import PaidTierBar from '@/components/infoBars/PaidTierBar.vue';
import MFARecoveryCodeBar from '@/components/infoBars/MFARecoveryCodeBar.vue';
import BetaSatBar from '@/components/infoBars/BetaSatBar.vue';
import MFARecoveryCodesPopup from '@/components/account/mfa/MFARecoveryCodesPopup.vue';
import NavigationArea from '@/components/navigation/NavigationArea.vue';
import ProjectInfoBar from "@/components/infoBars/ProjectInfoBar.vue";
@ -86,7 +84,6 @@ const {
MFARecoveryCodeBar,
BetaSatBar,
ProjectInfoBar,
MFARecoveryCodesPopup,
},
})
export default class DashboardArea extends Vue {
@ -97,8 +94,6 @@ export default class DashboardArea extends Vue {
// Minimum number of recovery codes before the recovery code warning bar is shown.
public recoveryCodeWarningThreshold = 4;
public isMFACodesPopup = false;
public readonly analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
@ -180,12 +175,19 @@ export default class DashboardArea extends Vue {
public async generateNewMFARecoveryCodes(): Promise<void> {
try {
await this.$store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_RECOVERY_CODES);
this.toggleMFACodesPopup();
this.toggleMFARecoveryModal();
} catch (error) {
await this.$notify.error(error.message);
}
}
/**
* Toggles MFA recovery modal visibility.
*/
public toggleMFARecoveryModal(): void {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_MFA_RECOVERY_MODAL_SHOWN);
}
/**
* Opens add payment method modal.
*/
@ -193,13 +195,6 @@ export default class DashboardArea extends Vue {
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_IS_ADD_PM_MODAL_SHOWN);
}
/**
* Toggles MFA recovery codes popup visibility.
*/
public toggleMFACodesPopup(): void {
this.isMFACodesPopup = !this.isMFACodesPopup;
}
/**
* Checks if stored project is in fetched projects array and selects it.
* Selects first fetched project if check is not successful.

View File

@ -9,7 +9,7 @@ import { appStateModule } from '@/store/modules/appState';
import { makeNotificationsModule } from '@/store/modules/notifications';
import { makeProjectMembersModule } from '@/store/modules/projectMembers';
import { ProjectMember, ProjectMemberHeaderState, ProjectMembersPage } from '@/types/projectMembers';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { APP_STATE_MUTATIONS } from "@/store/mutationConstants";
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { ProjectMembersApiMock } from '../mock/api/projectMembers';
@ -50,27 +50,24 @@ describe('Team HeaderArea', () => {
});
it('renders correctly with opened Add team member popup', () => {
store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
const wrapper = shallowMount<HeaderArea>(HeaderArea, {
store,
localVue,
});
const addNewTemMemberPopup = wrapper.findAll('adduserpopup-stub');
expect(wrapper).toMatchSnapshot();
expect(addNewTemMemberPopup.length).toBe(1);
expect(wrapper.findAll('.header-default-state').length).toBe(1);
expect(wrapper.vm.isDeleteClicked).toBe(false);
expect(wrapper.findAll('.blur-content').length).toBe(0);
expect(wrapper.findAll('.blur-search').length).toBe(0);
store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
});
it('renders correctly with selected users', () => {
store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
const selectedUsersCount = 2;
@ -93,7 +90,7 @@ describe('Team HeaderArea', () => {
});
it('renders correctly with 2 selected users and delete clicked once', async () => {
store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
const selectedUsersCount = 2;
@ -120,7 +117,7 @@ describe('Team HeaderArea', () => {
});
it('renders correctly with 1 selected user and delete clicked once', async () => {
store.dispatch(APP_STATE_ACTIONS.TOGGLE_TEAM_MEMBERS);
store.commit(APP_STATE_MUTATIONS.TOGGLE_ADD_TEAM_MEMBERS_MODAL);
const selectedUsersCount = 1;

View File

@ -17,7 +17,6 @@ exports[`Team HeaderArea renders correctly 1`] = `
<!---->
<!---->
</div>
<!---->
</div>
`;
@ -41,7 +40,6 @@ exports[`Team HeaderArea renders correctly with 1 selected user and delete click
<div class="blur-content"></div>
<div class="blur-search"></div>
</div>
<adduserpopup-stub></adduserpopup-stub>
</div>
`;
@ -65,7 +63,6 @@ exports[`Team HeaderArea renders correctly with 2 selected users and delete clic
<div class="blur-content"></div>
<div class="blur-search"></div>
</div>
<!---->
</div>
`;
@ -86,7 +83,6 @@ exports[`Team HeaderArea renders correctly with opened Add team member popup 1`]
<!---->
<!---->
</div>
<adduserpopup-stub></adduserpopup-stub>
</div>
`;
@ -108,6 +104,5 @@ exports[`Team HeaderArea renders correctly with selected users 1`] = `
<!---->
<!---->
</div>
<adduserpopup-stub></adduserpopup-stub>
</div>
`;

View File

@ -17,7 +17,6 @@ exports[`Dashboard renders correctly when data is loaded 1`] = `
</div>
</div>
</div>
<!---->
<allmodals-stub></allmodals-stub>
</div>
`;
@ -27,7 +26,6 @@ exports[`Dashboard renders correctly when data is loading 1`] = `
<div class="loading-overlay active">
<loaderimage-stub class="loading-icon"></loaderimage-stub>
</div>
<!---->
<allmodals-stub></allmodals-stub>
</div>
`;