From 155dc0c7cf3bd023c2eede5062c97e8c4685300f Mon Sep 17 00:00:00 2001 From: igorgoldobin Date: Tue, 7 Jan 2025 13:00:07 +1000 Subject: [PATCH 1/6] :sparkles: combined modes --- src/Routes.tsx | 7 + src/pages/Acknowledgements/index.tsx | 15 + src/pages/GenerateKeys/Instructions.tsx | 46 ++ src/pages/GenerateKeys/index.tsx | 582 +++++++++++++----- src/pages/Landing/Hero.tsx | 2 +- .../SelectClientButtonsManual.tsx | 81 +++ .../SelectClientSectionManual.tsx | 120 ++++ src/pages/SelectClient/index.tsx | 251 +++++--- src/pages/SelectMode/index.tsx | 127 ++++ 9 files changed, 992 insertions(+), 239 deletions(-) create mode 100644 src/pages/GenerateKeys/Instructions.tsx create mode 100644 src/pages/SelectClient/SelectClientButtonsManual.tsx create mode 100644 src/pages/SelectClient/SelectClientSectionManual.tsx create mode 100644 src/pages/SelectMode/index.tsx diff --git a/src/Routes.tsx b/src/Routes.tsx index dfcfa17e..2455d4eb 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -26,6 +26,7 @@ import { import ScrollToTop from './utils/ScrollToTop'; import { Prysm } from './pages/Clients/Consensus/Prysm'; import { Geth } from './pages/Clients/Execution/Geth'; +import { SelectModePage } from './pages/SelectMode'; type RouteType = { path: string; @@ -39,6 +40,7 @@ export enum routesEnum { connectWalletPage = '/connect-wallet', generateKeysPage = '/generate-keys', acknowledgementPage = '/overview', + selectMode = '/select-mode', selectClient = '/select-client', summaryPage = '/summary', uploadValidatorPage = '/upload-deposit-data', @@ -89,6 +91,11 @@ const routes: RouteType[] = [ exact: true, component: AcknowledgementPage, }, + { + path: routesEnum.selectMode, + exact: true, + component: SelectModePage, + }, { path: routesEnum.summaryPage, exact: true, component: SummaryPage }, { path: routesEnum.uploadValidatorPage, diff --git a/src/pages/Acknowledgements/index.tsx b/src/pages/Acknowledgements/index.tsx index acf3e87f..7f5b2088 100644 --- a/src/pages/Acknowledgements/index.tsx +++ b/src/pages/Acknowledgements/index.tsx @@ -29,6 +29,7 @@ import { WorkflowPageTemplate } from '../../components/WorkflowPage/WorkflowPage import { Paper } from '../../components/Paper'; import { Accordion } from './Accordion'; import { AccordionItem } from './AccordionItem'; +import { ClientId, DispatchClientUpdate, updateClient } from '../../store/actions/clientActions'; interface OwnProps {} interface StateProps { @@ -39,6 +40,7 @@ interface StateProps { interface DispatchProps { dispatchAcknowledgementStateUpdate: DispatchAcknowledgementStateUpdateType; dispatchWorkflowUpdate: DispatchWorkflowUpdateType; + dispatchClientUpdate: DispatchClientUpdate; } type Props = StateProps & DispatchProps & OwnProps; @@ -47,6 +49,7 @@ const _AcknowledgementPage = ({ dispatchAcknowledgementStateUpdate, workflow, dispatchWorkflowUpdate, + dispatchClientUpdate }: Props): JSX.Element => { @@ -80,7 +83,13 @@ const _AcknowledgementPage = ({ } }; + const setClientFxn = (clientId: ClientId) => { + dispatchClientUpdate(clientId, 'execution'); + }; + const handleAccept = () => { + const isOneClick = sessionStorage.getItem('oneClick') === 'true'; + setClientFxn(isOneClick ? ClientId.STEREUM : ClientId.GETH); acknowledgementIdsArray.forEach((id) => { console.log(workflow); dispatchAcknowledgementStateUpdate(id, true); @@ -175,6 +184,12 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ dispatchAcknowledgementStateUpdate: (id, value) => dispatch(updateAcknowledgementState(id, value)), dispatchWorkflowUpdate: step => dispatch(updateWorkflow(step)), + dispatchClientUpdate: ( + clientId: ClientId, + ethClientType: 'execution' | 'consensus' + ) => { + dispatch(updateClient(clientId, ethClientType)); + }, }); export const AcknowledgementPage = connect< diff --git a/src/pages/GenerateKeys/Instructions.tsx b/src/pages/GenerateKeys/Instructions.tsx new file mode 100644 index 00000000..919ff0c7 --- /dev/null +++ b/src/pages/GenerateKeys/Instructions.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { keysTool } from './index'; +import { Paper } from '../../components/Paper'; +import { TextSelectionBox } from '../../components/TextSelectionBox'; +import { Option1 } from './Option1'; +import { Heading } from '../../components/Heading'; + +interface Props { + validatorCount: number | string; + withdrawalAddress: string; + os: 'mac' | 'linux' | 'windows'; + chosenTool: keysTool; + setChosenTool: (tool: keysTool) => void; +} + +export const Instructions = ({ + validatorCount, + withdrawalAddress, + os, + chosenTool, + setChosenTool, +}: Props) => { + const { formatMessage } = useIntl(); + return ( + + + + +
+ setChosenTool(keysTool.CLI)} + style={{ marginEnd: '20px' }} + > + {formatMessage({ defaultMessage: 'Download CLI app' })} + +
+
+ {chosenTool === keysTool.CLI && ( + + )} +
+
+ ); +}; diff --git a/src/pages/GenerateKeys/index.tsx b/src/pages/GenerateKeys/index.tsx index b3583be6..6f44a175 100644 --- a/src/pages/GenerateKeys/index.tsx +++ b/src/pages/GenerateKeys/index.tsx @@ -1,5 +1,6 @@ // Import libraries import React, { useEffect, useState, useMemo } from 'react'; +import BigNumber from 'bignumber.js'; import { Dispatch } from 'redux'; import { connect } from 'react-redux'; import styled from 'styled-components'; @@ -7,7 +8,11 @@ import { Box, CheckBox } from 'grommet'; import { FormattedMessage, useIntl } from 'react-intl'; import { toChecksumAddress } from 'ethereumjs-util'; // Components +import { Instructions } from './Instructions'; +import { NumberInput } from './NumberInput'; +import { OperatingSystemButtons } from './OperatingSystemButtons'; import { WorkflowPageTemplate } from '../../components/WorkflowPage/WorkflowPageTemplate'; +import { Alert } from '../../components/Alert'; import { Button } from '../../components/Button'; import { Heading } from '../../components/Heading'; import { Link } from '../../components/Link'; @@ -26,9 +31,16 @@ import { updateWorkflow, WorkflowStep, } from '../../store/actions/workflowActions'; +import { + IS_MAINNET, + PRICE_PER_VALIDATOR, + TICKER_NAME, +} from '../../utils/envVars'; import { StoreState } from '../../store/reducers'; // Utilities import { routeToCorrectWorkflowStep } from '../../utils/RouteToCorrectWorkflowStep'; +import instructions1 from '../../static/instructions_1.svg'; +import instructions2 from '../../static/instructions_2.svg'; // Images // Routes import { routesEnum } from '../../Routes'; @@ -40,6 +52,72 @@ export enum operatingSystem { 'WINDOWS', } +const osMapping: { [os: number]: 'mac' | 'linux' | 'windows' } = { + [operatingSystem.MAC]: 'mac', + [operatingSystem.LINUX]: 'linux', + [operatingSystem.WINDOWS]: 'windows', +}; + +export enum keysTool { + 'CLI', + 'GUI', + 'CLISOURCE', +} + +const AddressInputContainer = styled.div` + display: flex; + gap: 1rem; +`; + +const AddressInput = styled.input` + height: 50px; + flex: 1; + font-size: 18px; + line-height: 24px; + color: #444444; + padding-left: 10px; + box-sizing: border-box; + background-color: ${(p: any) => p.theme.gray.lightest}; + border-radius: ${(p: any) => p.theme.borderRadius}; + -webkit-appearance: textfield; + -moz-appearance: textfield; + appearance: textfield; + ::-webkit-inner-spin-button, + ::-webkit-outer-spin-button { + -webkit-appearance: none; + } + border: 1px solid #ddd; + display: inline-flex; + :focus { + outline: none; + } +`; + +const AddressIndicator = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + font-size: 2rem; +`; + +const Highlight = styled.span` + background: ${p => p.theme.green.medium}; +`; + +const InstructionImgContainer = styled.div` + height: 250px; + margin: 64px; + border-radius: 4px; + display: flex; + justify-content: center; +`; + +const NumValidatorContainer = styled.div` + display: flex; + margin-top: 20px; + gap: 50px; +`; + const ButtonContainer = styled.div` display: flex; justify-content: center; @@ -82,16 +160,30 @@ const _GenerateKeysPage = ({ workflow, }: Props): JSX.Element => { const { formatMessage } = useIntl(); + const [validatorCount, setValidatorCount] = useState(0); const [ mnemonicAcknowledgementChecked, setMnemonicAcknowledgementChecked, ] = useState(workflow > WorkflowStep.GENERATE_KEY_PAIRS); + const [chosenOs, setChosenOs] = useState( + operatingSystem.LINUX + ); const [withdrawalAddress, setWithdrawalAddress] = useState(''); + const defaultKeysTool = IS_MAINNET ? keysTool.CLI : keysTool.GUI; + const [chosenTool, setChosenTool] = useState(defaultKeysTool); + const onCheckboxClick = (e: any) => { setMnemonicAcknowledgementChecked(e.target.checked); }; + const handleAddressChange = (e: any) => { + // Only allow hexadecimal characters and 'x' (for 0x prefix) + const re = /[^0-9a-fx]/gi; + const value = e.target.value.replace(re, ''); + setWithdrawalAddress(value); + }; + const isValidWithdrawalAddress = useMemo( () => /^0x[0-9a-f]{40}$/i.test(withdrawalAddress), [withdrawalAddress] @@ -102,6 +194,12 @@ const _GenerateKeysPage = ({ setWithdrawalAddress(toChecksumAddress(withdrawalAddress)); }, [isValidWithdrawalAddress, withdrawalAddress]); + const addressIndicatorEmoji = useMemo(() => { + if (!withdrawalAddress) return '⬅'; + if (isValidWithdrawalAddress) return '✅'; + return '❌'; + }, [withdrawalAddress, isValidWithdrawalAddress]); + const handleSubmit = () => { if (workflow === WorkflowStep.GENERATE_KEY_PAIRS) { dispatchWorkflowUpdate(WorkflowStep.UPLOAD_VALIDATOR_FILE); @@ -112,156 +210,354 @@ const _GenerateKeysPage = ({ return routeToCorrectWorkflowStep(workflow); } + const isOneClick = sessionStorage.getItem('oneClick') === 'true'; + return ( - - - - - - - - - - - - - - - - - - - - - - - - - WagyuStep1 - - - - - - - - - WagyuStep2 - - - - - - - - - WagyuStep3 - - - + <> + {isOneClick ? ( + + + + - + - - - WagyuStep4 - - - - - - + + + + + + + + + + + + + + + + + WagyuStep1 + + + + + + + + + WagyuStep2 + + + + + + + + + WagyuStep3 + + + + + + + + + WagyuStep4 + + + + + + + + + WagyuStep5 + + + + + + + + + WagyuStep6 + + + + + + + + + WagyuStep7 + + + + + +

+ Progress to the next page to upload your deposit_data-xxxxxxxxxx.json to make the deposits for your validator keys. Please note that once the deposits are made, your validator may take up to 24 hours to become active. +

+

+ You will need the MetaMask (https://metamask.io/) extension installed with a wallet that contains the correct STRAX balance before you can progress to make deposits for your validator(s). +

+
+
+
+
+ + + + + + )} + /> + + + + +