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:
parent
1be5277c2a
commit
27f6fbdeda
@ -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.
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@
|
||||
class="confirm-mfa__input"
|
||||
:placeholder="isRecovery ? 'Code' : '000000'"
|
||||
:type="isRecovery ? 'text' : 'number'"
|
||||
autofocus
|
||||
@input="event => onInput(event.target.value)"
|
||||
>
|
||||
</div>
|
||||
|
@ -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>
|
@ -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>
|
@ -1,112 +1,112 @@
|
||||
// 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">
|
||||
<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>
|
||||
<img src="@/../static/images/team/addMember.jpg" alt="add team member image">
|
||||
</div>
|
||||
<div class="add-user__form-container">
|
||||
<p v-if="!formError" class="add-user__form-container__common-label">Email Address</p>
|
||||
<div v-if="formError" class="add-user__form-container__label">
|
||||
<ErrorIcon alt="Red error icon" />
|
||||
<p class="add-user__form-container__label__error">{{ formError }}</p>
|
||||
<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>
|
||||
<img src="@/../static/images/team/addMember.jpg" alt="add team member image">
|
||||
</div>
|
||||
<div class="add-user__form-container__inputs-group" :class="{ 'scrollable': isInputsGroupScrollable }">
|
||||
<div
|
||||
v-for="(input, index) in inputs"
|
||||
:key="index"
|
||||
class="add-user__form-container__inputs-group__item"
|
||||
>
|
||||
<input
|
||||
v-model="input.value"
|
||||
placeholder="email@example.com"
|
||||
class="no-error-input"
|
||||
:class="{ 'error-input': input.error }"
|
||||
@keyup="resetFormErrors(index)"
|
||||
<div class="add-user__form-container">
|
||||
<p v-if="!formError" class="add-user__form-container__common-label">Email Address</p>
|
||||
<div v-if="formError" class="add-user__form-container__label">
|
||||
<ErrorIcon alt="Red error icon" />
|
||||
<p class="add-user__form-container__label__error">{{ formError }}</p>
|
||||
</div>
|
||||
<div class="add-user__form-container__inputs-group" :class="{ 'scrollable': isInputsGroupScrollable }">
|
||||
<div
|
||||
v-for="(input, index) in inputs"
|
||||
:key="index"
|
||||
class="add-user__form-container__inputs-group__item"
|
||||
>
|
||||
<DeleteFieldIcon
|
||||
class="add-user__form-container__inputs-group__item__image"
|
||||
@click="deleteInput(index)"
|
||||
<input
|
||||
v-model="input.value"
|
||||
placeholder="email@example.com"
|
||||
class="no-error-input"
|
||||
:class="{ 'error-input': input.error }"
|
||||
@keyup="resetFormErrors(index)"
|
||||
>
|
||||
<DeleteFieldIcon
|
||||
class="add-user__form-container__inputs-group__item__image"
|
||||
@click="deleteInput(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-user-row">
|
||||
<div id="add-user-button" class="add-user-row__item" @click="addInput">
|
||||
<div :class="{ 'inactive-image': isMaxInputsCount }">
|
||||
<AddFieldIcon class="add-user-row__item__image" />
|
||||
</div>
|
||||
<p class="add-user-row__item__label" :class="{ 'inactive-label': isMaxInputsCount }">Add More</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-user__form-container__button-container">
|
||||
<VButton
|
||||
label="Cancel"
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="closeModal"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Add Team Members"
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="onAddUsersClick"
|
||||
:is-disabled="!isButtonActive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-user-row">
|
||||
<div id="add-user-button" class="add-user-row__item" @click="addInput">
|
||||
<div :class="{ 'inactive-image': isMaxInputsCount }">
|
||||
<AddFieldIcon class="add-user-row__item__image" />
|
||||
</div>
|
||||
<p class="add-user-row__item__label" :class="{ 'inactive-label': isMaxInputsCount }">Add More</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-user__form-container__button-container">
|
||||
<VButton
|
||||
label="Cancel"
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="onClose"
|
||||
is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Add Team Members"
|
||||
width="205px"
|
||||
height="48px"
|
||||
:on-press="onAddUsersClick"
|
||||
:is-disabled="!isButtonActive"
|
||||
/>
|
||||
</div>
|
||||
<div class="notification-wrap">
|
||||
<AddMemberNotificationIcon class="notification-wrap__image" />
|
||||
<div class="notification-wrap__text-area">
|
||||
<p class="notification-wrap__text-area__text">
|
||||
If the team member you want to invite to join the project is still not on this Satellite, please
|
||||
share this link to the signup page and ask them to register here:
|
||||
<router-link target="_blank" rel="noopener noreferrer" exact to="/signup">
|
||||
{{ registerPath }}
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-user__close-cross-container" @click="onClose">
|
||||
<CloseCrossIcon />
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-wrap">
|
||||
<AddMemberNotificationIcon class="notification-wrap__image" />
|
||||
<div class="notification-wrap__text-area">
|
||||
<p class="notification-wrap__text-area__text">
|
||||
If the team member you want to invite to join the project is still not on this Satellite, please
|
||||
share this link to the signup page and ask them to register here:
|
||||
<router-link target="_blank" rel="noopener noreferrer" exact to="/signup">
|
||||
{{ registerPath }}
|
||||
</router-link>
|
||||
</p>
|
||||
</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;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 80px 0 50px;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
top: 40px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover .close-cross-svg-path {
|
||||
fill: #2683ff;
|
||||
&__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>
|
@ -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>
|
||||
|
@ -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;
|
||||
flex-direction: column;
|
||||
padding: 48px;
|
||||
|
||||
&__info-panel-container {
|
||||
&__row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
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;
|
||||
}
|
||||
|
||||
&__button-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 32px;
|
||||
margin: 0 0 0 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&__close-cross-container {
|
||||
&__buttons {
|
||||
width: 100%;
|
||||
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;
|
||||
}
|
||||
margin-top: 32px;
|
||||
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>
|
@ -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;
|
||||
|
@ -2,56 +2,55 @@
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<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">
|
||||
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>
|
||||
<ConfirmMFAInput ref="mfaInput" :on-input="onConfirmInput" :is-error="isError" :is-recovery="isRecoveryCodeState" />
|
||||
<span class="disable-mfa__container__confirm__toggle" @click="toggleRecoveryCodeState">
|
||||
Or use {{ isRecoveryCodeState ? '2FA code' : 'recovery code' }}
|
||||
</span>
|
||||
<VModal :on-close="closeModal">
|
||||
<template #content>
|
||||
<div class="disable-mfa">
|
||||
<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__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__confirm__toggle" @click="toggleRecoveryCodeState">
|
||||
Or use {{ isRecoveryCodeState ? '2FA code' : 'recovery code' }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="disable-mfa__info">
|
||||
After disabling 2FA, remove the authentication code from your TOTP app.
|
||||
</p>
|
||||
<div class="disable-mfa__buttons">
|
||||
<VButton
|
||||
class="disable-mfa__buttons__cancel-button"
|
||||
label="Cancel"
|
||||
width="50%"
|
||||
height="44px"
|
||||
is-white="true"
|
||||
:on-press="closeModal"
|
||||
/>
|
||||
<VButton
|
||||
label="Disable 2FA"
|
||||
width="50%"
|
||||
height="44px"
|
||||
:on-press="disable"
|
||||
:is-disabled="!(request.recoveryCode || request.passcode) || isLoading"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="disable-mfa__container__info">
|
||||
After disabling 2FA, remove the authentication code from your TOTP app.
|
||||
</p>
|
||||
<div class="disable-mfa__container__buttons">
|
||||
<VButton
|
||||
class="cancel-button"
|
||||
label="Cancel"
|
||||
width="50%"
|
||||
height="44px"
|
||||
is-white="true"
|
||||
:on-press="toggleModal"
|
||||
/>
|
||||
<VButton
|
||||
label="Disable 2FA"
|
||||
width="50%"
|
||||
height="44px"
|
||||
:on-press="disable"
|
||||
: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;
|
||||
@ -122,22 +125,8 @@ export default class DisableMFAPopup extends Vue {
|
||||
</script>
|
||||
|
||||
<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 {
|
||||
.disable-mfa {
|
||||
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 {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@media screen and (max-height: 750px) {
|
||||
|
||||
.disable-mfa {
|
||||
padding-bottom: 20px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -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;
|
||||
flex-direction: column;
|
||||
padding: 48px;
|
||||
|
||||
&__info-panel-container {
|
||||
&__row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-right: 100px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__form-container {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
margin-top: 10px;
|
||||
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 {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&__close-cross-container {
|
||||
&__buttons {
|
||||
width: 100%;
|
||||
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%;
|
||||
}
|
||||
}
|
||||
margin-top: 40px;
|
||||
column-gap: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
311
web/satellite/src/components/modals/EnableMFAModal.vue
Normal file
311
web/satellite/src/components/modals/EnableMFAModal.vue
Normal 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>
|
106
web/satellite/src/components/modals/MFARecoveryCodesModal.vue
Normal file
106
web/satellite/src/components/modals/MFARecoveryCodesModal.vue
Normal 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>
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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>
|
||||
`;
|
||||
|
Loading…
Reference in New Issue
Block a user