web/satellite: redesign and allow enter passphrase skip

This change allows users to skip the enter passphrase modal and opt in
for that choice to be remembered. The enter passphrase modal is also
redesigned.

Issue: https://github.com/storj/storj/issues/5818
Issue: https://github.com/storj/storj/issues/5616

Change-Id: Ie1cf6602682af3e9625656455fa3c096a24b95d3
This commit is contained in:
Wilfred Asomani 2023-04-27 17:31:37 +00:00 committed by Wilfred Asomani
parent 291e639ac2
commit fb1a0cc784
13 changed files with 200 additions and 39 deletions

View File

@ -264,6 +264,7 @@ export class AuthHttpApi implements UsersApi {
responseData.sessionDuration,
responseData.onboardingStart,
responseData.onboardingEnd,
responseData.passphrasePrompt,
responseData.onboardingStep,
);
}
@ -288,6 +289,7 @@ export class AuthHttpApi implements UsersApi {
responseData.sessionDuration,
responseData.onboardingStart,
responseData.onboardingEnd,
responseData.passphrasePrompt,
responseData.onboardingStep,
);
}

View File

@ -151,7 +151,9 @@ async function onCreateProjectClick(): Promise<void> {
closeModal();
bucketsStore.clearS3Data();
appStore.updateActiveModal(MODALS.createProjectPassphrase);
if (usersStore.state.settings.passphrasePrompt) {
appStore.updateActiveModal(MODALS.createProjectPassphrase);
}
analytics.pageVisit(RouteConfig.ProjectDashboard.path);
await router.push(RouteConfig.ProjectDashboard.path);

View File

@ -2,17 +2,20 @@
// See LICENSE for copying information.
<template>
<VModal :on-close="() => closeModal(true)">
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<EnterPassphraseIcon />
<h1 class="modal__title">Enter your encryption passphrase</h1>
<div class="modal__header">
<AccessEncryptionIcon />
<h1 class="modal__header__title">Enter passphrase</h1>
</div>
<p class="modal__info">
To open a project and view your encrypted files, <br>please enter your encryption passphrase.
Enter your encryption passphrase to view and manage your data in the browser. This passphrase will
be used to unlock all buckets in this project.
</p>
<VInput
label="Encryption Passphrase"
placeholder="Enter your passphrase"
placeholder="Enter a passphrase here"
:error="enterError"
role-description="passphrase"
is-password
@ -20,17 +23,20 @@
/>
<div class="modal__buttons">
<VButton
label="Enter without passphrase"
label="Skip"
height="48px"
font-size="14px"
border-radius="10px"
:is-transparent="true"
:on-press="() => closeModal()"
:on-press="skipPassphrase"
/>
<VButton
label="Continue ->"
height="48px"
font-size="14px"
border-radius="10px"
:on-press="onContinue"
:is-disabled="!passphrase"
/>
</div>
</div>
@ -41,19 +47,20 @@
<script setup lang="ts">
import { reactive, ref } from 'vue';
import AccessEncryptionIcon from '../../../static/images/accessGrants/newCreateFlow/accessEncryption.svg';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { RouteConfig } from '@/router';
import { useRouter } from '@/utils/hooks';
import { useAppStore } from '@/store/modules/appStore';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import { MODALS } from '@/utils/constants/appStatePopUps';
import VModal from '@/components/common/VModal.vue';
import VInput from '@/components/common/VInput.vue';
import VButton from '@/components/common/VButton.vue';
import EnterPassphraseIcon from '@/../static/images/buckets/openBucket.svg';
const bucketsStore = useBucketsStore();
const appStore = useAppStore();
const nativeRouter = useRouter();
@ -82,14 +89,16 @@ function onContinue(): void {
}
/**
* Closes enter passphrase modal and navigates to single project dashboard from
* all projects dashboard.
* Opens the SkipPassphrase modal for confirmation.
*/
function closeModal(isCloseButton = false): void {
if (!isCloseButton && router.currentRoute.name === RouteConfig.AllProjectsDashboard.name) {
router.push(RouteConfig.ProjectDashboard.path);
}
function skipPassphrase(): void {
appStore.updateActiveModal(MODALS.skipPassphrase);
}
/**
* Closes enter passphrase modal.
*/
function closeModal(): void {
appStore.removeActiveModal();
}
@ -108,28 +117,34 @@ function setPassphrase(value: string): void {
font-family: 'font_regular', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding: 62px 62px 54px;
max-width: 500px;
padding: 32px;
max-width: 350px;
@media screen and (max-width: 600px) {
padding: 62px 24px 54px;
}
&__header {
display: flex;
align-items: center;
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 26px;
line-height: 31px;
color: #131621;
margin: 30px 0 15px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
color: var(--c-grey-8);
margin-left: 16px;
text-align: left;
}
}
&__info {
font-size: 16px;
line-height: 21px;
text-align: center;
font-size: 14px;
line-height: 19px;
color: #354049;
margin-bottom: 32px;
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
text-align: left;
}
&__buttons {

View File

@ -182,7 +182,12 @@ async function onCreateProjectClick(): Promise<void> {
return;
}
appStore.updateActiveModal(MODALS.enterPassphrase);
analytics.pageVisit(RouteConfig.ProjectDashboard.path);
router.push(RouteConfig.ProjectDashboard.path);
if (usersStore.state.settings.passphrasePrompt) {
appStore.updateActiveModal(MODALS.enterPassphrase);
}
}
/**

View File

@ -0,0 +1,120 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<div class="modal__header">
<AccessEncryptionIcon />
<h1 class="modal__header__title">Skip passphrase</h1>
</div>
<p class="modal__info">
Do you want to remember this choice and always skip the passphrase when opening a project?
</p>
<div class="modal__buttons">
<VButton
label="No"
height="48px"
font-size="14px"
border-radius="10px"
:is-transparent="true"
:on-press="closeModal"
/>
<VButton
label="Yes"
height="48px"
font-size="14px"
border-radius="10px"
:on-press="rememberSkip"
/>
</div>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import AccessEncryptionIcon from '../../../static/images/accessGrants/newCreateFlow/accessEncryption.svg';
import { useAppStore } from '@/store/modules/appStore';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useUsersStore } from '@/store/modules/usersStore';
import { useNotify } from '@/utils/hooks';
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
const appStore = useAppStore();
const usersStore = useUsersStore();
const notify = useNotify();
/**
* Remembers to skip passphrase entry next time.
*/
async function rememberSkip() {
try {
await usersStore.updateSettings({ passphrasePrompt: false });
appStore.removeActiveModal();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.SKIP_PASSPHRASE_MODAL);
}
}
/**
* Closes modal.
*/
function closeModal(): void {
appStore.removeActiveModal();
}
</script>
<style scoped lang="scss">
.modal {
font-family: 'font_regular', sans-serif;
display: flex;
flex-direction: column;
padding: 32px;
max-width: 350px;
&__header {
display: flex;
align-items: center;
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
color: var(--c-grey-8);
margin-left: 16px;
text-align: left;
}
}
&__info {
font-size: 14px;
line-height: 19px;
color: #354049;
padding-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
text-align: left;
}
&__buttons {
display: flex;
column-gap: 20px;
margin-top: 31px;
width: 100%;
@media screen and (max-width: 500px) {
flex-direction: column-reverse;
column-gap: unset;
row-gap: 20px;
}
}
}
</style>

