Skip to content

Commit

Permalink
feat: improve copy and paste mnemonic
Browse files Browse the repository at this point in the history
  • Loading branch information
greatertomi committed Apr 25, 2024
1 parent 7cae9ba commit fc6e7ac
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 35 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ export const postHogOnboardingActions: PostHogOnboardingActionsType = {
RECOVERY_PHRASE_INTRO_WATCH_VIDEO_CLICK: PostHogAction.OnboardingCreateSaveRecoveryPhraseIntroPlayVideoClick,
RECOVERY_PHRASE_INTRO_VIDEO_GOTIT_CLICK: PostHogAction.OnboardingCreateKeepWalletSecureGotItClick,
RECOVERY_PHRASE_COPY_TO_CLIPBOARD_CLICK: PostHogAction.OnboardingCreateSaveRecoveryPhraseCopyToClipboardClick,
RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK: PostHogAction.OnboardingCreateEnterRecoveryPhrasePasteFromClipboardClick
RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK:
PostHogAction.OnboardingCreateEnterRecoveryPhrasePasteFromClipboardClick,
RECOVERY_PHRASE_COPY_READ_MORE_CLICK: PostHogAction.OnboardingCreateSaveRecoveryPhraseCopyReadMoreClick,
RECOVERY_PHRASE_PASTE_READ_MORE_CLICK: PostHogAction.OnboardingCreateEnterRecoveryPhrasePasteReadMoreClick
},
restore: {
SETUP_OPTION_CLICK: PostHogAction.OnboardingRestoreClick,
ENTER_WALLET: PostHogAction.OnboardingRestoreEnterWalletClick,
ENTER_RECOVERY_PHRASE_NEXT_CLICK: PostHogAction.OnboardingRestoreEnterRecoveryPhraseNextClick,
RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK:
PostHogAction.OnboardingRestoreEnterRecoveryPhrasePasteFromClipboardClick
PostHogAction.OnboardingRestoreEnterRecoveryPhrasePasteFromClipboardClick,
RECOVERY_PHRASE_PASTE_READ_MORE_CLICK: PostHogAction.OnboardingRestoreEnterRecoveryPhrasePasteReadMoreClick
},
hw: {
SETUP_OPTION_CLICK: PostHogAction.OnboardingHWClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ export type PostHogActionsKeys =
| 'RECOVERY_PHRASE_INTRO_VIDEO_GOTIT_CLICK'
| 'RECOVERY_PHRASE_COPY_TO_CLIPBOARD_CLICK'
| 'RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK'
| 'RECOVERY_PASSPHRASE_VERIFICATION_NEXT_CLICK';
| 'RECOVERY_PASSPHRASE_VERIFICATION_NEXT_CLICK'
| 'RECOVERY_PHRASE_COPY_READ_MORE_CLICK'
| 'RECOVERY_PHRASE_PASTE_READ_MORE_CLICK';
export type PostHogOnboardingActionsValueType = Partial<Record<PostHogActionsKeys, PostHogAction>>;
export type PostHogOnboardingActionsType = Partial<Record<OnboardingFlows, PostHogOnboardingActionsValueType>>;
export type PostHogPersonProperties = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import { SendOnboardingAnalyticsEvent, SetupType } from '../types';
import { isScriptAddress } from '@cardano-sdk/wallet';
import { filter, firstValueFrom } from 'rxjs';
import { getWalletFromStorage } from '@src/utils/get-wallet-from-storage';
import { toast } from '@lace/common';
import { TOAST_DEFAULT_DURATION } from '@hooks/useActionExecution';
import Copy from '@src/assets/icons/copy.component.svg';
import Paste from '@src/assets/icons/paste.component.svg';

const WalletSetupModeStep = React.lazy(() =>
import('@lace/core').then((module) => ({ default: module.WalletSetupModeStep }))
Expand Down Expand Up @@ -57,6 +61,7 @@ export interface WalletSetupWizardProps {
}

const DEFAULT_MNEMONIC_LENGTH = 24;
const COPY_PASTE_TOOLTIP_URL = `${process.env.FAQ_URL}?question=what-is-my-recovery-phrase`;

export const WalletSetupWizard = ({
onCancel,
Expand All @@ -71,6 +76,7 @@ export const WalletSetupWizard = ({
const [resetMnemonicStage, setResetMnemonicStage] = useState<MnemonicStage | ''>('');
const [isResetMnemonicModalOpen, setIsResetMnemonicModalOpen] = useState(false);
const [isBackFromNextStep, setIsBackFromNextStep] = useState(false);
const [currentMnemonicStage, setCurrentMnemonicStage] = useState<MnemonicStage>('writedown');
const walletName = getWalletFromStorage()?.name;
const { createWallet } = useWalletManager();
const analytics = useAnalyticsContext();
Expand All @@ -94,6 +100,16 @@ export const WalletSetupWizard = ({
);
}, [mnemonicLength, setupType]);

const handleReadMoreOnClick = () => {
if (setupType === SetupType.RESTORE) {
sendAnalytics(postHogOnboardingActions.restore.RECOVERY_PHRASE_PASTE_READ_MORE_CLICK);
return;
}
currentMnemonicStage === 'writedown'
? sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_COPY_READ_MORE_CLICK)
: sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_PASTE_READ_MORE_CLICK);
};

const walletSetupMnemonicStepTranslations = {
writePassphraseTitle: t('core.walletSetupMnemonicStepRevamp.writePassphraseTitle'),
enterPassphrase: t('core.walletSetupMnemonicStepRevamp.enterPassphrase'),
Expand All @@ -103,7 +119,15 @@ export const WalletSetupWizard = ({
passphraseError: t('core.walletSetupMnemonicStepRevamp.passphraseError'),
enterPassphraseLength: t('core.walletSetupMnemonicStepRevamp.enterPassphraseLength'),
copyToClipboard: t('core.walletSetupMnemonicStepRevamp.copyToClipboard'),
pasteFromClipboard: t('core.walletSetupMnemonicStepRevamp.pasteFromClipboard')
pasteFromClipboard: t('core.walletSetupMnemonicStepRevamp.pasteFromClipboard'),
copyPasteTooltipText: (
<>
{t('core.walletSetupMnemonicStepRevamp.copyPasteTooltipText1')}{' '}
<a href={COPY_PASTE_TOOLTIP_URL} target="_blank" rel="noopener noreferrer" onClick={handleReadMoreOnClick}>
{t('core.walletSetupMnemonicStepRevamp.copyPasteTooltipText2')}
</a>
</>
)
};

const walletSetupModeStepTranslations = {
Expand Down Expand Up @@ -229,9 +253,14 @@ export const WalletSetupWizard = ({
suggestionList={wordList}
defaultMnemonicLength={DEFAULT_MNEMONIC_LENGTH}
onSetMnemonicLength={(value: number) => setMnemonicLength(value)}
onPasteFromClipboard={() =>
sendAnalytics(postHogOnboardingActions[setupType]?.RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK)
}
onPasteFromClipboard={() => {
sendAnalytics(postHogOnboardingActions[setupType]?.RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK);
toast.notify({
duration: TOAST_DEFAULT_DURATION,
text: t('core.walletSetupMnemonicStepRevamp.recoveryPhrasePasted'),
icon: Paste
});
}}
/>
);
}
Expand All @@ -241,8 +270,13 @@ export const WalletSetupWizard = ({
mnemonic={mnemonic}
onReset={(resetStage) => {
setResetMnemonicStage(resetStage);
resetStage === 'input' ? setIsResetMnemonicModalOpen(true) : onCancel();
resetStage === 'input' && setIsBackFromNextStep(false);
if (resetStage === 'input') {
setIsResetMnemonicModalOpen(true);
setIsBackFromNextStep(false);
setCurrentMnemonicStage('writedown');
} else {
onCancel();
}
}}
renderVideoPopupContent={({ onClose }) => (
<MnemonicVideoPopupContent
Expand All @@ -256,6 +290,7 @@ export const WalletSetupWizard = ({
)}
onNext={moveForward}
onStepNext={(mnemonicStage) => {
mnemonicStage === 'writedown' && setCurrentMnemonicStage('input');
mnemonicStage === 'writedown'
? sendAnalytics(postHogOnboardingActions.create.SAVE_RECOVERY_PHRASE_NEXT_CLICK)
: sendAnalytics(postHogOnboardingActions.create.ENTER_RECOVERY_PHRASE_NEXT_CLICK);
Expand All @@ -264,10 +299,22 @@ export const WalletSetupWizard = ({
suggestionList={wordList}
passphraseInfoLink={`${process.env.FAQ_URL}?question=what-happens-if-i-lose-my-recovery-phrase`}
onWatchVideoClick={() => sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_INTRO_WATCH_VIDEO_CLICK)}
onCopyToClipboard={() => sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_COPY_TO_CLIPBOARD_CLICK)}
onPasteFromClipboard={() =>
sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK)
}
onCopyToClipboard={() => {
sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_COPY_TO_CLIPBOARD_CLICK);
toast.notify({
duration: TOAST_DEFAULT_DURATION,
text: t('core.walletSetupMnemonicStepRevamp.recoveryPhraseCopied'),
icon: Copy
});
}}
onPasteFromClipboard={() => {
sendAnalytics(postHogOnboardingActions.create.RECOVERY_PHRASE_PASTE_FROM_CLIPBOARD_CLICK);
toast.notify({
duration: TOAST_DEFAULT_DURATION,
text: t('core.walletSetupMnemonicStepRevamp.recoveryPhrasePasted'),
icon: Paste
});
}}
isBackFromNextStep={isBackFromNextStep}
/>
);
Expand Down
3 changes: 3 additions & 0 deletions packages/common/src/analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export enum PostHogAction {
OnboardingRestoreClick = 'onboarding | restore wallet revamp | restore | click',
OnboardingRestoreEnterRecoveryPhraseNextClick = 'onboarding | restore wallet revamp | enter your recovery phrase | next | click',
OnboardingRestoreEnterRecoveryPhrasePasteFromClipboardClick = 'onboarding | restore wallet revamp | enter your recovery phrase | paste from clipboard | click',
OnboardingRestoreEnterRecoveryPhrasePasteReadMoreClick = 'onboarding | restore wallet revamp | enter your recovery phrase | read more | click',
OnboardingRestoreHdWallet = 'onboarding | restore wallet | hd wallet',
OnboardingRestoreEnterWalletClick = "onboarding | restore wallet revamp | let's set up your new wallet | enter wallet | click",
// Create new wallet
Expand All @@ -25,8 +26,10 @@ export enum PostHogAction {
OnboardingCreateEnterRecoveryPhraseNextClick = 'onboarding | new wallet revamp | enter your recovery phrase | next | click',
OnboardingCreateSaveRecoveryPhraseIntroPlayVideoClick = 'onboarding | new wallet revamp | save your recovery phrase | watch video | click',
OnboardingCreateSaveRecoveryPhraseCopyToClipboardClick = 'onboarding | new wallet revamp | save your recovery phrase | copy to clipboard | click',
OnboardingCreateSaveRecoveryPhraseCopyReadMoreClick = 'onboarding | new wallet revamp | save your recovery phrase | read more | click',
OnboardingCreateKeepWalletSecureGotItClick = 'onboarding | new wallet revamp | keeping your wallet secure | got it | click',
OnboardingCreateEnterRecoveryPhrasePasteFromClipboardClick = 'onboarding | new wallet revamp | enter your recovery phrase | paste from clipboard | click',
OnboardingCreateEnterRecoveryPhrasePasteReadMoreClick = 'onboarding | new wallet revamp | enter your recovery phrase | read more | click',
OnboardingCreateEnterWalletClick = "onboarding | new wallet revamp | let's set up your new wallet | enter wallet | click",
// Multi wallet
MultiWalletSwitchWallet = 'multiwallet | switch wallet | click',
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/ui/assets/icons/purple-copy.component.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/core/src/ui/assets/icons/purple-paste.component.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,12 @@
left: 0;
opacity: 0;
}

.btnContentWrapper {
display: flex !important;
gap: size_unit(0.5) !important;

svg {
font-size: 24px;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Button } from 'antd';
import { Button, Tooltip } from 'antd';
import { WalletTimelineSteps } from '../../WalletSetup';
import { MnemonicWordsWritedownRevamp } from './MnemonicWordsWritedownRevamp';
import { WalletSetupStepLayoutRevamp } from '../WalletSetupStepLayoutRevamp';
import styles from '../../WalletSetup/WalletSetupOption.module.scss';
import './WalletSetupMnemonicRevampCommon.module.scss';
import { TranslationsFor } from '@ui/utils/types';
import { TranslationsForJSX } from '@ui/utils/types';
import { hasEmptyString } from './WalletSetupMnemonicVerificationStepRevamp';
import { Dialog } from '@lace/ui';
import { MnemonicWordsConfirmInputRevamp } from './MnemonicWordsConfirmInputRevamp';
import { Wallet } from '@lace/cardano';
import { readMnemonicFromClipboard, writeMnemonicToClipboard } from './wallet-utils';
import isEqual from 'lodash/isEqual';
import { ReactComponent as CopyIcon } from '../../../assets/icons/purple-copy.component.svg';
import { ReactComponent as PasteIcon } from '../../../assets/icons/purple-paste.component.svg';

export type MnemonicStage = 'writedown' | 'input';

Expand All @@ -21,7 +23,7 @@ export interface WalletSetupMnemonicStepProps {
onNext: () => void;
onStepNext?: (currentStage: MnemonicStage) => void;
isBackFromNextStep?: boolean;
translations: TranslationsFor<
translations: TranslationsForJSX<
| 'writePassphraseTitle'
| 'enterPassphraseDescription'
| 'enterPassphrase'
Expand All @@ -30,6 +32,7 @@ export interface WalletSetupMnemonicStepProps {
| 'passphraseError'
| 'copyToClipboard'
| 'pasteFromClipboard'
| 'copyPasteTooltipText'
>;
suggestionList?: Array<string>;
passphraseInfoLink?: string;
Expand Down Expand Up @@ -132,20 +135,30 @@ export const WalletSetupMnemonicStepRevamp = ({
currentTimelineStep={WalletTimelineSteps.RECOVERY_PHRASE}
customAction={
mnemonicStage === 'writedown' ? (
<Button
type="link"
onClick={async () => {
await writeMnemonicToClipboard(mnemonic);
onCopyToClipboard();
}}
data-testid="copy-to-clipboard-button"
>
{translations.copyToClipboard}
</Button>
<Tooltip placement="top" title={translations.copyPasteTooltipText} showArrow={false}>
<Button
type="link"
onClick={async () => {
await writeMnemonicToClipboard(mnemonic);
onCopyToClipboard();
}}
data-testid="copy-to-clipboard-button"
>
<span className={styles.btnContentWrapper}>
<CopyIcon />
{translations.copyToClipboard}
</span>
</Button>
</Tooltip>
) : (
<Button type="link" onClick={() => pasteRecoveryPhrase()} data-testid="paste-from-clipboard-button">
{translations.pasteFromClipboard}
</Button>
<Tooltip placement="top" title={translations.copyPasteTooltipText} showArrow={false}>
<Button type="link" onClick={() => pasteRecoveryPhrase()} data-testid="paste-from-clipboard-button">
<span className={styles.btnContentWrapper}>
<PasteIcon />
{translations.pasteFromClipboard}
</span>
</Button>
</Tooltip>
)
}
isNextEnabled={isSubmitEnabled}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import '../../../styles/theme.scss';
@import '../../../../../../common/src/ui/styles/abstracts/typography';
@import '../../WalletSetup/WalletSetupOption.module.scss';

.mnemonicContainer {
margin-top: size_unit(2) !important;
Expand Down Expand Up @@ -52,3 +53,7 @@
margin-top: size_unit(2) !important;
color: var(--color-pink, #ff5470);
}

.btnContentWrapper {
@extend .btnContentWrapper;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { WalletSetupStepLayoutRevamp } from '../WalletSetupStepLayoutRevamp';
import { MnemonicWordsConfirmInputRevamp } from './MnemonicWordsConfirmInputRevamp';
import styles from './WalletSetupMnemonicVerificationStepRevamp.module.scss';
import './WalletSetupMnemonicRevampCommon.module.scss';
import { TranslationsFor } from '@ui/utils/types';
import { Segmented, Button } from 'antd';
import { TranslationsForJSX } from '@ui/utils/types';
import { Segmented, Button, Tooltip } from 'antd';
import { readMnemonicFromClipboard } from './wallet-utils';
import { WalletTimelineSteps } from '@ui/components/WalletSetup';
import { ReactComponent as PasteIcon } from '../../../assets/icons/purple-paste.component.svg';

export const hasEmptyString = (arr: string[]): boolean => arr.includes('');
const MNEMONIC_LENGTHS = [12, 15, 24];
Expand All @@ -20,7 +21,9 @@ export interface WalletSetupMnemonicVerificationStepProps {
onSubmit: () => void;
isSubmitEnabled: boolean;
mnemonicWordsInStep?: number;
translations: TranslationsFor<'enterPassphrase' | 'passphraseError' | 'enterPassphraseLength' | 'pasteFromClipboard'>;
translations: TranslationsForJSX<
'enterPassphrase' | 'passphraseError' | 'enterPassphraseLength' | 'pasteFromClipboard' | 'copyPasteTooltipText'
>;
onCancel?: () => void;
suggestionList?: Array<string>;
defaultMnemonicLength?: number;
Expand Down Expand Up @@ -72,9 +75,14 @@ export const WalletSetupMnemonicVerificationStepRevamp = ({
onBack={onCancel}
onNext={onSubmit}
customAction={
<Button type="link" onClick={() => pasteRecoveryPhrase()} data-testid="paste-from-clipboard-button">
{translations.pasteFromClipboard}
</Button>
<Tooltip placement="top" title={translations.copyPasteTooltipText} showArrow={false}>
<Button type="link" onClick={() => pasteRecoveryPhrase()} data-testid="paste-from-clipboard-button">
<span className={styles.btnContentWrapper}>
<PasteIcon />
{translations.pasteFromClipboard}
</span>
</Button>
</Tooltip>
}
currentTimelineStep={WalletTimelineSteps.RECOVERY_PHRASE}
isNextEnabled={isSubmitEnabled}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/ui/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export type TranslationsFor<T extends string> = Record<T, string>;

export type TranslationsForJSX<T extends string> = Record<T, string | JSX.Element>;

0 comments on commit fc6e7ac

Please sign in to comment.