Skip to content

Commit

Permalink
Merge pull request #1284 from AmbireTech/enhancement/dismiss-password…
Browse files Browse the repository at this point in the history
…-recovery-banner

UX/ Allow the user to dismiss the password recovery banner
  • Loading branch information
PetromirDev authored Feb 5, 2025
2 parents ad33c41 + 1729a78 commit 169c666
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 15 deletions.
33 changes: 28 additions & 5 deletions src/controllers/emailVault/emailVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const RECOVERY_SECRET_ID = 'EmailVaultRecoverySecret'
const EMAIL_VAULT_STORAGE_KEY = 'emailVault'
const MAGIC_LINK_STORAGE_KEY = 'magicLinkKeys'
const SESSION_KEYS_STORAGE_KEY = 'sessionKeys'
const SETUP_BANNER_DISMISSED_AT_STORAGE_KEY = 'emailVaultSetupBannerDismissedAt'

export type MagicLinkKeys = {
[email: string]: MagicLinkKey
Expand All @@ -61,9 +62,9 @@ const STATUS_WRAPPED_METHODS = {
/**
* EmailVaultController
* @class
* The purpouse of this controller is to provide easy interface to the EmailVault, keystore and magic link libraries
* The purpose of this controller is to provide easy interface to the EmailVault, keystore and magic link libraries
* The most important thing it achieves is handling magicLink and session keys with polling.
* Emits the porper states e.g. loading, ready, awaiting email magicLink confirmation etc.
* Emits the proper states e.g. loading, ready, awaiting email magicLink confirmation etc.
* Extended documentation about the EV and its internal mechanisms
* https://github.com/AmbireTech/ambire-common/wiki/Email-Vault-Documentation
*/
Expand Down Expand Up @@ -104,6 +105,8 @@ export class EmailVaultController extends EventEmitter {
email: {}
}

#setupBannerDismissedAt: number = 0

statuses: Statuses<keyof typeof STATUS_WRAPPED_METHODS> = STATUS_WRAPPED_METHODS

constructor(
Expand All @@ -130,15 +133,17 @@ export class EmailVaultController extends EventEmitter {
// and then we call it's methods
await wait(1)
this.emitUpdate()
const [emailVaultState, magicLinkKey] = await Promise.all([
const [emailVaultState, magicLinkKey, dismissedAt] = await Promise.all([
this.storage.get(EMAIL_VAULT_STORAGE_KEY, {
email: {}
}),
this.storage.get(MAGIC_LINK_STORAGE_KEY, {})
this.storage.get(MAGIC_LINK_STORAGE_KEY, {}),
this.storage.get(SETUP_BANNER_DISMISSED_AT_STORAGE_KEY, this.#setupBannerDismissedAt)
])

this.emailVaultStates = emailVaultState
this.#magicLinkKeys = this.#parseMagicLinkKeys(magicLinkKey)
this.#setupBannerDismissedAt = dismissedAt

this.lastUpdate = new Date()
this.isReady = true
Expand Down Expand Up @@ -587,6 +592,14 @@ export class EmailVaultController extends EventEmitter {
this.emitUpdate()
}

dismissBanner() {
this.#setupBannerDismissedAt = Date.now()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.storage.set(SETUP_BANNER_DISMISSED_AT_STORAGE_KEY, this.#setupBannerDismissedAt)

this.emitUpdate()
}

get keystoreRecoveryEmail(): string | undefined {
const keyStoreUid = this.#keyStore.keyStoreUid
const EVEmails = Object.keys(this.emailVaultStates.email)
Expand Down Expand Up @@ -618,15 +631,25 @@ export class EmailVaultController extends EventEmitter {
get banners(): Banner[] {
const banners: Banner[] = []

const now = Date.now()
const ONE_WEEK = 1000 * 60 * 60 * 24 * 7

const isDismissed =
this.#setupBannerDismissedAt > 0 && now - this.#setupBannerDismissedAt < ONE_WEEK

// Show the banner if the keystore is already configured and the `password` secret is already set (for HW and ViewOnly accounts the app can run without keystore)
// and if the keystore secret backup is not enabled already
if (this.#keyStore.hasPasswordSecret && !this.hasKeystoreRecovery) {
if (this.#keyStore.hasPasswordSecret && !this.hasKeystoreRecovery && !isDismissed) {
banners.push({
id: 'keystore-secret-backup',
type: 'info',
title: 'Enable device password reset via email',
text: "Email Vault recovers your device password. It is securely stored in Ambire's infrastructure cloud.",
actions: [
{
label: 'Dismiss',
actionName: 'dismiss-email-vault'
},
{
label: 'Enable',
actionName: 'backup-keystore-secret'
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/banner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,7 @@ export type Action =
label: 'Retry'
actionName: 'reload-selected-account'
}
| {
label: 'Dismiss'
actionName: 'dismiss-email-vault'
}
10 changes: 0 additions & 10 deletions src/libs/banners/banners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,7 @@ import { AccountOpAction, Action as ActionFromActionsQueue } from '../../interfa
// eslint-disable-next-line import/no-cycle
import { Action, Banner } from '../../interfaces/banner'
import { Network } from '../../interfaces/network'
import { RPCProviders } from '../../interfaces/provider'
import { SelectedAccountPortfolioState } from '../../interfaces/selectedAccount'
import { ActiveRoute } from '../../interfaces/swapAndBridge'
import {
AccountState as DefiPositionsAccountState,
DeFiPositionsError,
NetworksWithPositions
} from '../defiPositions/types'
import { getNetworksWithFailedRPC } from '../networks/networks'
import { AccountAssetsState } from '../portfolio/interfaces'
import { PORTFOLIO_LIB_ERROR_NAMES } from '../portfolio/portfolio'
import { getIsBridgeTxn, getQuoteRouteSteps } from '../swapAndBridge/swapAndBridge'

const getBridgeBannerTitle = (routeStatus: ActiveRoute['routeStatus']) => {
Expand Down

0 comments on commit 169c666

Please sign in to comment.