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:
parent
291e639ac2
commit
fb1a0cc784
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
120
web/satellite/src/components/modals/SkipPassphraseModal.vue
Normal file
120
web/satellite/src/components/modals/SkipPassphraseModal.vue
Normal 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>
|
@ -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; });
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user