Skip to content

Commit

Permalink
[Issue 3050] [feat] Extension - Add popup remind backup account
Browse files Browse the repository at this point in the history
  • Loading branch information
Thiendekaco committed May 22, 2024
1 parent c17a72a commit 5e319d4
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/extension-base/src/constants/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
export const LANGUAGE = 'current-language';

export const CURRENCY = 'current-currency';

export const REMIND_EXPORT_ACCOUNT = 'remind_export_account';
30 changes: 29 additions & 1 deletion packages/extension-base/src/koni/background/handlers/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
123 changes: 123 additions & 0 deletions packages/extension-koni-ui/src/Popup/RemindExportAccount.tsx
Original file line number Diff line number Diff line change
@@ -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: 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 (
<Layout.WithSubHeaderOnly
leftFooterButton={{
children: t('Dismiss'),
onClick: dismiss,
schema: 'secondary',
icon: <Icon
phosphorIcon={XCircle}
weight={'fill'}
/>
}}
rightFooterButton={{
children: t('Learn more'),
onClick: learnMore,
icon: <Icon
phosphorIcon={ArrowCircleRight}
weight={'fill'}
/>
}}
showBackButton={false}
subHeaderLeft={(
<Icon
phosphorIcon={X}
size='md'
/>
)}
title={t('Pay attention')}
>
<div className={CN(className)}>
<div className='page-icon'>
<PageIcon
color={token.colorWarning}
iconProps={{
weight: 'fill',
phosphorIcon: Export
}}
/>
</div>
<div className='title'>
{t('Back up your accounts!')}
</div>
<div className='description'>
{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.')}
</div>
</div>
</Layout.WithSubHeaderOnly>
);
};

const RemindExportAccount = styled(Component)<Props>(({ 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;
3 changes: 2 additions & 1 deletion packages/extension-koni-ui/src/Popup/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions packages/extension-koni-ui/src/Popup/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'));

Expand Down Expand Up @@ -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: [
Expand Down
1 change: 1 addition & 0 deletions packages/extension-koni-ui/src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
6 changes: 6 additions & 0 deletions packages/extension-koni/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 5e319d4

Please sign in to comment.