Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Change mnemonic save screen to be similar to MetaMask. #179

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 85 additions & 22 deletions src/popup/components/Protocol/EnsureUserSavedMnemonic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import {
Title,
Subtitle,
ClickableText,
Cta,
} from "@popup/components/Protocol/common";
import React, { useEffect } from "react";
import { useEffect } from "react";
import styled from "styled-components";
import { getProtocolOptIn } from "@services/Factory";
import { SetupSuccess } from "./SetupScreen";
import { useState } from "react";
const ButtonContainer = styled.div`
width: 100%;
display: grid;
Expand All @@ -19,6 +22,10 @@ const ButtonContainer = styled.div`
`;

const Button = styled.button`
&.ClickedWord {
opacity: 0.4;
}

cursor: pointer;
text-align: center;
color: var(--c-white);
Expand All @@ -27,15 +34,16 @@ const Button = styled.button`
padding: var(--s-10) var(--s-24);
font-weight: bold;
transition: color 0.2s ease-in-out, background-color 0.2s ease-in-out;
:disabled {
opacity: 0.4;
cursor: default;
}
:hover {
background: var(--c-dark-orange);
}
`;

const TextArea = styled.textarea`
width: 100%;
height: var(--s-48);
`;

interface CheckButton {
word: string;
isEnabled: boolean;
Expand All @@ -47,28 +55,56 @@ const WordButton = (props: {
onClick: () => void;
}) => {
return (
<Button disabled={!props.isEnabled} onClick={props.onClick}>
<Button
className={(() => {
return !props.isEnabled ? "ClickedWord" : "";
})()}
onClick={props.onClick}
>
{props.word}
</Button>
);
};

export function EnsureUserSavedMnemonic(props: { onComplete: () => void }) {
const [buttons, setButtons] = React.useState<CheckButton[]>([]);
const [mnemonicArr, setMnemonicArr] = React.useState<string[]>([]);
const TextField = (props: { input: string }) => {
return <TextArea defaultValue={props.input} />;
};

const [counter, setCounter] = React.useState(0);
export function EnsureUserSavedMnemonic(props: { onComplete: () => void }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the logic for this component is getting complex enough to warrant extracting a (unit tested) class to manage this state. Could either be functional (as in returns a new instance each time a mutation happens) or could have a subscription for when mutations happen that triggers a re-render.

const [pageOverride, setPageOverride] = useState<JSX.Element | null>(null);
const [buttons, setButtons] = useState<CheckButton[]>([]);
const [success, setSuccess] = useState(false);
const [mnemonic, setMnemonic] = useState("");
const [currentInput, setCurrentInput] = useState<string[]>([]);

useEffect(() => {
async function getMnemonic() {
const mnemonic = await getProtocolOptIn().getMnemonic();
setMnemonicArr((mnemonic as string).split(" "));
const sortedMnemonic = (mnemonic as string).split(" ").sort();
setMnemonic((await getProtocolOptIn().getMnemonic()) as string);
const sortedMnemonic = mnemonic.split(" ").sort();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mnemonic value in here won't be updated from the setMnemonic call on the previous line. If you override the variable with the result of the getMnemonic call, then you won't need to run this effect multiple times.

setButtons(sortedMnemonic.map((w) => ({ word: w, isEnabled: true })));
setPageOverride(
<SetupSuccess
mnemonic={mnemonic as string}
onContinue={() => setPageOverride(null)}
/>,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't have this component do the first showing of the SetupSuccess screen. I would have this only show that if the user clicks to see the mnemonic again or fails.

}

getMnemonic();
}, []);
}, [mnemonic]);

useEffect(() => {
if (
currentInput.length !== 0 &&
currentInput.join(" ").localeCompare(mnemonic) === 0
) {
setSuccess(true);
}
}, [currentInput, mnemonic]);

if (pageOverride != null) {
return pageOverride;
}

return (
<VerticalSequence>
Expand All @@ -77,25 +113,52 @@ export function EnsureUserSavedMnemonic(props: { onComplete: () => void }) {
<ButtonContainer>
{buttons.map((button: { word: string; isEnabled: boolean }, index) => (
<WordButton
key={index}
onClick={() => {
const check = mnemonicArr[counter] === button.word;
if (button.isEnabled) {
setCurrentInput((oldArray) => [...oldArray, button.word]);
} else {
setCurrentInput(
currentInput.filter((item) => item !== button.word),
);
}

let isEnabled = !button.isEnabled;
setButtons([
...buttons.slice(0, index),
{ ...button, isEnabled: !check },
{ ...button, isEnabled },
...buttons.slice(index + 1),
]);
if (check) {
setCounter(counter + 1);
}
if (counter + 1 >= buttons.length) {
props.onComplete();
}
}}
{...button}
/>
))}
</ButtonContainer>

<TextField input={currentInput.join(" ")}></TextField>

<Cta
disabled={!success}
onClick={() => {
props.onComplete();
}}
>
Continue
</Cta>

<Cta
onClick={() => {
setPageOverride(
<SetupSuccess
mnemonic={mnemonic as string}
onContinue={() => setPageOverride(null)}
/>,
);
}}
>
Back to mnemonic
</Cta>

<Subtitle>I understand the importance of saving my mnemonic</Subtitle>
<ClickableText
onClick={() => {
Expand Down
7 changes: 1 addition & 6 deletions src/popup/components/Protocol/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,7 @@ function ProtocolState() {
await getProtocolOptIn().optIn(mnemonic);
serviceOptedIn.reload();
completedLiveness.reload();
setPageOverride(
<SetupSuccess
mnemonic={mnemonic}
onContinue={() => setPageOverride(null)}
/>,
);
setPageOverride(null);
} catch (e) {
handleError(e, () => optInWithMnemonic(mnemonic));
}
Expand Down