diff --git a/src/react/App.tsx b/src/react/App.tsx index 0a497fa..5d028ef 100644 --- a/src/react/App.tsx +++ b/src/react/App.tsx @@ -1,27 +1,34 @@ import { CssBaseline, ThemeProvider, StyledEngineProvider } from "@mui/material"; import { FC, ReactElement } from "react"; import { HashRouter, Route, Switch } from "react-router-dom"; + +import BTECContextWrapper from "./BTECContext"; import { OnlineDetector } from "./components/OnlineDetector"; import VersionFooter from "./components/VersionFooter"; +import { paths } from "./constants"; import GlobalContextWrapper from "./GlobalContext"; -import CreateMnemonic from "./pages/CreateMnemonic"; -import Home from "./pages/Home"; -import theme from "./theme"; -import ConfigureValidatorKeys from "./pages/ConfigureValidatorKeys"; -import MnemonicImport from "./pages/MnemonicImport"; -import ConfigureWithdrawalAddress from "./pages/ConfigureWithdrawalAddress"; -import { BTECImportPath, ConfigureBTECPath, ConfigureCreatePath, ConfigureExistingPath, CreateCredentialsPath, CreateKeysCreatePath, CreateKeysExistingPath, CreatePath, ExistingImportPath, FinishCreatePath, FinishCredentialsPath, FinishExistingPath } from "./constants"; -import CreateValidatorKeys from "./pages/CreateValidatorKeys"; import KeyCreationContextWrapper from "./KeyCreationContext"; -import BTECContextWrapper from "./BTECContext"; -import FinishKeyGeneration from "./pages/FinishKeyGeneration"; +import ConfigureValidatorKeys from "./pages/ConfigureValidatorKeys"; +import ConfigureWithdrawalAddress from "./pages/ConfigureWithdrawalAddress"; import CreateCredentialsChange from "./pages/CreateCredentialsChange"; +import CreateMnemonic from "./pages/CreateMnemonic"; +import CreateValidatorKeys from "./pages/CreateValidatorKeys"; import FinishCredentialsGeneration from "./pages/FinishCredentialsGeneration"; +import FinishKeyGeneration from "./pages/FinishKeyGeneration"; +import Home from "./pages/Home"; +import MnemonicImport from "./pages/MnemonicImport"; +import theme from "./theme"; /** - * The React app top level including theme and routing. + * Routing for the application. Broken into four sections: + * - Primary home page + * - Routes for creating a mnemonic and validator keys + * - Routes for using an existing mnemonic to create validator keys + * - Routes for generating the credentials change * - * @returns the react element containing the app + * Each of the three flows is wrapped in a React Context that will store + * the inputs of the user to be accessible across each page. This prevents + * prop drilling */ const App: FC = (): ReactElement => { return ( @@ -40,10 +47,10 @@ const App: FC = (): ReactElement => { {/* Create Mnemonic & Keys Flow */} - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> @@ -51,10 +58,10 @@ const App: FC = (): ReactElement => { {/* Import Mnemonic & Generate Keys Flow */} - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> @@ -62,10 +69,10 @@ const App: FC = (): ReactElement => { {/* Update Withdrawal Credentials Flow */} - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> diff --git a/src/react/BTECContext.tsx b/src/react/BTECContext.tsx index aa7d60a..9b407dd 100644 --- a/src/react/BTECContext.tsx +++ b/src/react/BTECContext.tsx @@ -30,6 +30,9 @@ export const BTECContext = createContext({ setWithdrawalAddress: () => {}, }); +/** + * Context for making the withdrawal credentials change + */ const BTECContextWrapper = ({ children }: { children: React.ReactNode}) => { const [btecIndices, setBTECIndices] = useState(""); const [btecCredentials, setBTECCredentials] = useState(""); diff --git a/src/react/KeyCreationContext.tsx b/src/react/KeyCreationContext.tsx index 21f798a..a1fff9c 100644 --- a/src/react/KeyCreationContext.tsx +++ b/src/react/KeyCreationContext.tsx @@ -30,6 +30,9 @@ export const KeyCreationContext = createContext({ setWithdrawalAddress: () => {}, }); +/** + * Context for generating a validator key for both using an existing mnemonic or a new one + */ const KeyCreationContextWrapper = ({ children }: { children: React.ReactNode}) => { const [folderLocation, setFolderLocation] = useState(""); const [index, setIndex] = useState(0); diff --git a/src/react/components/FolderSelector.tsx b/src/react/components/FolderSelector.tsx index 15c9107..06dbf7f 100644 --- a/src/react/components/FolderSelector.tsx +++ b/src/react/components/FolderSelector.tsx @@ -1,16 +1,26 @@ import { Button, Typography } from "@mui/material"; import { OpenDialogOptions, OpenDialogReturnValue } from 'electron'; import { useState } from "react"; + import { errors } from "../constants"; interface FolderSelectorParams { onFolderSelect: (folder: string) => void; } +/** + * Component to select a folder and will use onFolderSelect param to provide + * selected folder to parent + * @param onFolderSelect callback to provide selected folder + */ const FolderSelector = ({ onFolderSelect }: FolderSelectorParams) => { const [displayFolderPicker, setDisplayFolderPicker] = useState(false); const [errorMessage, setErrorMessage] = useState(""); + /** + * Called upon selecting a folder. Will verify the folder exists and is writable + * @param folderPath the path of the folder to verify + */ const verifyFolder = (folderPath: string) => { window.bashUtils.doesDirectoryExist(folderPath) .then((exists) => { @@ -30,6 +40,9 @@ const FolderSelector = ({ onFolderSelect }: FolderSelectorParams) => { }); }; + /** + * Will open a dialog to allow the user to select a folder + */ const chooseFolder = () => { const options: OpenDialogOptions = { properties: ['openDirectory'] diff --git a/src/react/components/Loader.tsx b/src/react/components/Loader.tsx index 160f0e3..d75c1f0 100644 --- a/src/react/components/Loader.tsx +++ b/src/react/components/Loader.tsx @@ -2,14 +2,16 @@ interface LoaderParams { message: string; } -const Loader = ({ message }: LoaderParams) => { - return ( -
-
{message}
+/** + * Simple loading spinner component with dynamic message + * @param message message to display to the user + */ +const Loader = ({ message }: LoaderParams) => ( +
+
{message}
-
-
- ) -}; +
+
+); export default Loader; diff --git a/src/react/components/OnlineDetector.tsx b/src/react/components/OnlineDetector.tsx index c8a13aa..7f05c94 100644 --- a/src/react/components/OnlineDetector.tsx +++ b/src/react/components/OnlineDetector.tsx @@ -1,25 +1,14 @@ -import { - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - Grid, - Typography, - styled, -} from "@mui/material"; +import { Button, Typography } from "@mui/material"; import PermScanWifiIcon from "@mui/icons-material/PermScanWifi"; import React from "react"; + import OnlineWarningModal from "../modals/OnlineWarningModal"; /** * This will add an event listener to detect the users internet connectivity. * If active, a pulsing warning icon with text will appear on the screen that - * when clicked will show a dialog warning the user of the danger of internet + * when clicked will show a modal to warn the user of the danger of internet * connectivity. - * - * @returns the warning and dialog component to render if necessary */ export const OnlineDetector = () => { const [open, setOpen] = React.useState(false); diff --git a/src/react/components/VerifyMnemonic.tsx b/src/react/components/VerifyMnemonic.tsx index 4ae1316..4d4ac77 100644 --- a/src/react/components/VerifyMnemonic.tsx +++ b/src/react/components/VerifyMnemonic.tsx @@ -1,8 +1,10 @@ import { Grid, TextField } from "@mui/material"; -import { Dispatch, ReactNode, SetStateAction, useContext, useEffect, useMemo, useRef, useState } from "react"; -import { GlobalContext } from "../GlobalContext"; -import { Network } from "../types"; +import { Dispatch, SetStateAction, useContext, useRef, useState } from "react"; + import { errors } from "../constants"; +import { GlobalContext } from "../GlobalContext"; +import { KeyCreationContext } from "../KeyCreationContext"; +import { Network } from "../types"; interface VerifyMnemonicParams { hasError: boolean; @@ -11,13 +13,22 @@ interface VerifyMnemonicParams { onVerifyMnemonic: () => void; } +/** + * Creates a grid of inputs if on mainnet or a single textbox for ease of testing. + * The user fills the form with the mnemonic and the input will be provided to the + * onVerifyMnemonic for futher action + * + * mnemonicToVerify is provided as a param instead of existing only in this component + * so the parent can fill the value if the user goes back in navigation + */ const VerifyMnemonic = ({ hasError = false, mnemonicToVerify, setMnemonicToVerify, onVerifyMnemonic, }: VerifyMnemonicParams) => { - const { mnemonic, network } = useContext(GlobalContext); + const { mnemonic } = useContext(KeyCreationContext); + const { network } = useContext(GlobalContext); const [mnemonicToVerifyArray, setMnemonicToVerifyArray] = useState( mnemonicToVerify ? mnemonicToVerify.split(' ') : Array(24).fill('')); const inputRefs = useRef([]); @@ -41,8 +52,11 @@ const VerifyMnemonic = ({ } }; + /** + * Will focus on a new input depending on the pressed key + * @param index the current input index + */ const handleKeysForWord = (index: number) => (e: React.KeyboardEvent) => { - // Navigate phrase confirmation with spacebar or arrowkeys let nextFocus = index; const currentTextFieldValue = mnemonicToVerifyArray[index]; diff --git a/src/react/components/VersionFooter.tsx b/src/react/components/VersionFooter.tsx index 8e14020..42fa49f 100644 --- a/src/react/components/VersionFooter.tsx +++ b/src/react/components/VersionFooter.tsx @@ -1,32 +1,15 @@ import { Typography } from "@mui/material"; -import styled from "styled-components"; declare var VERSION: string; declare var COMMITHASH: string; -const SoftText = styled(Typography)` - color: gray; - text-align: center; - font-size: 10px; -`; - -const Container = styled.div` - position: fixed; - bottom: 35; - width: 100%; -`; - /** - * This component is a footer used to display the version and commit hash. - * - * @returns the footer component containing the version and commit hash + * Footer to display the version and commit hash */ -const VersionFooter = () => { - return( - - Version: {VERSION} - Commit Hash: {COMMITHASH} - - ) -} +const VersionFooter = () => ( +
+ Version: {VERSION} - Commit Hash: {COMMITHASH} +
+); -export default VersionFooter; \ No newline at end of file +export default VersionFooter; diff --git a/src/react/components/WizardWrapper.tsx b/src/react/components/WizardWrapper.tsx index bd82519..5da2725 100644 --- a/src/react/components/WizardWrapper.tsx +++ b/src/react/components/WizardWrapper.tsx @@ -1,7 +1,8 @@ import { ReactNode, useContext } from "react"; -import { GlobalContext } from "../GlobalContext"; import { Step, StepLabel, Stepper, Typography } from "@mui/material"; + import { stepLabels } from "../constants"; +import { GlobalContext } from "../GlobalContext"; import { StepKey } from "../types"; interface WizardWrapperParams { @@ -12,6 +13,15 @@ interface WizardWrapperParams { title: string; } +/** + * Wrapper of a page to display the network, title, stepper, and action bar buttons. + * + * @param actionBarItems A list of buttons to display + * @param activeTimelineIndex The index of the timelineItems array that is active + * @param children The inner content of the page + * @param timelineItems A list of steps to display + * @param title The title to appear at the top of the page + */ const WizardWrapper = ({ actionBarItems, activeTimelineIndex, diff --git a/src/react/constants.ts b/src/react/constants.ts index b747113..8248913 100644 --- a/src/react/constants.ts +++ b/src/react/constants.ts @@ -53,17 +53,19 @@ export const CreateMnemonicFlow = [StepKey.MnemonicGeneration, StepKey.KeyConfig export const ExistingMnemonicFlow = [StepKey.MnemonicImport, StepKey.KeyConfiguration, StepKey.KeyGeneration, StepKey.Finish]; export const BTECFlow = [StepKey.MnemonicImport, StepKey.BTECConfiguration, StepKey.BTECGeneration, StepKey.FinishBTEC]; -export const CreatePath = "/create"; -export const ConfigureCreatePath = "/configure-create"; -export const CreateKeysCreatePath = "/create-keys"; -export const FinishCreatePath ="/finish-create"; +export const paths = { + CREATE_MNEMONIC: "/create", + CONFIGURE_CREATE: "/configure-create", + CREATE_KEYS_CREATE: "/create-keys", + FINISH_CREATE:"/finish-create", -export const ExistingImportPath = "/import-existing"; -export const ConfigureExistingPath = "/configure-existing"; -export const CreateKeysExistingPath = "/create-existing-keys"; -export const FinishExistingPath = "/finish-existing"; + EXISTING_IMPORT: "/import-existing", + CONFIGURE_EXISTING: "/configure-existing", + CREATE_KEYS_EXISTING: "/create-existing-keys", + FINISH_EXISTING: "/finish-existing", -export const BTECImportPath = "/import-btec"; -export const ConfigureBTECPath = "/configure-btec"; -export const CreateCredentialsPath = "/create-btec"; -export const FinishCredentialsPath = "/finish-btec"; + BTEC_IMPORT: "/import-btec", + CONFIGURE_BTEC: "/configure-btec", + CREATE_CREDENTIALS: "/create-btec", + FINISH_CREDENTIALS: "/finish-btec", +}; diff --git a/src/react/globalContext.tsx b/src/react/globalContext.tsx index 1d79ad7..942345e 100644 --- a/src/react/globalContext.tsx +++ b/src/react/globalContext.tsx @@ -1,26 +1,25 @@ import { Dispatch, SetStateAction, createContext, useState } from "react"; + import { Network } from "./types"; interface GlobalContextType { - mnemonic: string; - setMnemonic: Dispatch>; network: Network; setNetwork: Dispatch>; } export const GlobalContext = createContext({ - mnemonic: "", - setMnemonic: () => {}, network: Network.MAINNET, setNetwork: () => {}, }); +/** + * Global context for the network which is used across the application + */ const GlobalContextWrapper = ({ children }: { children: React.ReactNode}) => { - const [mnemonic, setMnemonic] = useState(""); const [network, setNetwork] = useState(Network.MAINNET); return ( - + {children} ); diff --git a/src/react/helpers.ts b/src/react/helpers.ts index 2c417d0..9e9640a 100644 --- a/src/react/helpers.ts +++ b/src/react/helpers.ts @@ -1,3 +1,8 @@ +/** + * Helper function to clean the mnemonic of invalid characters and extraneous spacing + * @param mnemonic the mnemonic to clean + * @returns the cleaned mnemonic + */ export const cleanMnemonic = (mnemonic: String): string => { const punctuationRemoved = mnemonic.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, " "); const singleSpace = punctuationRemoved.replace(/\s\s+/g, " "); diff --git a/src/react/index.tsx b/src/react/index.tsx index 1a3e066..33bfa16 100644 --- a/src/react/index.tsx +++ b/src/react/index.tsx @@ -1,6 +1,7 @@ import * as ReactDOM from "react-dom"; -import App from "./App"; import 'typeface-roboto'; + +import App from "./App"; import './index.css'; // We find our app DOM element as before diff --git a/src/react/modals/NetworkPickerModal.tsx b/src/react/modals/NetworkPickerModal.tsx index 6e5e107..131156a 100644 --- a/src/react/modals/NetworkPickerModal.tsx +++ b/src/react/modals/NetworkPickerModal.tsx @@ -7,9 +7,10 @@ import { RadioGroup, Typography, } from "@mui/material"; -import { Network } from "../types"; import { useContext, useEffect, useState } from "react"; + import { GlobalContext } from "../GlobalContext"; +import { Network } from "../types"; import WagyuModal from "./WagyuModal"; interface NetworkPickerModalParams { @@ -17,6 +18,9 @@ interface NetworkPickerModalParams { showModal: boolean; } +/** + * Modal to allow the user to pick the Ethereum Network + */ const NetworkPickerModal = ({onClose, showModal}: NetworkPickerModalParams) => { const { network, setNetwork } = useContext(GlobalContext); const [formNetwork, setFormNetwork] = useState(Network.MAINNET); diff --git a/src/react/modals/OnlineWarningModal.tsx b/src/react/modals/OnlineWarningModal.tsx index 1140341..d37bd96 100644 --- a/src/react/modals/OnlineWarningModal.tsx +++ b/src/react/modals/OnlineWarningModal.tsx @@ -1,4 +1,5 @@ import { Button, Typography } from "@mui/material"; + import WagyuModal from "./WagyuModal"; interface OlineWarningModalParams { @@ -7,49 +8,50 @@ interface OlineWarningModalParams { open: boolean; } -const OnlineWarningModal = ({ onClose, onHideWarning, open }: OlineWarningModalParams) => { - return ( - -
-
Internet Connection Detected
-
- - Being connected to the internet while using this tool drastically increases the risk of exposing your Secret Recovery Phrase. - - - You can avoid this risk by having a live OS such as Tails installed on a USB drive and run on a computer with network capabilities disabled. - +/** + * Modal to display to the user to explain the risks of using this tool with network connectivity + */ +const OnlineWarningModal = ({ onClose, onHideWarning, open }: OlineWarningModalParams) => ( + +
+
Internet Connection Detected
+
+ + Being connected to the internet while using this tool drastically increases the risk of exposing your Secret Recovery Phrase. + + + You can avoid this risk by having a live OS such as Tails installed on a USB drive and run on a computer with network capabilities disabled. + - - You can visit https://tails.net/install/ for instructions on how to download, install, and run Tails on a USB device. - - - If you have any questions you can get help at https://dsc.gg/ethstaker - -
-
- - -
+ + You can visit https://tails.net/install/ for instructions on how to download, install, and run Tails on a USB device. + + + If you have any questions you can get help at https://dsc.gg/ethstaker +
-
- ); -}; +
+ + +
+
+ +); export default OnlineWarningModal; diff --git a/src/react/modals/ReuseMnemonicActionModal.tsx b/src/react/modals/ReuseMnemonicActionModal.tsx index d3c2a73..3061a19 100644 --- a/src/react/modals/ReuseMnemonicActionModal.tsx +++ b/src/react/modals/ReuseMnemonicActionModal.tsx @@ -1,4 +1,5 @@ import { Button, Tooltip } from "@mui/material"; + import { ReuseMnemonicAction } from "../types"; import WagyuModal from "./WagyuModal"; @@ -8,35 +9,38 @@ interface ReuseMnemonicActionModalParams { showModal: boolean; } -const ReuseMnemonicActionModal = ({ onClose, onSubmit, showModal}: ReuseMnemonicActionModalParams) => { - return ( - -
-
How would you like to use your existing secret recovery phrase?
+/** + * Modal for the user to pick which action they would like to take when reusing a mnemonic + * + * Options are: Generate existing keys or Generate BLS change + */ +const ReuseMnemonicActionModal = ({ onClose, onSubmit, showModal}: ReuseMnemonicActionModalParams) => ( + +
+
How would you like to use your existing secret recovery phrase?
-
+
-
-
- +
+
+ + -
-
- - - -
+
- - ) -}; +
+ +); export default ReuseMnemonicActionModal; diff --git a/src/react/modals/WagyuModal.tsx b/src/react/modals/WagyuModal.tsx index 433e02f..dc1eff1 100644 --- a/src/react/modals/WagyuModal.tsx +++ b/src/react/modals/WagyuModal.tsx @@ -5,17 +5,18 @@ interface WagyuModalParams { children: React.ReactNode; } -const WagyuModal = ({ children, className, onClose, open}: WagyuModalParams & ModalProps) => { - return ( - -
- {children} -
-
- ); -}; +/** + * Wrapper for modal usages to keep consistent styling. + */ +const WagyuModal = ({ children, className, onClose, open}: WagyuModalParams & ModalProps) => ( + +
+ {children} +
+
+); export default WagyuModal; diff --git a/src/react/pages/ConfigureValidatorKeys.tsx b/src/react/pages/ConfigureValidatorKeys.tsx index 273c50b..cb2a91b 100644 --- a/src/react/pages/ConfigureValidatorKeys.tsx +++ b/src/react/pages/ConfigureValidatorKeys.tsx @@ -1,10 +1,18 @@ import { Button, TextField, Tooltip, Typography } from "@mui/material"; -import WizardWrapper from "../components/WizardWrapper"; import { useContext, useEffect, useState } from "react"; import { useHistory } from "react-router-dom"; -import { ConfigureExistingPath, CreateKeysCreatePath, CreateKeysExistingPath, CreateMnemonicFlow, CreatePath, ExistingImportPath, ExistingMnemonicFlow, errors, tooltips } from "../constants"; + +import WizardWrapper from "../components/WizardWrapper"; +import { CreateMnemonicFlow, ExistingMnemonicFlow, errors, paths, tooltips } from "../constants"; import { KeyCreationContext } from "../KeyCreationContext"; +/** + * Form to provide number of keys, index, password, and optional withdrawal address necessary to + * complete the validator key creation process. + * + * User will provide the necessary inputs and a verification of the password will be done before + * they can continue the flow + */ const ConfigureValidatorKeys = () => { const { mnemonic, @@ -14,7 +22,7 @@ const ConfigureValidatorKeys = () => { setWithdrawalAddress, } = useContext(KeyCreationContext); const history = useHistory(); - const usingExistingFlow = history.location.pathname === ConfigureExistingPath; + const usingExistingFlow = history.location.pathname === paths.CONFIGURE_EXISTING; const [passwordToVerify, setPasswordToVerify] = useState(""); const [verifyPassword, setVerifyPassword] = useState(false); @@ -34,7 +42,7 @@ const ConfigureValidatorKeys = () => { useEffect(() => { if (!mnemonic) { - history.replace(usingExistingFlow ? ExistingImportPath : CreatePath); + history.replace(usingExistingFlow ? paths.EXISTING_IMPORT : paths.CREATE_MNEMONIC); } }, []); @@ -56,6 +64,10 @@ const ConfigureValidatorKeys = () => { setInputWithdrawalAddress(e.target.value.trim()); }; + /** + * Validates each value simultaneously and if there are no errors will show the + * user the password verification input + */ const validateInputs = async () => { let isError = false; @@ -103,6 +115,9 @@ const ConfigureValidatorKeys = () => { } }; + /** + * Verifies the passwords match and will move the user to the next step in the flow if so + */ const checkPassword = () => { if (inputPassword.localeCompare(passwordToVerify) == 0) { setPasswordVerifyError(false); @@ -113,7 +128,7 @@ const ConfigureValidatorKeys = () => { setPassword(inputPassword); setWithdrawalAddress(inputWithdrawalAddress); - history.push(usingExistingFlow ? CreateKeysExistingPath : CreateKeysCreatePath); + history.push(usingExistingFlow ? paths.CREATE_KEYS_EXISTING : paths.CREATE_KEYS_CREATE); } else { setPasswordVerifyError(true); } diff --git a/src/react/pages/ConfigureWithdrawalAddress.tsx b/src/react/pages/ConfigureWithdrawalAddress.tsx index 40f6283..3547da9 100644 --- a/src/react/pages/ConfigureWithdrawalAddress.tsx +++ b/src/react/pages/ConfigureWithdrawalAddress.tsx @@ -1,12 +1,17 @@ import { Button, TextField, Tooltip, Typography } from "@mui/material"; -import WizardWrapper from "../components/WizardWrapper"; -import { BTECFlow, BTECImportPath, CreateCredentialsPath, errors, tooltips } from "../constants"; import { useContext, useEffect, useState } from "react"; import { useHistory } from "react-router-dom"; -import { BTECContext } from "../BTECContext"; -import { GlobalContext } from "../GlobalContext"; -import Loader from "../components/Loader"; +import { BTECContext } from "../BTECContext"; +import Loader from "../components/Loader"; +import WizardWrapper from "../components/WizardWrapper"; +import { BTECFlow, errors, paths, tooltips } from "../constants"; +import { GlobalContext } from "../GlobalContext"; + +/** + * Form to provide start index, validator indices, current withdrawal credentials, and the + * desired withdrawal address to be set. + */ const ConfigureWithdrawalAddress = () => { const { btecCredentials, @@ -35,7 +40,7 @@ const ConfigureWithdrawalAddress = () => { useEffect(() => { if (!mnemonic) { - history.replace(BTECImportPath); + history.replace(paths.BTEC_IMPORT); } }, []); @@ -56,6 +61,14 @@ const ConfigureWithdrawalAddress = () => { setInputWithdrawalAddress(e.target.value.trim()); }; + /** + * Three step function: + * - Validate all inputs and make sure there are no errors + * - If no errors, validate the provided BLS credentials + * - If successful, send user to the creation step + * + * Any errors the user will be provided a corresponding error message + */ const validateInputs = async () => { let isError = false; @@ -139,7 +152,7 @@ const ConfigureWithdrawalAddress = () => { setIndex(inputIndex); setWithdrawalAddress(inputWithdrawalAddress); - history.push(CreateCredentialsPath); + history.push(paths.CREATE_CREDENTIALS); }).catch(() => { setValidatingCredentials(false); setCredentialsError(errors.BLS_CREDENTIALS_NO_MATCH); diff --git a/src/react/pages/CreateCredentialsChange.tsx b/src/react/pages/CreateCredentialsChange.tsx index b3abe38..cc9c7d7 100644 --- a/src/react/pages/CreateCredentialsChange.tsx +++ b/src/react/pages/CreateCredentialsChange.tsx @@ -1,13 +1,19 @@ import { Button, Typography } from "@mui/material"; -import WizardWrapper from "../components/WizardWrapper"; -import { BTECFlow, BTECImportPath, FinishCredentialsPath } from "../constants"; import { useContext, useEffect, useState } from "react"; -import { GlobalContext } from "../GlobalContext"; -import { BTECContext } from "../BTECContext"; -import Loader from "../components/Loader"; -import FolderSelector from "../components/FolderSelector"; import { useHistory } from "react-router-dom"; +import { BTECContext } from "../BTECContext"; +import FolderSelector from "../components/FolderSelector"; +import Loader from "../components/Loader"; +import WizardWrapper from "../components/WizardWrapper"; +import { BTECFlow, paths } from "../constants"; +import { GlobalContext } from "../GlobalContext"; + +/** + * Allows the user to select a folder where the credentials will be saved + * and after which will attempt to generate the credential change and save + * to the specified folder + */ const CreateCredentialsChange = () => { const { network } = useContext(GlobalContext); const { @@ -26,7 +32,7 @@ const CreateCredentialsChange = () => { useEffect(() => { if (!mnemonic) { - history.replace(BTECImportPath); + history.replace(paths.BTEC_IMPORT); } }, []); @@ -34,6 +40,10 @@ const CreateCredentialsChange = () => { setSelectedFolder(folder); }; + /** + * Attempts to generate the credentials change and if successful send the user + * to the final step of the flow + */ const handleBTECFileGeneration = () => { setCreatingCredentialsChange(true); let appendedWithdrawalAddress = withdrawalAddress; @@ -52,7 +62,7 @@ const CreateCredentialsChange = () => { appendedWithdrawalAddress, ).then(() => { setFolderLocation(selectedFolder); - history.push(FinishCredentialsPath); + history.push(paths.FINISH_CREDENTIALS); }).catch((error) => { const errorMsg = ('stderr' in error) ? error.stderr : error.message; setGenerationError(errorMsg); diff --git a/src/react/pages/CreateMnemonic.tsx b/src/react/pages/CreateMnemonic.tsx index f5848c9..0006881 100644 --- a/src/react/pages/CreateMnemonic.tsx +++ b/src/react/pages/CreateMnemonic.tsx @@ -1,15 +1,20 @@ -import { Button, Grid, IconButton, TextField, Tooltip, Typography } from "@mui/material"; -import WizardWrapper from "../components/WizardWrapper"; -import { StepKey } from "../types"; -import Loader from "../components/Loader"; -import { useContext, useEffect, useMemo, useState } from "react"; import { FileCopy } from "@mui/icons-material"; -import VerifyMnemonic from "../components/VerifyMnemonic"; -import { cleanMnemonic } from '../helpers'; +import { Button, Grid, IconButton, TextField, Tooltip, Typography } from "@mui/material"; +import { useContext, useEffect, useMemo, useState } from "react"; import { useHistory } from "react-router-dom"; -import { ConfigureCreatePath } from "../constants"; -import { KeyCreationContext } from "../KeyCreationContext"; +import Loader from "../components/Loader"; +import VerifyMnemonic from "../components/VerifyMnemonic"; +import WizardWrapper from "../components/WizardWrapper"; +import { paths } from "../constants"; +import { cleanMnemonic } from '../helpers'; +import { KeyCreationContext } from "../KeyCreationContext"; +import { StepKey } from "../types"; + +/** + * Creates a new mnemonic for the user which will then be validated to make sure the + * user has stored it properly. + */ const CreateMnemonic = () => { const {mnemonic, setMnemonic} = useContext(KeyCreationContext); const history = useHistory(); @@ -76,6 +81,9 @@ const CreateMnemonic = () => { } }; + /** + * Creates an array of inputs that will display each word of the mnemonic + */ const createMnemonicDisplay = () => { return( @@ -124,7 +132,7 @@ const CreateMnemonic = () => { if (cleanedMnemonic.localeCompare(cleanedMnemonicToVerify) === 0) { setMnemonicValidationError(false); - history.push(ConfigureCreatePath); + history.push(paths.CONFIGURE_CREATE); } else { setMnemonicValidationError(true); } diff --git a/src/react/pages/CreateValidatorKeys.tsx b/src/react/pages/CreateValidatorKeys.tsx index 9d4432b..8a3f803 100644 --- a/src/react/pages/CreateValidatorKeys.tsx +++ b/src/react/pages/CreateValidatorKeys.tsx @@ -1,13 +1,18 @@ import { useContext, useEffect, useState } from "react"; import { useHistory } from "react-router-dom"; -import WizardWrapper from "../components/WizardWrapper"; import { Button, Typography } from "@mui/material"; -import { CreateKeysExistingPath, CreateMnemonicFlow, CreatePath, ExistingImportPath, ExistingMnemonicFlow, FinishCreatePath, FinishExistingPath } from "../constants"; -import { KeyCreationContext } from "../KeyCreationContext"; + import FolderSelector from "../components/FolderSelector"; import Loader from "../components/Loader"; +import WizardWrapper from "../components/WizardWrapper"; +import { CreateMnemonicFlow, ExistingMnemonicFlow, paths } from "../constants"; import { GlobalContext } from "../GlobalContext"; +import { KeyCreationContext } from "../KeyCreationContext"; +/** + * Allows the user to select a destination folder for the validator keys. + * After which the user can attempt to generate the keys. + */ const CreateValidatorKeys = () => { const { setFolderLocation, @@ -19,7 +24,7 @@ const CreateValidatorKeys = () => { } = useContext(KeyCreationContext); const { network } = useContext(GlobalContext); const history = useHistory(); - const usingExistingFlow = history.location.pathname === CreateKeysExistingPath; + const usingExistingFlow = history.location.pathname === paths.CREATE_KEYS_EXISTING; const [creatingKeys, setCreatingKeys] = useState(false); const [generationError, setGenerationError] = useState(""); @@ -27,7 +32,7 @@ const CreateValidatorKeys = () => { useEffect(() => { if (!mnemonic) { - history.replace(usingExistingFlow ? ExistingImportPath : CreatePath); + history.replace(usingExistingFlow ? paths.EXISTING_IMPORT : paths.CREATE_MNEMONIC); } }, []); @@ -35,6 +40,10 @@ const CreateValidatorKeys = () => { setSelectedFolder(folder); }; + /** + * Will attempt to generate the validator keys with the provided folder and if successful + * will send the user to the final step of the flow + */ const createKeys = () => { setCreatingKeys(true); @@ -54,7 +63,7 @@ const CreateValidatorKeys = () => { selectedFolder, ).then(() => { setFolderLocation(selectedFolder); - history.push(usingExistingFlow ? FinishExistingPath : FinishCreatePath); + history.push(usingExistingFlow ? paths.FINISH_EXISTING : paths.FINISH_CREATE); }).catch((error) => { const errorMsg = ('stderr' in error) ? error.stderr : error.message; setGenerationError(errorMsg); diff --git a/src/react/pages/FinishCredentialsGeneration.tsx b/src/react/pages/FinishCredentialsGeneration.tsx index e287444..46f7453 100644 --- a/src/react/pages/FinishCredentialsGeneration.tsx +++ b/src/react/pages/FinishCredentialsGeneration.tsx @@ -1,10 +1,16 @@ import { Button, Link, Typography } from "@mui/material"; +import { useContext, useEffect } from "react"; +import { useHistory } from "react-router-dom"; + +import { BTECContext } from "../BTECContext"; import WizardWrapper from "../components/WizardWrapper"; import { BTECFlow } from "../constants"; -import { useHistory } from "react-router-dom"; -import { useContext, useEffect } from "react"; -import { BTECContext } from "../BTECContext"; +/** + * Final step of the credentials generation flow. + * Shows the user the location where the files were stored and provides + * some additional information. + */ const FinishCredentialsGeneration = () => { const { folderLocation } = useContext(BTECContext); const history = useHistory(); @@ -15,6 +21,9 @@ const FinishCredentialsGeneration = () => { } }, []); + /** + * Will open a directory explorer where the credential change file was saved + */ const openKeyLocation = () => { window.bashUtils.findFirstFile(folderLocation, "keystore") .then((keystoreFile) => { diff --git a/src/react/pages/FinishKeyGeneration.tsx b/src/react/pages/FinishKeyGeneration.tsx index 4502458..dd5528b 100644 --- a/src/react/pages/FinishKeyGeneration.tsx +++ b/src/react/pages/FinishKeyGeneration.tsx @@ -1,14 +1,19 @@ -import { useHistory } from "react-router-dom"; -import { CreateMnemonicFlow, ExistingMnemonicFlow, FinishExistingPath } from "../constants"; -import { useContext, useEffect } from "react"; -import { KeyCreationContext } from "../KeyCreationContext"; -import WizardWrapper from "../components/WizardWrapper"; import { Button, Link, Typography } from "@mui/material"; +import { useContext, useEffect } from "react"; +import { useHistory } from "react-router-dom"; +import WizardWrapper from "../components/WizardWrapper"; +import { CreateMnemonicFlow, ExistingMnemonicFlow, paths } from "../constants"; +import { KeyCreationContext } from "../KeyCreationContext"; + +/** + * Final step of creating validator keys. Will show the folder of where + * the keys were created and additional information + */ const FinishKeyGeneration = () => { const { folderLocation } = useContext(KeyCreationContext); const history = useHistory(); - const usingExistingFlow = history.location.pathname === FinishExistingPath; + const usingExistingFlow = history.location.pathname === paths.FINISH_EXISTING; useEffect(() => { if (!folderLocation) { @@ -16,6 +21,9 @@ const FinishKeyGeneration = () => { } }, []); + /** + * Will open a directory explorer where the validator keys were saved + */ const openKeyLocation = () => { window.bashUtils.findFirstFile(folderLocation, "keystore") .then((keystoreFile) => { @@ -38,7 +46,7 @@ const FinishKeyGeneration = () => { timelineItems={usingExistingFlow ? ExistingMnemonicFlow : CreateMnemonicFlow} title="Create Keys" > -
+
Your keys have been created here:{" "} { const { network } = useContext(GlobalContext); const [wasNetworkModalOpened, setWasNetworkModalOpened] = useState(false); @@ -40,7 +46,7 @@ const Home = () => { if (!wasNetworkModalOpened) { handleOpenNetworkModal(); } else { - history.push(CreatePath) + history.push(paths.CREATE_MNEMONIC) } }; @@ -62,10 +68,10 @@ const Home = () => { setShowReuseMnemonicModal(false); if (action === ReuseMnemonicAction.RegenerateKeys) { - history.push(ExistingImportPath); + history.push(paths.EXISTING_IMPORT); } else if (action === ReuseMnemonicAction.GenerateBLSToExecutionChange) { - history.push(BTECImportPath); + history.push(paths.BTEC_IMPORT); } }; diff --git a/src/react/pages/MnemonicImport.tsx b/src/react/pages/MnemonicImport.tsx index 66c3bfe..74d19ac 100644 --- a/src/react/pages/MnemonicImport.tsx +++ b/src/react/pages/MnemonicImport.tsx @@ -1,23 +1,38 @@ import { Button, TextField } from "@mui/material"; -import { useHistory } from "react-router-dom"; -import WizardWrapper from "../components/WizardWrapper"; import { useContext, useState } from "react"; -import { cleanMnemonic } from "../helpers"; -import { BTECFlow, BTECImportPath, ConfigureBTECPath, ConfigureExistingPath, ExistingMnemonicFlow, MNEMONIC_ERROR_SEARCH, VALID_MNEMONIC_LENGTHS, errors } from "../constants"; -import Loader from "../components/Loader"; -import { KeyCreationContext } from "../KeyCreationContext"; -import { BTECContext } from "../BTECContext"; +import { useHistory } from "react-router-dom"; +import { BTECContext } from "../BTECContext"; +import Loader from "../components/Loader"; +import WizardWrapper from "../components/WizardWrapper"; +import { + BTECFlow, + ExistingMnemonicFlow, + MNEMONIC_ERROR_SEARCH, + VALID_MNEMONIC_LENGTHS, + errors, + paths, +} from "../constants"; +import { cleanMnemonic } from "../helpers"; +import { KeyCreationContext } from "../KeyCreationContext"; + +/** + * Allows the user to import an existing mnemonic to kickstart either the + * validator key creation or withdrawal credentials change flow + */ const MnemonicImport = () => { const {mnemonic: btecMnemonic, setMnemonic: setBTECMnemonic} = useContext(BTECContext); const {mnemonic, setMnemonic} = useContext(KeyCreationContext); const history = useHistory(); - const usingBTEC = history.location.pathname === BTECImportPath; + const usingBTEC = history.location.pathname === paths.BTEC_IMPORT; const [error, setError] = useState(""); const [inputMnemonic, setInputMnemonic] = useState(usingBTEC ? btecMnemonic : mnemonic); const [validatingMnemonic, setValidatingMnemonic] = useState(false); + /** + * Verifies the mnemonic is valid and will notify the user if not + */ const verifyMnemonic = () => { setError(""); @@ -37,7 +52,7 @@ const MnemonicImport = () => { setMnemonic(cleanedMnemonic); } setValidatingMnemonic(false); - history.push(usingBTEC ? ConfigureBTECPath : ConfigureExistingPath); + history.push(usingBTEC ? paths.CONFIGURE_BTEC : paths.CONFIGURE_EXISTING); }).catch((error) => { const errorMsg = ('stderr' in error) ? error.stderr : error.message; diff --git a/tailwind.config.js b/tailwind.config.js index afe2439..050a623 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -34,6 +34,9 @@ module.exports = { orange: "#F2994A", }, extend: { + fontSize: { + xxs: ".625rem", + }, keyframes: { OnlinePulse: { "0%": {