web/satellite: added update your session timeout banner

Added new banner to inform user that they can update their session timeout now.

Issue:
https://github.com/storj/storj/issues/5772

Change-Id: Icdf2164b80b12954d004537a4f31d30ef6bb12b8
This commit is contained in:
Vitalii 2023-04-27 14:16:53 +03:00 committed by Vitalii Shpital
parent 98562d06c8
commit 3f1166b5aa
7 changed files with 149 additions and 35 deletions

View File

@ -83,8 +83,11 @@ watch(() => props.dashboardRef, () => {
border-radius: 10px; border-radius: 10px;
box-shadow: 0 7px 20px rgba(0 0 0 / 15%); box-shadow: 0 7px 20px rgba(0 0 0 / 15%);
@media screen and (max-width: 800px) { @media screen and (max-width: 450px) {
margin: 0 1.5rem; flex-direction: column;
align-items: flex-start;
row-gap: 10px;
position: relative;
} }
&__icon { &__icon {
@ -123,6 +126,7 @@ watch(() => props.dashboardRef, () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
column-gap: 10px;
} }
&__close { &__close {
@ -130,6 +134,13 @@ watch(() => props.dashboardRef, () => {
height: 15px; height: 15px;
margin-left: 2.375rem; margin-left: 2.375rem;
cursor: pointer; cursor: pointer;
flex-shrink: 0;
@media screen and (max-width: 450px) {
position: absolute;
right: 20px;
top: 20px;
}
} }
} }
@ -146,11 +157,4 @@ watch(() => props.dashboardRef, () => {
text-decoration: underline !important; text-decoration: underline !important;
cursor: pointer; cursor: pointer;
} }
@media screen and (max-width: 500px) {
.notification-wrap {
right: 15px;
}
}
</style> </style>

View File

@ -0,0 +1,56 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<v-banner
severity="info"
:dashboard-ref="dashboardRef"
:on-close="onCloseClick"
>
<template #text>
<p class="medium">
You can now update your session timeout from your
<span class="link" @click.stop.self="redirectToSettingsPage">account settings</span>
</p>
</template>
</v-banner>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { RouteConfig } from '@/router';
import { useRouter } from '@/utils/hooks';
import { useAppStore } from '@/store/modules/appStore';
import VBanner from '@/components/common/VBanner.vue';
const appStore = useAppStore();
const nativeRouter = useRouter();
const router = reactive(nativeRouter);
const props = defineProps<{
dashboardRef: HTMLElement
}>();
/**
* Redirects to settings page.
*/
function redirectToSettingsPage(): void {
onCloseClick();
if (router.currentRoute.path.includes(RouteConfig.AllProjectsDashboard.path)) {
router.push(RouteConfig.AccountSettings.with(RouteConfig.Settings2).path);
return;
}
router.push(RouteConfig.Account.with(RouteConfig.Settings).path);
}
/**
* Closes notification.
*/
function onCloseClick(): void {
appStore.closeUpdateSessionTimeoutBanner();
}
</script>

View File

@ -455,6 +455,7 @@ onBeforeUnmount((): void => {
<style scoped lang="scss"> <style scoped lang="scss">
.project-dashboard { .project-dashboard {
max-width: calc(100vw - 280px - 95px); max-width: calc(100vw - 280px - 95px);
background-origin: content-box;
background-image: url('../../../../static/images/project/background.png'); background-image: url('../../../../static/images/project/background.png');
background-position: top right; background-position: top right;
background-size: 70%; background-size: 70%;

View File

@ -7,10 +7,12 @@ import { defineStore } from 'pinia';
import { OnboardingOS, PricingPlanInfo } from '@/types/common'; import { OnboardingOS, PricingPlanInfo } from '@/types/common';
import { FetchState } from '@/utils/constants/fetchStateEnum'; import { FetchState } from '@/utils/constants/fetchStateEnum';
import { ManageProjectPassphraseStep } from '@/types/managePassphrase'; import { ManageProjectPassphraseStep } from '@/types/managePassphrase';
import { LocalData } from '@/utils/localData';
class AppState { class AppState {
public fetchState = FetchState.LOADING; public fetchState = FetchState.LOADING;
public isSuccessfulPasswordResetShown = false; public isSuccessfulPasswordResetShown = false;
public isUpdateSessionTimeoutBanner = !LocalData.getSessionTimeoutBannerAcknowledged();
public hasJustLoggedIn = false; public hasJustLoggedIn = false;
public onbAGStepBackRoute = ''; public onbAGStepBackRoute = '';
public onbAPIKeyStepBackRoute = ''; public onbAPIKeyStepBackRoute = '';
@ -125,6 +127,12 @@ export const useAppStore = defineStore('app', () => {
state.isLargeUploadNotificationShown = value; state.isLargeUploadNotificationShown = value;
} }
function closeUpdateSessionTimeoutBanner(): void {
LocalData.setSessionTimeoutBannerAcknowledged();
state.isUpdateSessionTimeoutBanner = false;
}
function closeDropdowns(): void { function closeDropdowns(): void {
state.activeDropdown = ''; state.activeDropdown = '';
} }
@ -174,6 +182,7 @@ export const useAppStore = defineStore('app', () => {
setLargeUploadWarningNotification, setLargeUploadWarningNotification,
setLargeUploadNotification, setLargeUploadNotification,
closeDropdowns, closeDropdowns,
closeUpdateSessionTimeoutBanner,
setErrorPage, setErrorPage,
removeErrorPage, removeErrorPage,
clear, clear,

View File

@ -9,6 +9,7 @@ export class LocalData {
private static bucketWasCreated = 'bucketWasCreated'; private static bucketWasCreated = 'bucketWasCreated';
private static demoBucketCreated = 'demoBucketCreated'; private static demoBucketCreated = 'demoBucketCreated';
private static bucketGuideHidden = 'bucketGuideHidden'; private static bucketGuideHidden = 'bucketGuideHidden';
private static sessionTimeoutBannerAcknowledged = 'sessionTimeoutBannerAcknowledged';
private static serverSideEncryptionBannerHidden = 'serverSideEncryptionBannerHidden'; private static serverSideEncryptionBannerHidden = 'serverSideEncryptionBannerHidden';
private static serverSideEncryptionModalHidden = 'serverSideEncryptionModalHidden'; private static serverSideEncryptionModalHidden = 'serverSideEncryptionModalHidden';
private static largeUploadNotificationDismissed = 'largeUploadNotificationDismissed'; private static largeUploadNotificationDismissed = 'largeUploadNotificationDismissed';
@ -61,6 +62,14 @@ export class LocalData {
return value === 'true'; return value === 'true';
} }
public static getSessionTimeoutBannerAcknowledged(): boolean {
return Boolean(localStorage.getItem(LocalData.sessionTimeoutBannerAcknowledged));
}
public static setSessionTimeoutBannerAcknowledged(): void {
localStorage.setItem(LocalData.sessionTimeoutBannerAcknowledged, 'true');
}
/** /**
* "Disable" showing the server-side encryption banner on the bucket page * "Disable" showing the server-side encryption banner on the bucket page
*/ */

View File

@ -16,6 +16,11 @@
<BetaSatBar v-if="isBetaSatellite" /> <BetaSatBar v-if="isBetaSatellite" />
<MFARecoveryCodeBar v-if="showMFARecoveryCodeBar" :open-generate-modal="generateNewMFARecoveryCodes" /> <MFARecoveryCodeBar v-if="showMFARecoveryCodeBar" :open-generate-modal="generateNewMFARecoveryCodes" />
<div class="banner-container dashboard__wrap__main-area__content-wrap__container__content"> <div class="banner-container dashboard__wrap__main-area__content-wrap__container__content">
<UpdateSessionTimeoutBanner
v-if="isUpdateSessionTimeoutBanner && dashboardContent"
:dashboard-ref="dashboardContent"
/>
<UpgradeNotification <UpgradeNotification
v-if="isPaidTierBannerShown" v-if="isPaidTierBannerShown"
:open-add-p-m-modal="togglePMModal" :open-add-p-m-modal="togglePMModal"
@ -167,6 +172,7 @@ import VBanner from '@/components/common/VBanner.vue';
import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue'; import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue';
import ProjectLimitBanner from '@/components/notifications/ProjectLimitBanner.vue'; import ProjectLimitBanner from '@/components/notifications/ProjectLimitBanner.vue';
import BrandedLoader from '@/components/common/BrandedLoader.vue'; import BrandedLoader from '@/components/common/BrandedLoader.vue';
import UpdateSessionTimeoutBanner from '@/components/notifications/UpdateSessionTimeoutBanner.vue';
import CloudIcon from '@/../static/images/notifications/cloudAlert.svg'; import CloudIcon from '@/../static/images/notifications/cloudAlert.svg';
import WarningIcon from '@/../static/images/notifications/circleWarning.svg'; import WarningIcon from '@/../static/images/notifications/circleWarning.svg';
@ -226,6 +232,13 @@ const sessionRefreshInterval = computed((): number => {
return sessionDuration.value / 2; return sessionDuration.value / 2;
}); });
/**
* Indicates whether the update session timeout notification should be shown.
*/
const isUpdateSessionTimeoutBanner = computed((): boolean => {
return router.currentRoute.name !== RouteConfig.Settings.name && appStore.state.isUpdateSessionTimeoutBanner;
});
/** /**
* Indicates whether to display the session timer for debugging. * Indicates whether to display the session timer for debugging.
*/ */
@ -713,10 +726,17 @@ onMounted(async () => {
} }
try { try {
await usersStore.getUser(); await Promise.all([
await usersStore.getFrozenStatus(); usersStore.getUser(),
await abTestingStore.fetchValues(); usersStore.getFrozenStatus(),
await usersStore.getSettings(); abTestingStore.fetchValues(),
usersStore.getSettings(),
]);
if (usersStore.state.settings.sessionDuration && appStore.state.isUpdateSessionTimeoutBanner) {
appStore.closeUpdateSessionTimeoutBanner();
}
setupSessionTimers(); setupSessionTimers();
} catch (error) { } catch (error) {
if (!(error instanceof ErrorUnauthorized)) { if (!(error instanceof ErrorUnauthorized)) {
@ -898,6 +918,10 @@ onBeforeUnmount(() => {
.dashboard__wrap__main-area__content-wrap__container__content { .dashboard__wrap__main-area__content-wrap__container__content {
padding: 32px 24px 50px; padding: 32px 24px 50px;
} }
.banner-container {
padding-bottom: 0;
}
} }
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {

View File

@ -18,6 +18,11 @@
<div class="all-dashboard__content__divider" /> <div class="all-dashboard__content__divider" />
<div class="all-dashboard__banners"> <div class="all-dashboard__banners">
<UpdateSessionTimeoutBanner
v-if="isUpdateSessionTimeoutBanner && dashboardContent"
:dashboard-ref="dashboardContent"
/>
<UpgradeNotification <UpgradeNotification
v-if="isPaidTierBannerShown" v-if="isPaidTierBannerShown"
class="all-dashboard__banners__upgrade" class="all-dashboard__banners__upgrade"
@ -151,6 +156,7 @@ import LimitWarningModal from '@/components/modals/LimitWarningModal.vue';
import VBanner from '@/components/common/VBanner.vue'; import VBanner from '@/components/common/VBanner.vue';
import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue'; import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue';
import ProjectLimitBanner from '@/components/notifications/ProjectLimitBanner.vue'; import ProjectLimitBanner from '@/components/notifications/ProjectLimitBanner.vue';
import UpdateSessionTimeoutBanner from '@/components/notifications/UpdateSessionTimeoutBanner.vue';
import LoaderImage from '@/../static/images/common/loadIcon.svg'; import LoaderImage from '@/../static/images/common/loadIcon.svg';
@ -209,6 +215,13 @@ const sessionRefreshInterval = computed((): number => {
return sessionDuration.value / 2; return sessionDuration.value / 2;
}); });
/**
* Indicates whether the update session timeout notification should be shown.
*/
const isUpdateSessionTimeoutBanner = computed((): boolean => {
return router.currentRoute.name !== RouteConfig.Settings2.name && appStore.state.isUpdateSessionTimeoutBanner;
});
/** /**
* Indicates whether to display the session timer for debugging. * Indicates whether to display the session timer for debugging.
*/ */
@ -427,7 +440,7 @@ async function handleInactive(): Promise<void> {
} catch (error) { } catch (error) {
if (error instanceof ErrorUnauthorized) return; if (error instanceof ErrorUnauthorized) return;
await notify.error(error.message, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR); notify.error(error.message, AnalyticsErrorEventSource.OVERALL_SESSION_EXPIRED_ERROR);
} }
} }
@ -439,7 +452,7 @@ async function generateNewMFARecoveryCodes(): Promise<void> {
await usersStore.generateUserMFARecoveryCodes(); await usersStore.generateUserMFARecoveryCodes();
toggleMFARecoveryModal(); toggleMFARecoveryModal();
} catch (error) { } catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD); notify.error(error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
} }
} }
@ -520,7 +533,7 @@ function restartSessionTimers(): void {
inactivityModalShown.value = true; inactivityModalShown.value = true;
inactivityTimerId.value = setTimeout(async () => { inactivityTimerId.value = setTimeout(async () => {
await clearStoreAndTimers(); await clearStoreAndTimers();
await notify.notify('Your session was timed out.'); notify.notify('Your session was timed out.');
}, inactivityModalTime); }, inactivityModalTime);
}, sessionDuration.value - inactivityModalTime); }, sessionDuration.value - inactivityModalTime);
@ -553,7 +566,7 @@ async function refreshSession(): Promise<void> {
try { try {
LocalData.setSessionExpirationDate(await auth.refreshSession()); LocalData.setSessionExpirationDate(await auth.refreshSession());
} catch (error) { } catch (error) {
await notify.error((error instanceof ErrorUnauthorized) ? 'Your session was timed out.' : error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD); notify.error((error instanceof ErrorUnauthorized) ? 'Your session was timed out.' : error.message, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
await handleInactive(); await handleInactive();
isSessionRefreshing.value = false; isSessionRefreshing.value = false;
return; return;
@ -594,10 +607,17 @@ onMounted(async () => {
}); });
try { try {
await usersStore.getUser(); await Promise.all([
await usersStore.getFrozenStatus(); usersStore.getUser(),
await abTestingStore.fetchValues(); usersStore.getFrozenStatus(),
await usersStore.getSettings(); abTestingStore.fetchValues(),
usersStore.getSettings(),
]);
if (usersStore.state.settings.sessionDuration && appStore.state.isUpdateSessionTimeoutBanner) {
appStore.closeUpdateSessionTimeoutBanner();
}
setupSessionTimers(); setupSessionTimers();
} catch (error) { } catch (error) {
if (!(error instanceof ErrorUnauthorized)) { if (!(error instanceof ErrorUnauthorized)) {
@ -614,26 +634,26 @@ onMounted(async () => {
agStore.stopWorker(); agStore.stopWorker();
await agStore.startWorker(); await agStore.startWorker();
} catch (error) { } catch (error) {
await notify.error(`Unable to set access grants wizard. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD); notify.error(`Unable to set access grants wizard. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
} }
try { try {
const couponType = await billingStore.setupAccount(); const couponType = await billingStore.setupAccount();
if (couponType === CouponType.NoCoupon) { if (couponType === CouponType.NoCoupon) {
await notify.error(`The coupon code was invalid, and could not be applied to your account`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD); notify.error(`The coupon code was invalid, and could not be applied to your account`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
} }
if (couponType === CouponType.SignupCoupon) { if (couponType === CouponType.SignupCoupon) {
await notify.success(`The coupon code was added successfully`); notify.success(`The coupon code was added successfully`);
} }
} catch (error) { } catch (error) {
await notify.error(`Unable to setup account. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD); notify.error(`Unable to setup account. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
} }
try { try {
await billingStore.getCreditCards(); await billingStore.getCreditCards();
} catch (error) { } catch (error) {
await notify.error(`Unable to get credit cards. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD); notify.error(`Unable to get credit cards. ${error.message}`, AnalyticsErrorEventSource.ALL_PROJECT_DASHBOARD);
} }
try { try {
@ -718,15 +738,6 @@ onBeforeUnmount(() => {
&__banners { &__banners {
margin-bottom: 20px; margin-bottom: 20px;
&__billing {
position: initial;
margin-top: 20px;
& :deep(.notification-wrap__content) {
position: initial;
}
}
&__upgrade, &__upgrade,
&__project-limit, &__project-limit,
&__freeze, &__freeze,