diff --git a/packages/adena-extension/src/assets/web/confirm-check.svg b/packages/adena-extension/src/assets/web/confirm-check.svg new file mode 100644 index 00000000..da9b1a9f --- /dev/null +++ b/packages/adena-extension/src/assets/web/confirm-check.svg @@ -0,0 +1,6 @@ + + + diff --git a/packages/adena-extension/src/common/constants/resource.constant.ts b/packages/adena-extension/src/common/constants/resource.constant.ts index 329db438..2bd6a921 100644 --- a/packages/adena-extension/src/common/constants/resource.constant.ts +++ b/packages/adena-extension/src/common/constants/resource.constant.ts @@ -1,2 +1,3 @@ export const ADENA_TERMS_PAGE = 'https://adena.app/terms' as const; export const ADENA_DOCS_PAGE = 'https://docs.adena.app' as const; +export const GNO_CLI_HELP_PAGE = 'https://docs.onbloc.xyz/docs/cli' as const; diff --git a/packages/adena-extension/src/components/atoms/rolling-number/index.tsx b/packages/adena-extension/src/components/atoms/rolling-number/index.tsx new file mode 100644 index 00000000..07e6a3a8 --- /dev/null +++ b/packages/adena-extension/src/components/atoms/rolling-number/index.tsx @@ -0,0 +1,88 @@ +import { WebFontType } from '@styles/theme'; +import React, { useEffect, useState } from 'react'; +import styled, { css, FlattenSimpleInterpolation } from 'styled-components'; +import { View } from '../base'; +import { WebText } from '../web-text'; + +export const StyledWrapper = styled(View) <{ active: boolean, height?: number }>` + width: fit-content; + height: ${({ height }): string => height ? `${height}px` : '1em'}; + flex-direction: column; + overflow: hidden; + + @keyframes rolling-animation { + from { + transform: translate(0, 0); + } + + to { + transform: translate(0, -100%); + } + } + + ${({ active }): FlattenSimpleInterpolation | string => active ? css` + & > * { + animation: rolling-animation 0.2s linear forwards; + } + ` : ''} +`; + + +export interface RollingNumberProps { + value: number; + height?: number; + type: WebFontType; + color?: string; + style?: React.CSSProperties; + textCenter?: boolean; +} + +const RollingNumber: React.FC = ({ + height, + value, + type, + color, + style, + textCenter, +}) => { + const [currentValue, setCurrentValue] = useState(value); + const [animated, setAnimated] = useState(false); + + useEffect(() => { + if (currentValue !== value) { + setAnimated(true); + } + }, [value]); + + useEffect(() => { + if (animated) { + setTimeout(() => { + setAnimated(false); + setCurrentValue(value); + }, 200); + } + }, [animated]); + + return ( + + + {currentValue} + + + {value} + + + ); +}; + +export default RollingNumber; \ No newline at end of file diff --git a/packages/adena-extension/src/components/atoms/rolling-number/rolling-number.spec.tsx b/packages/adena-extension/src/components/atoms/rolling-number/rolling-number.spec.tsx new file mode 100644 index 00000000..6f70a878 --- /dev/null +++ b/packages/adena-extension/src/components/atoms/rolling-number/rolling-number.spec.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { RecoilRoot } from 'recoil'; +import { ThemeProvider } from 'styled-components'; +import { render } from '@testing-library/react'; +import theme from '@styles/theme'; +import { GlobalWebStyle } from '@styles/global-style'; +import RollingNumber from '.'; + +describe('RollingNumber Component', () => { + it('RollingNumber render', () => { + + render( + + + + + + , + ); + }); +}); \ No newline at end of file diff --git a/packages/adena-extension/src/components/atoms/rolling-number/rolling-number.stories.tsx b/packages/adena-extension/src/components/atoms/rolling-number/rolling-number.stories.tsx new file mode 100644 index 00000000..d94e9eae --- /dev/null +++ b/packages/adena-extension/src/components/atoms/rolling-number/rolling-number.stories.tsx @@ -0,0 +1,15 @@ +import { Meta, StoryObj } from '@storybook/react'; +import RollingNumber from '.'; + +export default { + title: 'components/atoms/RollingNumber', + component: RollingNumber, +} as Meta; + +export const Default: StoryObj = { + args: { + value: 3, + type: 'body6', + color: '#FBC224', + }, +}; \ No newline at end of file diff --git a/packages/adena-extension/src/components/atoms/web-button/index.tsx b/packages/adena-extension/src/components/atoms/web-button/index.tsx index f2d797d4..ffba7c79 100644 --- a/packages/adena-extension/src/components/atoms/web-button/index.tsx +++ b/packages/adena-extension/src/components/atoms/web-button/index.tsx @@ -1,7 +1,7 @@ import React, { ButtonHTMLAttributes, ReactElement, ReactNode } from 'react'; import styled from 'styled-components'; -import { WebFontType } from '@styles/theme'; +import { getTheme, WebFontType } from '@styles/theme'; import IconRight from '@assets/web/chevron-right.svg'; import { WebImg } from '../web-img'; @@ -13,18 +13,18 @@ type WebButtonProps = { textType?: WebFontType; figure: 'primary' | 'secondary' | 'tertiary' | 'quaternary'; } & ( - | { text: string; rightIcon?: 'chevronRight' } - | { + | { text: string; rightIcon?: 'chevronRight' } + | { children: ReactNode; } -) & + ) & ButtonHTMLAttributes; const StyledButtonBase = styled.button<{ size: 'full' | 'large' | 'small' }>` cursor: pointer; border: transparent; border-radius: 14px; - padding: ${({ size }): string => (size === 'large' ? '12px 16px 16px' : '8px 24px')}; + padding: ${({ size }): string => (size === 'large' ? '12px 16px 16px' : '8px 16px')}; display: flex; flex-direction: row; width: ${({ size }): string => (size === 'full' ? '100%' : 'auto')}; @@ -37,10 +37,12 @@ const StyledButtonBase = styled.button<{ size: 'full' | 'large' | 'small' }>` `; const StyledButtonPrimary = styled(StyledButtonBase)` + color ${getTheme('webNeutral', '_100')}; outline: 1px solid rgba(255, 255, 255, 0.4); background: linear-gradient(180deg, #0059ff 0%, #004bd6 100%); :hover { + color ${getTheme('webNeutral', '_100')}; outline: 2px solid rgba(255, 255, 255, 0.4); background: linear-gradient(180deg, #0059ff 0%, #004bd6 100%); box-shadow: 0px 0px 24px 0px rgba(0, 89, 255, 0.32), 0px 1px 3px 0px rgba(0, 0, 0, 0.1), @@ -48,6 +50,7 @@ const StyledButtonPrimary = styled(StyledButtonBase)` } :active { + color ${getTheme('webNeutral', '_100')}; outline: 2px solid rgba(255, 255, 255, 0.4); background: linear-gradient(180deg, #0059ff 0%, #004bd6 100%); box-shadow: 0px 0px 24px 0px rgba(0, 89, 255, 0.32), 0px 0px 0px 3px rgba(0, 89, 255, 0.16), @@ -56,31 +59,37 @@ const StyledButtonPrimary = styled(StyledButtonBase)` `; const StyledButtonSecondary = styled(StyledButtonBase)` + color: #ADCAFF; outline: 1px solid rgba(122, 169, 255, 0.24); background: rgba(0, 89, 255, 0.16); :hover { + color: #ADCAFF; outline: 2px solid rgba(122, 169, 255, 0.24); background: rgba(0, 89, 255, 0.2); box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.06); } :active { + color: #7AA9FF; outline: 2px solid rgba(122, 169, 255, 0.24); background: rgba(0, 89, 255, 0.2); } `; const StyledButtonTertiary = styled(StyledButtonBase)` + color ${getTheme('webNeutral', '_300')}; outline: 1px solid rgba(188, 197, 214, 0.16); background: rgba(188, 197, 214, 0.04); :hover { + color ${getTheme('webNeutral', '_300')}; outline: 2px solid rgba(188, 197, 214, 0.16); background: rgba(188, 197, 214, 0.06); } :active { + color ${getTheme('webNeutral', '_100')}; outline: 1px solid rgba(188, 197, 214, 0.24); background: rgba(188, 197, 214, 0.04); box-shadow: 0px 0px 16px 0px rgba(255, 255, 255, 0.04), diff --git a/packages/adena-extension/src/components/atoms/web-input/index.tsx b/packages/adena-extension/src/components/atoms/web-input/index.tsx index acc852bb..0a92fd4c 100644 --- a/packages/adena-extension/src/components/atoms/web-input/index.tsx +++ b/packages/adena-extension/src/components/atoms/web-input/index.tsx @@ -15,12 +15,13 @@ export const WebInput = styled.input` border: 1px solid; padding: 12px 16px; border-color: ${({ error, theme }): string => (error ? theme.webError._200 : theme.webNeutral._800)}; - background-color: ${({ error, theme }): string => (error ? theme.webError._300 : 'transparent')}; + background-color: ${({ error, theme }): string => (error ? theme.webError._300 : theme.webNeutral._900)}; ::placeholder { color: ${getTheme('webNeutral', '_700')}; } :focus { + background-color: ${({ error, theme }): string => (error ? theme.webError._300 : theme.webInput._100)}; box-shadow: 0px 0px 0px 3px rgba(255, 255, 255, 0.04), 0px 1px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.06); } diff --git a/packages/adena-extension/src/components/atoms/web-text/index.tsx b/packages/adena-extension/src/components/atoms/web-text/index.tsx index 3ed3a022..95c73fde 100644 --- a/packages/adena-extension/src/components/atoms/web-text/index.tsx +++ b/packages/adena-extension/src/components/atoms/web-text/index.tsx @@ -1,18 +1,18 @@ -import React, { ReactElement } from 'react'; +import React, { CSSProperties, ReactElement } from 'react'; import styled, { FlattenSimpleInterpolation } from 'styled-components'; -import { WebFontType, getTheme, webFonts } from '@styles/theme'; +import { WebFontType, webFonts } from '@styles/theme'; type FormTextProps = { type: WebFontType; children: string | number; - color?: string; + color?: CSSProperties['color']; style?: React.CSSProperties; textCenter?: boolean; }; -const StyledContainer = styled.div<{ type: WebFontType }>` - color: ${getTheme('webNeutral', '_100')}; +const StyledContainer = styled.div<{ type: WebFontType, color?: CSSProperties['color']; }>` + color: ${({ color }): CSSProperties['color'] => color ? color : '#FAFCFF'}; ${({ type }): FlattenSimpleInterpolation => webFonts[type]} white-space: pre-wrap; `; diff --git a/packages/adena-extension/src/components/molecules/terms-checkbox/index.tsx b/packages/adena-extension/src/components/molecules/terms-checkbox/index.tsx index 39d33fd4..905f470a 100644 --- a/packages/adena-extension/src/components/molecules/terms-checkbox/index.tsx +++ b/packages/adena-extension/src/components/molecules/terms-checkbox/index.tsx @@ -29,7 +29,7 @@ const Wrapper = styled.div<{ margin?: string }>` const Label = styled.label<{ checkboxPos: CheckboxPos }>` ${mixins.flex({ direction: 'row', justify: 'flex-start' })}; position: relative; - padding-left: 32px; + padding-left: 28px; cursor: pointer; &:before { ${({ checkboxPos }): CSSProp => @@ -54,6 +54,7 @@ const Label = styled.label<{ checkboxPos: CheckboxPos }>` `; const Input = styled.input` + display: none; &[type='checkbox'] { width: 0px; height: 0px; diff --git a/packages/adena-extension/src/components/pages/web/loading-accounts/index.tsx b/packages/adena-extension/src/components/pages/web/loading-accounts/index.tsx new file mode 100644 index 00000000..e41e5bff --- /dev/null +++ b/packages/adena-extension/src/components/pages/web/loading-accounts/index.tsx @@ -0,0 +1,35 @@ +import { ReactElement } from 'react'; +import styled, { useTheme } from 'styled-components'; + +import AnimationLoadingAccount from '@assets/web/loading-account-idle.gif'; + +import { View, WebImg, WebText } from '@components/atoms'; + +const StyledContainer = styled(View)` + row-gap: 24px; + align-items: center; +`; + +const StyledMessageBox = styled(View)` + row-gap: 16px; +`; + +const WebLoadingAccounts: React.FC = (): ReactElement => { + const theme = useTheme(); + + return ( + + + + + Loading Accounts + + + We’re loading accounts. This will take a few seconds... + + + + ); +}; + +export default WebLoadingAccounts; diff --git a/packages/adena-extension/src/components/pages/web/main-header/index.tsx b/packages/adena-extension/src/components/pages/web/main-header/index.tsx index e7ea28dd..3fd65377 100644 --- a/packages/adena-extension/src/components/pages/web/main-header/index.tsx +++ b/packages/adena-extension/src/components/pages/web/main-header/index.tsx @@ -19,6 +19,10 @@ const StyledDot = styled(View) <{ selected: boolean }>` selected ? theme.webPrimary._100 : 'rgba(0, 89, 255, 0.32)'}; `; +const StyledEmpty = styled(View)` + width: 24px; +`; + export type WebMainHeaderProps = { stepLength: number; onClickGoBack: () => void; @@ -47,7 +51,7 @@ export const WebMainHeader = ({ ))} )} - + ); }; diff --git a/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts b/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts index 156e4621..4740783a 100644 --- a/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts +++ b/packages/adena-extension/src/hooks/web/common/use-create-password-screen.ts @@ -14,12 +14,15 @@ import { useAdenaContext } from '@hooks/use-context'; export type UseCreatePasswordScreenReturn = { passwordState: { value: string; + confirm: boolean; onChange: (e: React.ChangeEvent) => void; + errorMessage: string; error: boolean; ref: React.RefObject; }; confirmPasswordState: { value: string; + confirm: boolean; onChange: (e: React.ChangeEvent) => void; error: boolean; }; @@ -48,11 +51,19 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { const [isConfirmPasswordError, setIsConfirmPasswordError] = useState(false); const { password, confirmPassword } = inputs; const [errorMessage, setErrorMessage] = useState(''); + const [passwordErrorMessage, setPasswordErrorMessage] = useState(''); const disabledCreateButton = useMemo(() => { return !terms || !password || !confirmPassword; }, [terms, password, confirmPassword]); + const confirmPasswordLength = (password: string): boolean => { + if (password.length < 8 || password.length > 256) { + return false; + } + return true; + }; + const _validateConfirmPassword = (validationPassword?: boolean): boolean => { try { if (validateNotMatchConfirmPassword(password, confirmPassword)) return true; @@ -82,7 +93,7 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { console.log(error); setIsPasswordError(true); if (error instanceof PasswordValidationError) { - setErrorMessage(error.message); + setPasswordErrorMessage(error.message); } } return false; @@ -148,6 +159,7 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { setIsPasswordError(false); setIsConfirmPasswordError(false); setErrorMessage(''); + setPasswordErrorMessage(''); }, [password, confirmPassword]); useEffect(() => { @@ -159,12 +171,15 @@ export const useCreatePasswordScreen = (): UseCreatePasswordScreenReturn => { return { passwordState: { value: password, + confirm: confirmPasswordLength(password), onChange: onChangePassword, error: isPasswordError, + errorMessage: passwordErrorMessage, ref: inputRef, }, confirmPasswordState: { value: confirmPassword, + confirm: confirmPasswordLength(confirmPassword), onChange: onChangePassword, error: isConfirmPasswordError, }, diff --git a/packages/adena-extension/src/hooks/web/questionnaire/use-questionnaire-screen.ts b/packages/adena-extension/src/hooks/web/questionnaire/use-questionnaire-screen.ts index c82ae437..2747efe0 100644 --- a/packages/adena-extension/src/hooks/web/questionnaire/use-questionnaire-screen.ts +++ b/packages/adena-extension/src/hooks/web/questionnaire/use-questionnaire-screen.ts @@ -38,7 +38,7 @@ export const questionnaireStep: Record< }; const useQuestionnaireScreen = (): UseQuestionnaireScreenReturn => { - const { navigate, params } = useAppNavigate(); + const { navigate, goBack, params } = useAppNavigate(); const { doneQuestionnaire } = useQuestionnaire(); const [questionnaireState, setQuestionnaireState] = useState('INIT'); const [questionIndex, setQuestionIndex] = useState(1); @@ -78,8 +78,12 @@ const useQuestionnaireScreen = (): UseQuestionnaireScreenReturn => { navigate(callbackPath, { state: { doneQuestionnaire: false } }); return; } - if (questionnaireState === 'QUESTION' && questionIndex > 1) { - setQuestionIndex(questionIndex - 1); + if (questionnaireState === 'QUESTION') { + if (questionIndex > 1) { + setQuestionIndex(questionIndex - 1); + } else { + goBack(); + } return; } setQuestionIndex(1); diff --git a/packages/adena-extension/src/hooks/web/setup-airgap/use-setup-airgap-screen.ts b/packages/adena-extension/src/hooks/web/setup-airgap/use-setup-airgap-screen.ts index 16164be1..6ef138b1 100644 --- a/packages/adena-extension/src/hooks/web/setup-airgap/use-setup-airgap-screen.ts +++ b/packages/adena-extension/src/hooks/web/setup-airgap/use-setup-airgap-screen.ts @@ -6,6 +6,7 @@ import useAppNavigate from '@hooks/use-app-navigate'; import { useAdenaContext, useWalletContext } from '@hooks/use-context'; import { useCurrentAccount } from '@hooks/use-current-account'; import { RoutePath } from '@types'; +import { useLoadAccounts } from '@hooks/use-load-accounts'; export type UseSetupAirgapScreenReturn = { address: string; @@ -45,6 +46,7 @@ const useSetupAirgapScreen = (): UseSetupAirgapScreenReturn => { const { navigate } = useAppNavigate(); const { updateWallet } = useWalletContext(); const { walletService } = useAdenaContext(); + const { accounts } = useLoadAccounts(); const [setupAirgapState, setSetupAirgapState] = useState('INIT'); const [address, setAddress] = useState(''); const [errorMessage, setErrorMessage] = useState(null); @@ -72,12 +74,24 @@ const useSetupAirgapScreen = (): UseSetupAirgapScreenReturn => { return false; }, [address]); - const confirmAddress = useCallback(() => { + const _existsAddress = useCallback(async () => { + return Promise.all(accounts.map((account) => account.getAddress('g'))).then((addresses) => + addresses.includes(address), + ); + }, [accounts, address]); + + const confirmAddress = useCallback(async () => { if (!_validateAddress()) { const errorMessage = 'Invalid address'; setErrorMessage(errorMessage); return; } + const existAddress = await _existsAddress(); + if (existAddress) { + const errorMessage = 'Address already synced'; + setErrorMessage(errorMessage); + return; + } setSetupAirgapState('COMPLETE'); }, [_validateAddress]); diff --git a/packages/adena-extension/src/hooks/web/use-account-import-screen.ts b/packages/adena-extension/src/hooks/web/use-account-import-screen.ts index d1823867..54cdfd45 100644 --- a/packages/adena-extension/src/hooks/web/use-account-import-screen.ts +++ b/packages/adena-extension/src/hooks/web/use-account-import-screen.ts @@ -1,9 +1,8 @@ -import { useCallback, useState } from 'react'; -import { Wallet, PrivateKeyKeyring, SingleAccount } from 'adena-module'; +import { useCallback, useMemo, useState } from 'react'; +import { Wallet, PrivateKeyKeyring, SingleAccount, Account, Keyring } from 'adena-module'; import { RoutePath } from '@types'; import useAppNavigate from '@hooks/use-app-navigate'; -import { useQuery } from '@tanstack/react-query'; import { useWalletContext } from '@hooks/use-context'; import { useCurrentAccount } from '@hooks/use-current-account'; import useQuestionnaire from './use-questionnaire'; @@ -12,19 +11,20 @@ export type UseAccountImportReturn = { isValidForm: boolean; errMsg: string; privateKey: string; - setPrivateKey: React.Dispatch>; + setPrivateKey: (privateKey: string) => void; step: AccountImportStateType; setStep: React.Dispatch>; accountImportStepNo: { INIT: number; SET_PRIVATE_KEY: number; + LOADING: number; }; stepLength: number; onClickGoBack: () => void; onClickNext: () => void; }; -export type AccountImportStateType = 'INIT' | 'SET_PRIVATE_KEY'; +export type AccountImportStateType = 'INIT' | 'SET_PRIVATE_KEY' | 'LOADING'; const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImportReturn => { const { navigate, params } = useAppNavigate(); @@ -39,43 +39,50 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor const [privateKey, setPrivateKey] = useState(''); const [errMsg, setErrMsg] = useState(''); - const { data: keyring } = useQuery( - ['keyring', privateKey], - async () => { - setErrMsg(''); - if (privateKey) { - const _privateKey = privateKey.replace('0x', ''); - const regExp = /[0-9A-Fa-f]{64}/g; - if (_privateKey.length !== 64 || !_privateKey.match(regExp)) { - setErrMsg('Invalid private key'); - return; - } - const _keyring = await PrivateKeyKeyring.fromPrivateKeyStr(privateKey); - const account = await SingleAccount.createBy(_keyring, wallet.nextAccountName); - const storedAccount = wallet.accounts.find( - (_account) => JSON.stringify(_account.publicKey) === JSON.stringify(account.publicKey), - ); - if (storedAccount) { - setErrMsg('Private key already registered'); - return; - } - - return _keyring; - } - }, - { - enabled: !!privateKey, - }, - ); - - const isValidForm = !!privateKey && !!keyring && !errMsg; - const stepLength = ableToSkipQuestionnaire ? 3 : 4; const accountImportStepNo = { INIT: 0, SET_PRIVATE_KEY: ableToSkipQuestionnaire ? 1 : 2, + LOADING: ableToSkipQuestionnaire ? 1 : 2, }; + const isValidForm = useMemo(() => { + return !!privateKey || !errMsg; + }, [privateKey]); + + const changePrivateKey = useCallback((privateKey: string) => { + setErrMsg(''); + setPrivateKey(privateKey); + }, []); + + const makePrivateKeyAccountAndKeyring = useCallback(async (): Promise<{ + account: Account; + keyring: Keyring; + } | null> => { + setErrMsg(''); + if (!privateKey) { + return null; + } + + const keyring = await PrivateKeyKeyring.fromPrivateKeyStr(privateKey).catch(() => null); + if (keyring === null) { + setErrMsg('Invalid private key'); + return null; + } + + const account = await SingleAccount.createBy(keyring, wallet.nextAccountName); + const address = await account.getAddress('g'); + const storedAddresses = await Promise.all( + wallet.accounts.map((account) => account.getAddress('g')), + ); + const existAddress = storedAddresses.includes(address); + if (existAddress) { + setErrMsg('Private key already registered'); + return null; + } + return { account, keyring }; + }, [wallet, privateKey]); + const onClickGoBack = useCallback(() => { if (step === 'INIT') { navigate(RoutePath.WebAdvancedOption); @@ -95,9 +102,13 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor }, }); } - } else if (step === 'SET_PRIVATE_KEY' && keyring) { - const account = await SingleAccount.createBy(keyring, wallet.nextAccountName); - + } else if (step === 'SET_PRIVATE_KEY') { + const result = await makePrivateKeyAccountAndKeyring(); + if (!result) { + return; + } + setStep('LOADING'); + const { account, keyring } = result; account.index = wallet.lastAccountIndex + 1; account.name = `Account ${account.index}`; const clone = wallet.clone(); @@ -108,13 +119,13 @@ const useAccountImportScreen = ({ wallet }: { wallet: Wallet }): UseAccountImpor await changeCurrentAccount(account); navigate(RoutePath.WebAccountAddedComplete); } - }, [step, privateKey, ableToSkipQuestionnaire, keyring]); + }, [step, privateKey, ableToSkipQuestionnaire, makePrivateKeyAccountAndKeyring]); return { isValidForm, errMsg, privateKey, - setPrivateKey, + setPrivateKey: changePrivateKey, step, setStep, accountImportStepNo, diff --git a/packages/adena-extension/src/pages/web/account-import-screen/index.tsx b/packages/adena-extension/src/pages/web/account-import-screen/index.tsx index f17d028e..cb1bb4c4 100644 --- a/packages/adena-extension/src/pages/web/account-import-screen/index.tsx +++ b/packages/adena-extension/src/pages/web/account-import-screen/index.tsx @@ -11,12 +11,17 @@ import useAppNavigate from '@hooks/use-app-navigate'; import { RoutePath } from '@types'; import SensitiveInfoStep from '@components/pages/web/sensitive-info-step'; import { ADENA_DOCS_PAGE } from '@common/constants/resource.constant'; +import WebLoadingAccounts from '@components/pages/web/loading-accounts'; const HasWallet = ({ wallet }: { wallet: Wallet }): ReactElement => { const useAccountImportScreenReturn = useAccountImportScreen({ wallet }); const { step, onClickGoBack, stepLength, accountImportStepNo, onClickNext } = useAccountImportScreenReturn; + if (step === 'LOADING') { + return ; + } + return ( <> ` +const StyledItem = styled(Row) <{ error: boolean }>` position: relative; overflow: hidden; height: 40px; @@ -66,7 +66,7 @@ const SetPrivateKeyStep = ({ - Enter a seed phrase or your private key to import your existing wallet. + Enter a private key to import your existing wallet. @@ -75,6 +75,7 @@ const SetPrivateKeyStep = ({ { setPrivateKey(value); }} diff --git a/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx b/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx index 2cc3d88c..28135160 100644 --- a/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx +++ b/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx @@ -58,9 +58,9 @@ const AdvancedOptionScreen = (): ReactElement => { }} style={{ width: 176 }} > - + - Create New Wallet + Create New Wallet { } }} > - + - Import Existing Wallet + Import Existing Wallet { navigate(RoutePath.WebGoogleLogin); }} > - + - Sign In With Google + Sign In With Google diff --git a/packages/adena-extension/src/pages/web/create-password-screen/index.tsx b/packages/adena-extension/src/pages/web/create-password-screen/index.tsx index 33b1139c..363bac5d 100644 --- a/packages/adena-extension/src/pages/web/create-password-screen/index.tsx +++ b/packages/adena-extension/src/pages/web/create-password-screen/index.tsx @@ -3,12 +3,14 @@ import styled, { useTheme } from 'styled-components'; import { WebInput, - ErrorText, WebMain, View, WebText, Pressable, WebButton, + Row, + WebImg, + WebErrorText, } from '@components/atoms'; import { TermsCheckbox } from '@components/molecules'; import { WebMainHeader } from '@components/pages/web/main-header'; @@ -18,13 +20,32 @@ import useLink from '@hooks/use-link'; import useAppNavigate from '@hooks/use-app-navigate'; import { RoutePath } from '@types'; +import IconConfirm from '@assets/web/confirm-check.svg'; + +const StyledContainer = styled(View)` + height: 330px; + row-gap: 24px; + align-items: flex-start; +`; + const StyledMessageBox = styled(View)` + width: 100%; row-gap: 16px; `; -const StyledInputBox = styled(View)` +const StyledInputContainer = styled(View)` row-gap: 16px; - width: 384px; + width: 100%; +`; + +const StyledInputBox = styled(View)` + row-gap: 12px; + width: 100%; +`; + +const StyledInputWrapper = styled(Row)` + width: 416px; + gap: 12px; `; const CreatePasswordScreen = (): JSX.Element => { @@ -39,61 +60,77 @@ const CreatePasswordScreen = (): JSX.Element => { }, [openLink]); return ( - + - - Create a password - - This will be used to unlock your wallet. - - - - - - + + Create a password + + This will be used to unlock your wallet. + + + + + + + + {passwordState.confirm && ()} + + {passwordState.errorMessage && } + + + + + + {confirmPasswordState.confirm && ()} + + {errorMessage && } + + + + + + Terms of Use. + + + + - {errorMessage && } - - - - - Terms of Use. - - - - + ); }; diff --git a/packages/adena-extension/src/pages/web/google-login-screen/request-fail.tsx b/packages/adena-extension/src/pages/web/google-login-screen/request-fail.tsx index a17d01b9..60e23c5a 100644 --- a/packages/adena-extension/src/pages/web/google-login-screen/request-fail.tsx +++ b/packages/adena-extension/src/pages/web/google-login-screen/request-fail.tsx @@ -39,7 +39,7 @@ const GoogleLoginRequestFail: React.FC = ({ Login Failed @@ -49,10 +49,11 @@ const GoogleLoginRequestFail: React.FC = ({ ); diff --git a/packages/adena-extension/src/pages/web/google-login-screen/request.tsx b/packages/adena-extension/src/pages/web/google-login-screen/request.tsx index 215c563f..202f23c7 100644 --- a/packages/adena-extension/src/pages/web/google-login-screen/request.tsx +++ b/packages/adena-extension/src/pages/web/google-login-screen/request.tsx @@ -45,7 +45,7 @@ const GoogleLoginRequest: React.FC = ({ Waiting for Google Login @@ -55,7 +55,7 @@ const GoogleLoginRequest: React.FC = ({ { }} style={{ width: 204 }} > - + - Connect Hardware Wallet + Connect Hardware Wallet { style={{ width: 204 }} onClick={moveSetupAirgapScreen} > - + - Set Up Airgap Account + Set Up Airgap Account { }} style={{ width: 204 }} > - + - Advanced Options + Advanced Options diff --git a/packages/adena-extension/src/pages/web/questionnaire-screen/question.tsx b/packages/adena-extension/src/pages/web/questionnaire-screen/question.tsx index d05e2823..41d7c6a3 100644 --- a/packages/adena-extension/src/pages/web/questionnaire-screen/question.tsx +++ b/packages/adena-extension/src/pages/web/questionnaire-screen/question.tsx @@ -6,6 +6,7 @@ import { Question } from '@types'; import { WebMainHeader } from '@components/pages/web/main-header'; import WebAnswerButton from '@components/molecules/web-answer-button/web-answer-button'; import IconInfo from '@assets/web/info.svg'; +import RollingNumber from '@components/atoms/rolling-number'; const StyledContainer = styled(View)` width: 416px; @@ -37,6 +38,12 @@ const StyledWarningTextWrapper = styled(Row)` align-items: center; `; +const StyledWarningDescriptionWrapper = styled(Row)` + margin-left: 1px; + justify-content: flex-start; + align-items: center; +`; + interface QuestionnaireQuestionProps { question: Question | null; nextQuestion: () => void; @@ -49,15 +56,26 @@ const QuestionnaireQuestion: React.FC = ({ backStep, }) => { const theme = useTheme(); - const [selectedAnswers, setSelectedAnswers] = useState([]); + const [selectedAnswerIndex, setSelectedAnswerIndex] = useState(null); const [retryTime, setRetryTime] = useState(0); - const isWarning = useMemo(() => { + const selectedAnswer = useMemo(() => { if (!question) { + return null; + } + const selectedAnswer = question.answers.find((_, index) => selectedAnswerIndex === index); + if (!selectedAnswer) { + return null; + } + return selectedAnswer; + }, [question, selectedAnswerIndex]) + + const isWarning = useMemo(() => { + if (!selectedAnswer) { return false; } - return question.answers.findIndex((answer, index) => answer.correct === false && selectedAnswers.includes(index)) > 0; - }, [question, selectedAnswers]) + return selectedAnswer.correct === false; + }, [selectedAnswer]) const isRetry = useMemo(() => { return retryTime > 0; @@ -67,9 +85,8 @@ const QuestionnaireQuestion: React.FC = ({ if (!question) { return false; } - const correctAnswer = question.answers.find((answer, index) => answer.correct && selectedAnswers.includes(index)); - return correctAnswer ? true : false; - }, [question, selectedAnswers]); + return selectedAnswer?.correct === true; + }, [question, selectedAnswer]); const questionTitle = useMemo(() => { if (!question) { @@ -89,22 +106,20 @@ const QuestionnaireQuestion: React.FC = ({ if (retryTime > 0) { return; } - if (!selectedAnswers.includes(index)) { - setSelectedAnswers([...selectedAnswers, index]); - setRetryTime(3); - } - }, [retryTime, selectedAnswers]); + setSelectedAnswerIndex(index); + setRetryTime(3); + }, [retryTime]); const onClickNextButton = useCallback(() => { if (availableNext) { nextQuestion(); } - setSelectedAnswers([]); + setSelectedAnswerIndex(null); setRetryTime(0); }, [availableNext]); const onClickBack = useCallback(() => { - setSelectedAnswers([]); + setSelectedAnswerIndex(null); setRetryTime(0); backStep() }, [backStep]); @@ -142,7 +157,7 @@ const QuestionnaireQuestion: React.FC = ({ key={index} correct={answer.correct} answer={answer.answer} - selected={selectedAnswers.includes(index)} + selected={selectedAnswerIndex === index} onClick={(): void => selectAnswer(index)} /> ))} @@ -155,7 +170,12 @@ const QuestionnaireQuestion: React.FC = ({ {isRetry ? 'Incorrect answer! Please try again in ' : 'Incorrect answer! Please try again.'} - {isRetry ? `${retryTime} seconds.` : ''} + {isRetry && ( + + + seconds. + + )} )} diff --git a/packages/adena-extension/src/pages/web/setup-airgap-screen/complete.tsx b/packages/adena-extension/src/pages/web/setup-airgap-screen/complete.tsx index 30e26016..119424c5 100644 --- a/packages/adena-extension/src/pages/web/setup-airgap-screen/complete.tsx +++ b/packages/adena-extension/src/pages/web/setup-airgap-screen/complete.tsx @@ -8,6 +8,7 @@ import IconCheck from '@assets/web/web-check-circle.svg'; const StyledContainer = styled(View)` row-gap: 24px; + height: 350px; `; const StyledMessageBox = styled(View)` diff --git a/packages/adena-extension/src/pages/web/setup-airgap-screen/enter-address.tsx b/packages/adena-extension/src/pages/web/setup-airgap-screen/enter-address.tsx index 80b7650f..1b9f6189 100644 --- a/packages/adena-extension/src/pages/web/setup-airgap-screen/enter-address.tsx +++ b/packages/adena-extension/src/pages/web/setup-airgap-screen/enter-address.tsx @@ -7,6 +7,7 @@ import IconAirgap from '@assets/web/airgap-green.svg'; const StyledContainer = styled(View)` width: 416px; + height: 350px; row-gap: 24px; `; @@ -26,7 +27,7 @@ interface StyledInputProps { placeholder: string; onChange: (event: React.ChangeEvent) => void; } -const StyledInput = styled(WebInput)` +const StyledInput = styled(WebInput) ` border: ${({ theme, error }): string => (error ? `1px solid ${theme.webError._200}` : '')}; background-color: ${({ theme, error }): string => (error ? theme.webError._300 : '')}; @@ -79,6 +80,7 @@ const SetupAirgapEnterAddress: React.FC = ({ type='text' name='address' placeholder='Account Address' + autoComplete='off' onChange={onChangeAddressInput} /> {errorMessage && }