View File

@ -196,7 +196,9 @@ async function onProjectSelected(projectID: string): Promise<void> {
closeDropdown();
bucketsStore.clearS3Data();
appStore.updateActiveModal(MODALS.enterPassphrase);
if (userStore.state.settings.passphrasePrompt) {
appStore.updateActiveModal(MODALS.enterPassphrase);
}
if (isBucketsView.value) {
await router.push(RouteConfig.Buckets.path).catch(() => {return; });

View File

@ -413,12 +413,16 @@ onMounted(async (): Promise<void> => {
await projectsStore.getProjectLimits(projectID);
if (hasJustLoggedIn.value) {
if (limits.value.objectCount > 0) {
appStore.updateActiveModal(MODALS.enterPassphrase);
if (usersStore.state.settings.passphrasePrompt) {
appStore.updateActiveModal(MODALS.enterPassphrase);
}
if (!bucketWasCreated.value) {
LocalData.setBucketWasCreatedStatus();
}
} else {
appStore.updateActiveModal(MODALS.createProjectPassphrase);
if (usersStore.state.settings.passphrasePrompt) {
appStore.updateActiveModal(MODALS.createProjectPassphrase);
}
}
appStore.toggleHasJustLoggedIn();

View File

@ -163,6 +163,7 @@ export class UserSettings {
private _sessionDuration: number | null = null,
public onboardingStart = false,
public onboardingEnd = false,
public passphrasePrompt = true,
public onboardingStep: string | null = null,
) {}
@ -177,6 +178,7 @@ export class UserSettings {
export interface SetUserSettingsData {
onboardingStart?: boolean
onboardingEnd?: boolean;
passphrasePrompt?: boolean;
onboardingStep?: string | null;
sessionDuration?: number;
}

View File

@ -114,4 +114,5 @@ export enum AnalyticsErrorEventSource {
ONBOARDING_OVERVIEW_STEP = 'Onboarding Overview step error',
PRICING_PLAN_STEP = 'Onboarding Pricing Plan step error',
EDIT_TIMEOUT_MODAL = 'Edit session timeout error',
SKIP_PASSPHRASE_MODAL = 'Remember skip passphrase error',
}

View File

@ -31,6 +31,7 @@ import NewCreateProjectModal from '@/components/modals/NewCreateProjectModal.vue
import EditSessionTimeoutModal from '@/components/modals/EditSessionTimeoutModal.vue';
import UpgradeAccountModal from '@/components/modals/upgradeAccountFlow/UpgradeAccountModal.vue';
import DeleteAccessGrantModal from '@/components/modals/DeleteAccessGrantModal.vue';
import SkipPassphraseModal from '@/components/modals/SkipPassphraseModal.vue';
export const APP_STATE_DROPDOWNS = {
ACCOUNT: 'isAccountDropdownShown',
@ -78,6 +79,7 @@ enum Modals {
EDIT_SESSION_TIMEOUT = 'editSessionTimeout',
UPGRADE_ACCOUNT = 'upgradeAccount',
DELETE_ACCESS_GRANT = 'deleteAccessGrant',
SKIP_PASSPHRASE = 'skipPassphrase',
}
// modals could be of VueConstructor type or Object (for composition api components).
@ -109,4 +111,5 @@ export const MODALS: Record<Modals, unknown> = {
[Modals.EDIT_SESSION_TIMEOUT]: EditSessionTimeoutModal,
[Modals.UPGRADE_ACCOUNT]: UpgradeAccountModal,
[Modals.DELETE_ACCESS_GRANT]: DeleteAccessGrantModal,
[Modals.SKIP_PASSPHRASE]: SkipPassphraseModal,
};

View File

@ -720,7 +720,7 @@ onMounted(async () => {
usersStore.$onAction(({ name, after, args }) => {
if (name === 'clear') clearSessionTimers();
else if (name === 'updateSettings') {
if (args[0].sessionDuration !== usersStore.state.settings.sessionDuration?.nanoseconds) {
if (args[0].sessionDuration && args[0].sessionDuration !== usersStore.state.settings.sessionDuration?.nanoseconds) {
after((_) => refreshSession());
}
}

View File

@ -605,7 +605,7 @@ onMounted(async () => {
usersStore.$onAction(({ name, after, args }) => {
if (name === 'clear') clearSessionTimers();
else if (name === 'updateSettings') {
if (args[0].sessionDuration !== usersStore.state.settings.sessionDuration?.nanoseconds) {
if (args[0].sessionDuration && args[0].sessionDuration !== usersStore.state.settings.sessionDuration?.nanoseconds) {
after((_) => refreshSession());
}
}

View File

@ -125,7 +125,12 @@ async function onOpenClicked(): Promise<void> {
return;
}
await analytics.eventTriggered(AnalyticsEvent.NAVIGATE_PROJECTS);
appStore.updateActiveModal(MODALS.enterPassphrase);
if (usersStore.state.settings.passphrasePrompt) {
appStore.updateActiveModal(MODALS.enterPassphrase);
}
analytics.pageVisit(RouteConfig.ProjectDashboard.path);
router.push(RouteConfig.ProjectDashboard.path);
}
async function selectProject() {