From 5e319d4c2fa2a22f4736eff914afc196caadeb92 Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Wed, 22 May 2024 16:51:54 +0700 Subject: [PATCH] [Issue 3050] [feat] Extension - Add popup remind backup account --- .../extension-base/src/constants/storage.ts | 2 + .../src/koni/background/handlers/State.ts | 30 ++++- .../request-service/handler/PopupHandler.ts | 15 +++ .../src/Popup/RemindExportAccount.tsx | 123 ++++++++++++++++++ packages/extension-koni-ui/src/Popup/Root.tsx | 3 +- .../extension-koni-ui/src/Popup/router.tsx | 2 + .../extension-koni-ui/src/constants/common.ts | 1 + packages/extension-koni/src/background.ts | 6 + 8 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 packages/extension-koni-ui/src/Popup/RemindExportAccount.tsx diff --git a/packages/extension-base/src/constants/storage.ts b/packages/extension-base/src/constants/storage.ts index 4542daface8..8344c135706 100644 --- a/packages/extension-base/src/constants/storage.ts +++ b/packages/extension-base/src/constants/storage.ts @@ -4,3 +4,5 @@ export const LANGUAGE = 'current-language'; export const CURRENCY = 'current-currency'; + +export const REMIND_EXPORT_ACCOUNT = 'remind_export_account'; diff --git a/packages/extension-base/src/koni/background/handlers/State.ts b/packages/extension-base/src/koni/background/handlers/State.ts index 88ccefca99b..c2a581a3053 100644 --- a/packages/extension-base/src/koni/background/handlers/State.ts +++ b/packages/extension-base/src/koni/background/handlers/State.ts @@ -7,7 +7,7 @@ import { withErrorLog } from '@subwallet/extension-base/background/handlers/help import { isSubscriptionRunning, unsubscribe } from '@subwallet/extension-base/background/handlers/subscriptions'; import { AccountRefMap, AddTokenRequestExternal, AmountData, APIItemState, ApiMap, AuthRequestV2, BasicTxErrorType, ChainStakingMetadata, ChainType, ConfirmationsQueue, CrowdloanItem, CrowdloanJson, CurrencyType, CurrentAccountInfo, EvmProviderErrorType, EvmSendTransactionParams, EvmSendTransactionRequest, EvmSignatureRequest, ExternalRequestPromise, ExternalRequestPromiseStatus, ExtrinsicType, MantaAuthorizationContext, MantaPayConfig, MantaPaySyncState, NftCollection, NftItem, NftJson, NominatorMetadata, RequestAccountExportPrivateKey, RequestCheckPublicAndSecretKey, RequestConfirmationComplete, RequestCrowdloanContributions, RequestSettingsType, ResponseAccountExportPrivateKey, ResponseCheckPublicAndSecretKey, ServiceInfo, SingleModeJson, StakingItem, StakingJson, StakingRewardItem, StakingRewardJson, StakingType, UiSettings } from '@subwallet/extension-base/background/KoniTypes'; import { AccountJson, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning } from '@subwallet/extension-base/background/types'; -import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, MANTA_PAY_BALANCE_INTERVAL } from '@subwallet/extension-base/constants'; +import { ALL_ACCOUNT_KEY, ALL_GENESIS_HASH, MANTA_PAY_BALANCE_INTERVAL, REMIND_EXPORT_ACCOUNT } from '@subwallet/extension-base/constants'; import { BalanceService } from '@subwallet/extension-base/services/balance-service'; import { ServiceStatus } from '@subwallet/extension-base/services/base/types'; import BuyService from '@subwallet/extension-base/services/buy-service'; @@ -27,6 +27,7 @@ import MintCampaignService from '@subwallet/extension-base/services/mint-campaig import NotificationService from '@subwallet/extension-base/services/notification-service/NotificationService'; import { PriceService } from '@subwallet/extension-base/services/price-service'; import RequestService from '@subwallet/extension-base/services/request-service'; +import { openPopup } from '@subwallet/extension-base/services/request-service/handler/PopupHandler'; import { AuthUrls, MetaRequest, SignRequest } from '@subwallet/extension-base/services/request-service/types'; import SettingService from '@subwallet/extension-base/services/setting-service/SettingService'; import DatabaseService from '@subwallet/extension-base/services/storage-service/DatabaseService'; @@ -36,6 +37,7 @@ import { SwapService } from '@subwallet/extension-base/services/swap-service'; import TransactionService from '@subwallet/extension-base/services/transaction-service'; import { TransactionEventResponse } from '@subwallet/extension-base/services/transaction-service/types'; import WalletConnectService from '@subwallet/extension-base/services/wallet-connect-service'; +import { SWStorage } from '@subwallet/extension-base/storage'; import AccountRefStore from '@subwallet/extension-base/stores/AccountRef'; import { BalanceItem, BalanceMap, EvmFeeInfo } from '@subwallet/extension-base/types'; import { isAccountAll, stripUrl, TARGET_ENV, wait } from '@subwallet/extension-base/utils'; @@ -1638,6 +1640,32 @@ export default class KoniState { return await this.requestService.completeConfirmation(request); } + private onHandleRemindExportAccount () { + const remindStatus = SWStorage.instance.getItem(REMIND_EXPORT_ACCOUNT); + + if (!remindStatus || !remindStatus.includes('done')) { + const handleRemind = (account: CurrentAccountInfo) => { + if (account.address !== '') { + // Open remind tab + const url = `${chrome.runtime.getURL('index.html')}#/remind-export-account`; + + openPopup(url); + subscription.unsubscribe(); + } else { + setTimeout(() => { + subscription.unsubscribe(); + }, 3000); + } + }; + + const subscription = this.keyringService.currentAccountSubject.subscribe(handleRemind); + } + } + + public onCheckToRemindUser () { + this.onHandleRemindExportAccount(); + } + public onInstall () { // const singleModes = Object.values(_PREDEFINED_SINGLE_MODES); diff --git a/packages/extension-base/src/services/request-service/handler/PopupHandler.ts b/packages/extension-base/src/services/request-service/handler/PopupHandler.ts index 5481cde91dc..df65df46aa3 100644 --- a/packages/extension-base/src/services/request-service/handler/PopupHandler.ts +++ b/packages/extension-base/src/services/request-service/handler/PopupHandler.ts @@ -26,6 +26,21 @@ const NORMAL_WINDOW_OPTS: chrome.windows.CreateData = { url: NOTIFICATION_URL }; +export function openPopup (url: string) { + chrome.windows.getCurrent( + (win) => { + const popupOptions = { ...POPUP_WINDOW_OPTS, url }; + + if (win) { + popupOptions.left = (win.left || 0) + (win.width || 0) - (popupOptions.width || 0) - 20; + popupOptions.top = (win.top || 0) + 110; + } + + chrome.windows.create(popupOptions).catch(console.error); + } + ); +} + export default class PopupHandler { readonly #requestService: RequestService; #notification: BrowserConfirmationType = DEFAULT_NOTIFICATION_TYPE; diff --git a/packages/extension-koni-ui/src/Popup/RemindExportAccount.tsx b/packages/extension-koni-ui/src/Popup/RemindExportAccount.tsx new file mode 100644 index 00000000000..1ced81b9a0b --- /dev/null +++ b/packages/extension-koni-ui/src/Popup/RemindExportAccount.tsx @@ -0,0 +1,123 @@ +// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { Layout } from '@subwallet/extension-koni-ui/components'; +import { USER_GUIDE_URL } from '@subwallet/extension-koni-ui/constants'; +import { Theme, ThemeProps } from '@subwallet/extension-koni-ui/types'; +import { Icon, PageIcon } from '@subwallet/react-ui'; +import CN from 'classnames'; +import { ArrowCircleRight, Export, X, XCircle } from 'phosphor-react'; +import React, { useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled, { useTheme } from 'styled-components'; +import { useLocalStorage } from 'usehooks-ts'; + +type Props = ThemeProps; + +const SUB_DOMAIN_USER_GUIDE = 'account-management/export-and-backup-accounts#export-all-accounts'; +const keyStorage = 'remind_export_account'; + +const Component: React.FC = (props: Props) => { + const { className } = props; + const { token } = useTheme() as Theme; + const [, setStorage] = useLocalStorage(keyStorage, 'done'); + const learnMore = useCallback(() => { + window.open(`${USER_GUIDE_URL}/${SUB_DOMAIN_USER_GUIDE}`); + setTimeout(() => { + window.close(); + }, 300); + }, []); + + const dismiss = useCallback(() => { + window.close(); + }, []); + + useEffect(() => { + setStorage('done'); + }, [setStorage]); + + const { t } = useTranslation(); + + return ( + + }} + rightFooterButton={{ + children: t('Learn more'), + onClick: learnMore, + icon: + }} + showBackButton={false} + subHeaderLeft={( + + )} + title={t('Pay attention')} + > +
+
+ +
+
+ {t('Back up your accounts!')} +
+
+ {t('If you lose your seed phrases/private keys/JSON backup files/QR backup codes, your accounts can\'t be recovered and your assets are lost. Learn how to back up your accounts to secure your assets now.')} +
+
+
+ ); +}; + +const RemindExportAccount = styled(Component)(({ theme: { token } }: Props) => { + return { + textAlign: 'center', + + '.page-icon': { + display: 'flex', + justifyContent: 'center', + marginTop: token.controlHeightLG, + marginBottom: token.margin, + '--page-icon-color': token.colorSecondary + }, + + '.title': { + marginTop: token.margin, + marginBottom: token.margin, + fontWeight: token.fontWeightStrong, + fontSize: token.fontSizeHeading3, + lineHeight: token.lineHeightHeading3, + color: token.colorTextBase + }, + + '.description': { + padding: `0 ${token.controlHeightLG - token.padding}px`, + marginTop: token.margin, + marginBottom: token.margin * 2, + fontSize: token.fontSizeHeading5, + lineHeight: token.lineHeightHeading5, + color: token.colorTextDescription, + textAlign: 'center' + } + }; +}); + +export default RemindExportAccount; diff --git a/packages/extension-koni-ui/src/Popup/Root.tsx b/packages/extension-koni-ui/src/Popup/Root.tsx index d4d251e3f37..b58cab4d4de 100644 --- a/packages/extension-koni-ui/src/Popup/Root.tsx +++ b/packages/extension-koni-ui/src/Popup/Root.tsx @@ -36,6 +36,7 @@ const welcomeUrl = '/welcome'; const tokenUrl = '/home/tokens'; const loginUrl = '/keyring/login'; const phishingUrl = '/phishing-page-detected'; +const remindExportAccountUrl = '/remind-export-account'; const createPasswordUrl = '/keyring/create-password'; const migratePasswordUrl = '/keyring/migrate-password'; const accountNewSeedPhrase = '/accounts/new-seed-phrase'; @@ -167,7 +168,7 @@ function DefaultRoute ({ children }: { children: React.ReactNode }): React.React // Do nothing } else if (needMigrate && hasMasterPassword && !needUnlock) { redirectTarget = migratePasswordUrl; - } else if (hasMasterPassword && needUnlock) { + } else if (hasMasterPassword && needUnlock && pathName !== remindExportAccountUrl) { redirectTarget = loginUrl; } else if (hasMasterPassword && pathName === createPasswordUrl) { redirectTarget = DEFAULT_ROUTER_PATH; diff --git a/packages/extension-koni-ui/src/Popup/router.tsx b/packages/extension-koni-ui/src/Popup/router.tsx index a617fb0ad94..caba54e854d 100644 --- a/packages/extension-koni-ui/src/Popup/router.tsx +++ b/packages/extension-koni-ui/src/Popup/router.tsx @@ -53,6 +53,7 @@ export class LazyLoader { const PhishingDetected = new LazyLoader('PhishingDetected', () => import('@subwallet/extension-koni-ui/Popup/PhishingDetected')); const Welcome = new LazyLoader('Welcome', () => import('@subwallet/extension-koni-ui/Popup/Welcome')); const CreateDone = new LazyLoader('CreateDone', () => import('@subwallet/extension-koni-ui/Popup/CreateDone')); +const RemindExportAccount = new LazyLoader('RemindExportAccount', () => import('@subwallet/extension-koni-ui/Popup/RemindExportAccount')); const BuyTokens = new LazyLoader('BuyTokens', () => import('@subwallet/extension-koni-ui/Popup/BuyTokens')); // const Staking = new LazyLoader('Staking', () => import('@subwallet/extension-koni-ui/Popup/Home/Staking')); @@ -149,6 +150,7 @@ export const router = createHashRouter([ Welcome.generateRouterObject('/welcome'), BuyTokens.generateRouterObject('/buy-tokens'), CreateDone.generateRouterObject('/create-done'), + RemindExportAccount.generateRouterObject('/remind-export-account'), { ...Home.generateRouterObject('/home'), children: [ diff --git a/packages/extension-koni-ui/src/constants/common.ts b/packages/extension-koni-ui/src/constants/common.ts index 015e8c6aac7..5c66d0e6ba5 100644 --- a/packages/extension-koni-ui/src/constants/common.ts +++ b/packages/extension-koni-ui/src/constants/common.ts @@ -7,6 +7,7 @@ export const EXTENSION_VERSION = chrome.runtime.getManifest().version; export const WIKI_URL = 'https://docs.subwallet.app/'; export const PRIVACY_AND_POLICY_URL = 'https://docs.subwallet.app/privacy-and-security/privacy-policy'; export const TERMS_OF_SERVICE_URL = 'https://docs.subwallet.app/privacy-and-security/terms-of-service'; +export const USER_GUIDE_URL = 'https://docs.subwallet.app/main/extension-user-guide'; export const WEBSITE_URL = 'https://subwallet.app/'; export const TELEGRAM_URL = 'https://t.me/subwallet'; export const TWITTER_URL = 'https://twitter.com/subwalletapp'; diff --git a/packages/extension-koni/src/background.ts b/packages/extension-koni/src/background.ts index 5ecddca51e5..a8e0638e27e 100644 --- a/packages/extension-koni/src/background.ts +++ b/packages/extension-koni/src/background.ts @@ -36,6 +36,12 @@ function handleExtensionIdling () { // handle extension being idle since the ini }, IDLE_TIME); } +function handleRemindUserToExportAccount () { + koniState.onCheckToRemindUser(); +} + +handleRemindUserToExportAccount(); + // listen to all messages and handle appropriately chrome.runtime.onConnect.addListener((port): void => { // shouldn't happen, however... only listen to what we know about