web/satellite: add new settings page
This change implements a new account settings page for the all projects dashboard. Issue: https://github.com/storj/storj/issues/5514 Change-Id: Id777cd5c1efe3fa4b40234771ae2a99cc5cb9dd3
This commit is contained in:
parent
52abe8ddb4
commit
31e386c607
262
web/satellite/src/components/account/NewSettingsArea.vue
Normal file
262
web/satellite/src/components/account/NewSettingsArea.vue
Normal file
@ -0,0 +1,262 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="settings">
|
||||
<h1 class="settings__title" aria-roledescription="title">Account Settings</h1>
|
||||
|
||||
<div class="settings__section">
|
||||
<p class="settings__section__title">Profile</p>
|
||||
<div class="settings__section__content">
|
||||
<div class="settings__section__content__row">
|
||||
<span class="settings__section__content__row__title">Full Name</span>
|
||||
<span class="settings__section__content__row__subtitle">{{ user.fullName }}</span>
|
||||
<div class="settings__section__content__row__actions">
|
||||
<VButton
|
||||
class="button"
|
||||
font-size="14px"
|
||||
width="110px"
|
||||
is-white
|
||||
:on-press="toggleEditProfileModal"
|
||||
label="Change Name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings__section__content__divider" />
|
||||
|
||||
<div class="settings__section__content__row">
|
||||
<span class="settings__section__content__row__title">Email</span>
|
||||
<span class="settings__section__content__row__subtitle">{{ user.email }}</span>
|
||||
<div class="settings__section__content__row__empty-actions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings__section">
|
||||
<p class="settings__section__title">Security</p>
|
||||
<div class="settings__section__content">
|
||||
<div class="settings__section__content__row">
|
||||
<span class="settings__section__content__row__title">Password</span>
|
||||
<span class="settings__section__content__row__subtitle">**************</span>
|
||||
<div class="settings__section__content__row__actions">
|
||||
<VButton
|
||||
class="button"
|
||||
is-white
|
||||
font-size="14px"
|
||||
width="136px"
|
||||
:on-press="toggleChangePasswordModal"
|
||||
label="Change Password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings__section__content__divider" />
|
||||
|
||||
<div class="settings__section__content__row">
|
||||
<span class="settings__section__content__row__title">Two-Factor Authentication</span>
|
||||
<span v-if="!user.isMFAEnabled" class="settings__section__content__row__subtitle">Improve account security by enabling 2FA.</span>
|
||||
<span v-else class="settings__section__content__row__subtitle">2FA is enabled.</span>
|
||||
<div class="settings__section__content__row__actions">
|
||||
<VButton
|
||||
v-if="user.isMFAEnabled"
|
||||
class="button"
|
||||
font-size="14px"
|
||||
width="208px"
|
||||
label="Regenerate Recovery Codes"
|
||||
is-white
|
||||
:on-press="generateNewMFARecoveryCodes"
|
||||
:is-disabled="isLoading"
|
||||
/>
|
||||
<VButton
|
||||
class="button"
|
||||
font-size="14px"
|
||||
width="90px"
|
||||
:is-white="user.isMFAEnabled"
|
||||
:on-press="!user.isMFAEnabled ? enableMFA : toggleDisableMFAModal"
|
||||
:label="!user.isMFAEnabled ? 'Enable 2FA' : 'Disable 2FA'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue';
|
||||
|
||||
import { USER_ACTIONS } from '@/store/modules/users';
|
||||
import { User } from '@/types/users';
|
||||
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||
import { useNotify, useStore } from '@/utils/hooks';
|
||||
import { useLoading } from '@/composables/useLoading';
|
||||
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';
|
||||
|
||||
const store = useStore();
|
||||
const notify = useNotify();
|
||||
const { isLoading, withLoading } = useLoading();
|
||||
|
||||
/**
|
||||
* Returns user info from store.
|
||||
*/
|
||||
const user = computed((): User => {
|
||||
return store.getters.user;
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles enable MFA modal visibility.
|
||||
*/
|
||||
function toggleEnableMFAModal(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.enableMFA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles disable MFA modal visibility.
|
||||
*/
|
||||
function toggleDisableMFAModal(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.disableMFA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles MFA recovery codes modal visibility.
|
||||
*/
|
||||
function toggleMFACodesModal(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.mfaRecovery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens change password popup.
|
||||
*/
|
||||
function toggleChangePasswordModal(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.changePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens edit account info modal.
|
||||
*/
|
||||
function toggleEditProfileModal(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.editProfile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates user's MFA secret and opens popup.
|
||||
*/
|
||||
async function enableMFA(): Promise<void> {
|
||||
await withLoading(async () => {
|
||||
try {
|
||||
await store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_SECRET);
|
||||
toggleEnableMFAModal();
|
||||
} catch (error) {
|
||||
await notify.error(error.message, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles generate new MFA recovery codes popup visibility.
|
||||
*/
|
||||
async function generateNewMFARecoveryCodes(): Promise<void> {
|
||||
await withLoading(async () => {
|
||||
try {
|
||||
await store.dispatch(USER_ACTIONS.GENERATE_USER_MFA_RECOVERY_CODES);
|
||||
toggleMFACodesModal();
|
||||
} catch (error) {
|
||||
await notify.error(error.message, AnalyticsErrorEventSource.ACCOUNT_SETTINGS_AREA);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle hook after initial render where user info is fetching.
|
||||
*/
|
||||
onMounted(() => {
|
||||
store.dispatch(USER_ACTIONS.GET);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.settings {
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
|
||||
&__section {
|
||||
margin-top: 40px;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
font-size: 18px;
|
||||
line-height: 27px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-top: 20px;
|
||||
background: var(--c-white);
|
||||
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
|
||||
border-radius: 8px;
|
||||
padding: 10px 20px;
|
||||
|
||||
&__divider {
|
||||
margin: 10px;
|
||||
border: 0.5px solid var(--c-grey-2);
|
||||
}
|
||||
|
||||
&__row {
|
||||
padding: 10px;
|
||||
min-height: 55px;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--c-grey-6);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: 5px;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: 100%;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
@ -47,6 +47,7 @@ import SuccessScreen from '@/components/onboardingTour/steps/cliFlow/SuccessScre
|
||||
import AGName from '@/components/onboardingTour/steps/cliFlow/AGName.vue';
|
||||
import AGPermissions from '@/components/onboardingTour/steps/cliFlow/AGPermissions.vue';
|
||||
import BucketDetails from '@/components/objects/BucketDetails.vue';
|
||||
import NewSettingsArea from '@/components/account/NewSettingsArea.vue';
|
||||
|
||||
const ActivateAccount = () => import('@/views/ActivateAccount.vue');
|
||||
const AuthorizeArea = () => import('@/views/AuthorizeArea.vue');
|
||||
@ -436,7 +437,7 @@ export const router = new Router({
|
||||
{
|
||||
path: RouteConfig.Settings2.path,
|
||||
name: RouteConfig.Settings2.name,
|
||||
component: SettingsArea,
|
||||
component: NewSettingsArea,
|
||||
},
|
||||
{
|
||||
path: RouteConfig.Billing2.path,
|
||||
|
@ -14,7 +14,7 @@
|
||||
</a>
|
||||
|
||||
<div v-if="isDropdownOpen" class="project-item__header__dropdown">
|
||||
<div class="project-item__header__dropdown__item" @click.stop.prevent="goToProjectEdit">
|
||||
<div v-if="isOwner" class="project-item__header__dropdown__item" @click.stop.prevent="goToProjectEdit">
|
||||
<gear-icon />
|
||||
<p class="project-item__header__dropdown__item__label">Project settings</p>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user