web/satellite: update session timeout modals

This change opens up the "set session timeout" modal for users to set a
custom timeout duration if they haven't already.
It also fixes a few UI/UX issues on the modal including toggling on the
dashboard.

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

Change-Id: I0e71e191049b242e638ca36214d6dd33f78ae5fe
This commit is contained in:
Wilfred Asomani 2023-04-26 17:10:52 +00:00 committed by Storj Robot
parent e0542c2d24
commit 034431e4a8
6 changed files with 92 additions and 27 deletions

View File

@ -2,7 +2,7 @@
// See LICENSE for copying information.
<template>
<VModal :on-close="withLoading(onClose)">
<VModal :on-close="() => withLoading(onClose)">
<template #content>
<div class="timeout-modal">
<div class="timeout-modal__header">
@ -32,7 +32,7 @@
font-size="13px"
is-white
class="timeout-modal__buttons__button"
:on-press="withLoading(onClose)"
:on-press="() => withLoading(onClose)"
:is-disabled="isLoading"
/>
<VButton
@ -42,7 +42,7 @@
border-radius="10px"
font-size="13px"
class="timeout-modal__buttons__button save"
:on-press="withLoading(save)"
:on-press="() => withLoading(save)"
:is-disabled="isLoading || !hasChanged"
/>
</div>
@ -54,11 +54,13 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useNotify } from '@/utils/hooks';
import { useNotify, useRouter } from '@/utils/hooks';
import { Duration } from '@/utils/time';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useUsersStore } from '@/store/modules/usersStore';
import { useAppStore } from '@/store/modules/appStore';
import { useLoading } from '@/composables/useLoading';
import { RouteConfig } from '@/router';
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
@ -69,8 +71,9 @@ import Icon from '@/../static/images/session/inactivityTimer.svg';
const appStore = useAppStore();
const usersStore = useUsersStore();
const notify = useNotify();
const router = useRouter();
const { isLoading, withLoading } = useLoading();
const isLoading = ref(false);
const sessionDuration = ref<Duration | null>(null);
/**
@ -128,18 +131,6 @@ async function save() {
function onClose(): void {
appStore.removeActiveModal();
}
/**
* Returns a function that disables modal interaction during execution.
*/
function withLoading(fn: () => Promise<void>): () => Promise<void> {
return async () => {
if (isLoading.value) return;
isLoading.value = true;
await fn();
isLoading.value = false;
};
}
</script>
<style scoped lang="scss">
@ -153,7 +144,7 @@ function withLoading(fn: () => Promise<void>): () => Promise<void> {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 20px;
margin: 20px 0;
@media screen and (max-width: 500px) {
flex-direction: column;
@ -161,6 +152,11 @@ function withLoading(fn: () => Promise<void>): () => Promise<void> {
gap: 10px;
}
&__icon {
height: 40px;
width: 40px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 28px;

View File

@ -3,15 +3,21 @@
<template>
<div class="selector">
<div v-click-outside="closeSelector" tabindex="0" class="selector__content" @keyup.enter="toggleSelector" @click="toggleSelector">
<div tabindex="0" class="selector__content" @keyup.enter="toggleSelector" @click.stop="toggleSelector">
<span v-if="selected" class="selector__content__label">{{ selected?.shortString }}</span>
<span v-else class="selector__content__label">Select duration</span>
<arrow-down-icon class="selector__content__arrow" :class="{ open: isOpen }" />
</div>
<div v-if="isOpen" class="selector__dropdown">
<div
v-if="isOpen"
v-click-outside="closeSelector"
tabindex="0"
class="selector__dropdown"
>
<div
v-for="(option, index) in options"
:key="index" tabindex="0"
:key="index"
tabindex="0"
class="selector__dropdown__item"
:class="{ selected: isSelected(option) }"
@click.stop="() => select(option)"

View File

@ -9,6 +9,14 @@
</div>
<div class="notification-wrap__content-area__message-area">
<p class="notification-wrap__content-area__message">{{ notification.message }}</p>
<p v-if="isTimeoutMentioned && notOnSettingsPage" class="notification-wrap__content-area__account-msg">
To change this go to your
<router-link :to="settingsRoute" class="notification-wrap__content-area__account-msg__link">
account settings
</router-link>
</p>
<a
v-if="isSupportLinkMentioned"
:href="requestURL"
@ -28,16 +36,20 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { DelayedNotification } from '@/types/DelayedNotification';
import { useNotificationsStore } from '@/store/modules/notificationsStore';
import { useConfigStore } from '@/store/modules/configStore';
import { RouteConfig } from '@/router';
import { useRouter } from '@/utils/hooks';
import CloseIcon from '@/../static/images/notifications/close.svg';
const configStore = useConfigStore();
const notificationsStore = useNotificationsStore();
const nativeRouter = useRouter();
const router = reactive(nativeRouter);
const props = withDefaults(defineProps<{
notification: DelayedNotification;
@ -47,6 +59,20 @@ const props = withDefaults(defineProps<{
const isClassActive = ref<boolean>(false);
/**
* Returns the correct settings route based on if we're on all projects dashboard.
*/
const settingsRoute = computed((): string => {
if (
router.currentRoute.path.includes(RouteConfig.AllProjectsDashboard.path)
|| router.currentRoute.path.includes(RouteConfig.AccountSettings.path)
) {
return RouteConfig.AccountSettings.with(RouteConfig.Settings2).path;
}
return RouteConfig.Account.with(RouteConfig.Settings).path;
});
/**
* Returns the URL for the general request page from the store.
*/
@ -54,6 +80,22 @@ const requestURL = computed((): string => {
return configStore.state.config.generalRequestURL;
});
/**
* Returns whether we are not on a settings page.
*/
const notOnSettingsPage = computed((): boolean => {
return router.currentRoute.name !== RouteConfig.Settings.name
&& router.currentRoute.name !== RouteConfig.Settings2.name;
});
/**
* Indicates if session timeout is mentioned in message.
* Temporal solution, can be changed later.
*/
const isTimeoutMentioned = computed((): boolean => {
return props.notification.message.toLowerCase().includes('session timeout');
});
/**
* Indicates if support word is mentioned in message.
* Temporal solution, can be changed later.
@ -139,6 +181,17 @@ onMounted((): void => {
cursor: pointer;
word-break: normal;
}
&__account-msg {
margin-top: 20px;
&__link {
display: block;
color: var(--c-black);
text-decoration: underline;
cursor: pointer;
}
}
}
&__buttons-group {

View File

@ -94,7 +94,7 @@ export abstract class RouteConfig {
public static BillingPaymentMethods2 = new NavigationLink('payment-methods', 'Payment Methods 2');
public static BillingHistory = new NavigationLink('billing-history', 'Billing History');
// this duplicates the path of BillingHistory so that they can be used interchangeably in BillingArea.vue
public static BillingHistory2 = new NavigationLink('billing-history2', 'Billing History 2');
public static BillingHistory2 = new NavigationLink('billing-history', 'Billing History 2');
public static BillingCoupons = new NavigationLink('coupons', 'Coupons');
public static BillingCoupons2 = new NavigationLink('coupons', 'Billing Coupons');

View File

@ -123,7 +123,7 @@
<!-- IMPORTANT! Make sure these 2 modals are positioned as the last elements here so that they are shown on top of everything else -->
<InactivityModal
v-if="inactivityModalShown"
:on-continue="refreshSession"
:on-continue="() => refreshSession(true)"
:on-logout="handleInactive"
:on-close="closeInactivityModal"
:initial-seconds="inactivityModalTime / 1000"
@ -570,8 +570,9 @@ function selectProject(fetchedProjects: Project[]): void {
/**
* Refreshes session and resets session timers.
* @param manual - whether the user manually refreshed session. i.e.: clicked "Stay Logged In".
*/
async function refreshSession(): Promise<void> {
async function refreshSession(manual = false): Promise<void> {
isSessionRefreshing.value = true;
try {
@ -588,6 +589,10 @@ async function refreshSession(): Promise<void> {
inactivityModalShown.value = false;
isSessionActive.value = false;
isSessionRefreshing.value = false;
if (manual && !usersStore.state.settings.sessionDuration) {
appStore.updateActiveModal(MODALS.editSessionTimeout);
}
}
/**

View File

@ -109,7 +109,7 @@
</div>
<InactivityModal
v-if="inactivityModalShown"
:on-continue="refreshSession"
:on-continue="() => refreshSession(true)"
:on-logout="handleInactive"
:on-close="closeInactivityModal"
:initial-seconds="inactivityModalTime / 1000"
@ -559,8 +559,9 @@ function restartSessionTimers(): void {
/**
* Refreshes session and resets session timers.
* @param manual - whether the user manually refreshed session. i.e.: clicked "Stay Logged In".
*/
async function refreshSession(): Promise<void> {
async function refreshSession(manual = false): Promise<void> {
isSessionRefreshing.value = true;
try {
@ -577,6 +578,10 @@ async function refreshSession(): Promise<void> {
inactivityModalShown.value = false;
isSessionActive.value = false;
isSessionRefreshing.value = false;
if (manual && !usersStore.state.settings.sessionDuration) {
appStore.updateActiveModal(MODALS.editSessionTimeout);
}
}
/**