mirror of
https://github.com/zhigang1992/xverse-web-extension.git
synced 2026-04-30 05:35:53 +08:00
implement auth and create wallet logic
This commit is contained in:
27
src/app/components/guards/auth.tsx
Normal file
27
src/app/components/guards/auth.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { StoreState } from '@stores/root/reducer';
|
||||
import { getEncryptedSeed } from '@utils/localStorage';
|
||||
import { ReactNode } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
interface AuthGuardProps {
|
||||
children: ReactNode,
|
||||
}
|
||||
|
||||
function AuthGuard({ children }: AuthGuardProps) {
|
||||
const encryptedSeedPhrase = getEncryptedSeed();
|
||||
const {
|
||||
isLocked,
|
||||
} = useSelector((state: StoreState) => ({
|
||||
...state.walletState,
|
||||
}));
|
||||
|
||||
if (encryptedSeedPhrase && isLocked) return <Navigate to="/login" />;
|
||||
|
||||
if (!encryptedSeedPhrase) return <Navigate to="/landing" />;
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export default AuthGuard;
|
||||
@@ -1,16 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const StyledHeader = styled.h1`
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
font-family: Satoshi-Regular;
|
||||
color: ${(props) => props.theme.colors.action.classic};
|
||||
`;
|
||||
|
||||
function Header():JSX.Element {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'common' });
|
||||
return <StyledHeader>{t('appName')}</StyledHeader>;
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -7,6 +7,8 @@ import LegalLinks from '@screens/legalLinks';
|
||||
import BackupWallet from '@screens/backupWallet';
|
||||
import CreateWalletSuccess from '@screens/createWalletSuccess';
|
||||
import CreatePassword from '@screens/createPassword';
|
||||
import AuthGuard from '@components/guards/auth';
|
||||
import Login from '@screens/login';
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
@@ -14,7 +16,7 @@ const router = createHashRouter([
|
||||
element: <ScreenContainer />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
path: 'landing',
|
||||
element: <Landing />,
|
||||
},
|
||||
{
|
||||
@@ -22,8 +24,12 @@ const router = createHashRouter([
|
||||
element: <Onboarding />,
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
element: <Home />,
|
||||
index: true,
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<Home />
|
||||
</AuthGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'legal',
|
||||
@@ -41,6 +47,10 @@ const router = createHashRouter([
|
||||
path: 'create-wallet-success',
|
||||
element: <CreateWalletSuccess />,
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
element: <Login />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -4,10 +4,10 @@ import PasswordIcon from '@assets/img/createPassword/Password.svg';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getStoreSeedPhraseRequestAction } from '@stores/actions/wallet/actionCreators';
|
||||
import NewPassword from './newPassword';
|
||||
import ConfirmPassword from './confirmPassword';
|
||||
import { StoreState } from '@stores/reducers/root';
|
||||
import { StoreState } from '@stores/root/reducer';
|
||||
import { storeWalletSeed } from '@core/wallet';
|
||||
|
||||
const Container = styled.div((props) => ({
|
||||
flex: 1,
|
||||
@@ -53,9 +53,7 @@ function CreatePassword(): JSX.Element {
|
||||
const [currentStepIndex, setCurrentStepIndex] = useState<number>(0);
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
seedPhrase,
|
||||
} = useSelector((state: StoreState) => ({
|
||||
const { seedPhrase } = useSelector((state: StoreState) => ({
|
||||
...state.walletState,
|
||||
}));
|
||||
|
||||
@@ -63,8 +61,8 @@ function CreatePassword(): JSX.Element {
|
||||
setCurrentStepIndex(1);
|
||||
};
|
||||
|
||||
const handleConfirmPassword = () => {
|
||||
dispatch(getStoreSeedPhraseRequestAction(seedPhrase, password));
|
||||
const handleConfirmPassword = async () => {
|
||||
await storeWalletSeed(seedPhrase, password);
|
||||
navigate('/create-wallet-success');
|
||||
};
|
||||
|
||||
@@ -79,9 +77,11 @@ function CreatePassword(): JSX.Element {
|
||||
return (
|
||||
<Container>
|
||||
<StepsContainer>
|
||||
{Array(2).fill(0).map((view, index) => (
|
||||
<StepDot active={index === currentStepIndex} key={index.toString() + 1} />
|
||||
))}
|
||||
{Array(2)
|
||||
.fill(0)
|
||||
.map((view, index) => (
|
||||
<StepDot active={index === currentStepIndex} key={index.toString() + 1} />
|
||||
))}
|
||||
</StepsContainer>
|
||||
<HeaderContainer>
|
||||
<img src={PasswordIcon} alt="passoword" />
|
||||
@@ -89,26 +89,23 @@ function CreatePassword(): JSX.Element {
|
||||
{currentStepIndex === 0 ? t('CREATE_PASSWORD_TITLE') : t('CONFIRM_PASSWORD_TITLE')}
|
||||
</HeaderText>
|
||||
</HeaderContainer>
|
||||
{
|
||||
currentStepIndex === 0 ? (
|
||||
<NewPassword
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
handleContinue={handleContinuePasswordCreation}
|
||||
handleBack={handleNewPasswordBack}
|
||||
/>
|
||||
) : (
|
||||
<ConfirmPassword
|
||||
password={password}
|
||||
confirmPassword={confirmPassword}
|
||||
setConfirmPassword={setConfirmPassword}
|
||||
handleContinue={handleConfirmPassword}
|
||||
handleBack={handleConfirmPasswordBack}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{currentStepIndex === 0 ? (
|
||||
<NewPassword
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
handleContinue={handleContinuePasswordCreation}
|
||||
handleBack={handleNewPasswordBack}
|
||||
/>
|
||||
) : (
|
||||
<ConfirmPassword
|
||||
password={password}
|
||||
confirmPassword={confirmPassword}
|
||||
setConfirmPassword={setConfirmPassword}
|
||||
handleContinue={handleConfirmPassword}
|
||||
handleBack={handleConfirmPasswordBack}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ function CreateWalletSuccess(): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleOpenWallet = () => {
|
||||
navigate('/home');
|
||||
navigate('/');
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,9 +2,9 @@ import styled from 'styled-components';
|
||||
import logo from '@assets/img/full_logo_vertical.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getGenerateWalletAction } from '@stores/actions/wallet/actionCreators';
|
||||
import { StoreState } from '@stores/reducers/root';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setWalletAction } from '@stores/wallet/actions/actionCreators';
|
||||
import { newWallet } from '@core/wallet';
|
||||
|
||||
const TopSectionContainer = styled.div({
|
||||
display: 'flex',
|
||||
@@ -69,9 +69,10 @@ function Landing(): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handlePressAction = () => {
|
||||
const handlePressAction = async () => {
|
||||
navigate('/onboarding');
|
||||
dispatch(getGenerateWalletAction());
|
||||
const wallet = await newWallet();
|
||||
dispatch(setWalletAction(wallet));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
|
||||
155
src/app/screens/login/index.tsx
Normal file
155
src/app/screens/login/index.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import logo from '@assets/img/full_logo_vertical.svg';
|
||||
import styled from 'styled-components';
|
||||
import Eye from '@assets/img/createPassword/Eye.svg';
|
||||
import EyeSlash from '@assets/img/createPassword/EyeSlash.svg';
|
||||
import { useState } from 'react';
|
||||
import { decryptSeedPhrase } from '@utils/encryptionUtils';
|
||||
import { getEncryptedSeed } from '@utils/localStorage';
|
||||
import { walletFromSeedPhrase } from '@core/wallet';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setWalletAction } from '@stores/wallet/actions/actionCreators';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const ScreenContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingLeft: props.theme.spacing(9),
|
||||
paddingRight: props.theme.spacing(9),
|
||||
}));
|
||||
|
||||
const AppVersion = styled.p((props) => ({
|
||||
...props.theme.body_xs,
|
||||
color: props.theme.colors.white['0'],
|
||||
textAlign: 'right',
|
||||
marginTop: props.theme.spacing(8),
|
||||
}));
|
||||
|
||||
const TopSectionContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: props.theme.spacing(50),
|
||||
marginBottom: props.theme.spacing(15),
|
||||
}));
|
||||
|
||||
const PasswordInputLabel = styled.h2((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
textAlign: 'left',
|
||||
}));
|
||||
|
||||
const PasswordInputContainer = styled.div((props) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
border: '1px solid #303354;',
|
||||
paddingLeft: props.theme.spacing(8),
|
||||
paddingRight: props.theme.spacing(8),
|
||||
borderRadius: props.theme.radius(1),
|
||||
marginTop: props.theme.spacing(4),
|
||||
}));
|
||||
|
||||
const PasswordInput = styled.input((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
height: 44,
|
||||
backgroundColor: props.theme.colors.background.elevation0,
|
||||
color: props.theme.colors.white['0'],
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
}));
|
||||
|
||||
const LandingTitle = styled.h1((props) => ({
|
||||
...props.theme.tile_text,
|
||||
paddingTop: props.theme.spacing(15),
|
||||
paddingLeft: props.theme.spacing(34),
|
||||
paddingRight: props.theme.spacing(34),
|
||||
color: props.theme.colors.white['200'],
|
||||
textAlign: 'center',
|
||||
}));
|
||||
|
||||
const VerifyButton = styled.button((props) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: props.theme.radius(1),
|
||||
backgroundColor: props.theme.colors.action.classic,
|
||||
color: props.theme.colors.white['0'],
|
||||
marginTop: props.theme.spacing(8),
|
||||
width: '100%',
|
||||
height: 44,
|
||||
}));
|
||||
|
||||
const ErrorMessage = styled.h2((props) => ({
|
||||
...props.theme.body_medium_m,
|
||||
textAlign: 'left',
|
||||
color: props.theme.colors.feedback.error,
|
||||
marginTop: props.theme.spacing(4),
|
||||
}));
|
||||
|
||||
function Login(): JSX.Element {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'LOGIN_SCREEN' });
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false);
|
||||
const [password, setPassword] = useState<string>('');
|
||||
const [error, setError] = useState<string>('');
|
||||
const [isVerifying, setIsVerifying] = useState(false);
|
||||
|
||||
const handleTogglePasswordView = () => {
|
||||
setIsPasswordVisible(!isPasswordVisible);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
if (error) {
|
||||
setError('');
|
||||
}
|
||||
setPassword(event.currentTarget.value);
|
||||
};
|
||||
|
||||
const handleVerifyPassword = async () => {
|
||||
setIsVerifying(true);
|
||||
const seed = getEncryptedSeed();
|
||||
try {
|
||||
if (seed) {
|
||||
const decrypted = await decryptSeedPhrase(seed, password);
|
||||
const wallet = await walletFromSeedPhrase(decrypted);
|
||||
dispatch(setWalletAction(wallet));
|
||||
setIsVerifying(false);
|
||||
navigate('/');
|
||||
}
|
||||
} catch (err) {
|
||||
setIsVerifying(false);
|
||||
setError(t('VERIFY_PASSWORD_ERROR'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScreenContainer>
|
||||
<AppVersion>V 1.0.0</AppVersion>
|
||||
<TopSectionContainer>
|
||||
<img src={logo} width={100} alt="logo" />
|
||||
<LandingTitle>{t('WELCOME_MESSAGE_FIRST_LOGIN')}</LandingTitle>
|
||||
</TopSectionContainer>
|
||||
<PasswordInputLabel>{t('PASSWORD_INPUT_LABEL')}</PasswordInputLabel>
|
||||
<PasswordInputContainer>
|
||||
<PasswordInput
|
||||
type={isPasswordVisible ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
placeholder={t('PASSWORD_INPUT_PLACEHOLDER')}
|
||||
/>
|
||||
<button type="button" onClick={handleTogglePasswordView} style={{ background: 'none' }}>
|
||||
<img src={isPasswordVisible ? Eye : EyeSlash} alt="show-password" height={24} />
|
||||
</button>
|
||||
</PasswordInputContainer>
|
||||
{error && <ErrorMessage>{error}</ErrorMessage>}
|
||||
<VerifyButton onClick={handleVerifyPassword}>
|
||||
{t('VERIFY_PASSWORD_BUTTON')}
|
||||
</VerifyButton>
|
||||
</ScreenContainer>
|
||||
);
|
||||
}
|
||||
export default Login;
|
||||
@@ -46,5 +46,13 @@
|
||||
"SCREEN_TITLE": "Wallet created successfully",
|
||||
"SCREEN_SUBTITLE": "Your new wallet has been created successfully, you can now connect to your wallet.",
|
||||
"OPEN_WALLET_BUTTON": "Open Wallet"
|
||||
},
|
||||
"LOGIN_SCREEN": {
|
||||
"WELCOME_MESSAGE_FIRST_LOGIN": "Welcome!",
|
||||
"WELCOME_MESSAGE": "Welcome back!",
|
||||
"PASSWORD_INPUT_LABEL": "Password",
|
||||
"PASSWORD_INPUT_PLACEHOLDER": "Enter your password",
|
||||
"VERIFY_PASSWORD_BUTTON": "Verify",
|
||||
"VERIFY_PASSWORD_ERROR": "Incorrect Password"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user