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:
Wilfred Asomani 2023-02-24 10:32:49 +00:00 committed by Storj Robot
parent 52abe8ddb4
commit 31e386c607
3 changed files with 265 additions and 2 deletions

View 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>

View File

@ -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,

View File

@ -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>