Skip to content

Commit

Permalink
wallet-ext: create & import wallet flow
Browse files Browse the repository at this point in the history
* force extension to open in main window when wallet is not initialized
* create and import mnemonic
* after creating a wallet show the mnemonic to the user to save for backup
* NOTE: mnemonic is not encrypted for now
  • Loading branch information
pchrysochoidis committed May 17, 2022
1 parent c8505b4 commit d3ff5d6
Show file tree
Hide file tree
Showing 34 changed files with 756 additions and 16 deletions.
5 changes: 3 additions & 2 deletions wallet/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

import Browser from 'webextension-polyfill';

import { openInNewTab } from '_shared/utils';

Browser.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
const url = Browser.runtime.getURL('ui.html');
Browser.tabs.create({ url });
openInNewTab();
}
});
2 changes: 1 addition & 1 deletion wallet/src/manifest/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"permissions": ["storage"],
"action": {
"default_popup": "ui.html"
"default_popup": "ui.html?type=popup"
},
"host_permissions": [],
"icons": {
Expand Down
9 changes: 9 additions & 0 deletions wallet/src/shared/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import Browser from 'webextension-polyfill';

export function openInNewTab() {
const url = Browser.runtime.getURL('ui.html');
return Browser.tabs.create({ url });
}
9 changes: 9 additions & 0 deletions wallet/src/ui/app/components/loading/LoadingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

// TODO: better loading indicator
const LoadingIndicator = () => {
return <span>Loading...</span>;
};

export default LoadingIndicator;
19 changes: 19 additions & 0 deletions wallet/src/ui/app/components/loading/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { memo } from 'react';

import LoadingIndicator from './LoadingIndicator';

import type { ReactNode } from 'react';

type LoadingProps = {
loading: boolean;
children: ReactNode | ReactNode[];
};

const Loading = ({ loading, children }: LoadingProps) => {
return loading ? <LoadingIndicator /> : <>{children}</>;
};

export default memo(Loading);
29 changes: 29 additions & 0 deletions wallet/src/ui/app/components/logo/Logo.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.container {
display: inline-flex;
}

.image {
background-image: url('~_images/sui-icon.png');
background-repeat: no-repeat;
background-size: contain;

&.normal {
width: 30px;
height: 30px;
}

&.big {
width: 60px;
height: 60px;
}

&.bigger {
width: 90px;
height: 90px;
}

&.huge {
width: 120px;
height: 120px;
}
}
22 changes: 22 additions & 0 deletions wallet/src/ui/app/components/logo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import classnames from 'classnames/bind';

import st from './Logo.module.scss';

const cl = classnames.bind(st);

type LogoProps = {
size?: 'normal' | 'big' | 'bigger' | 'huge';
};

const Logo = ({ size = 'normal' }: LogoProps) => {
return (
<div className={cl('container')}>
<span className={cl('image', size)} />
</div>
);
};

export default Logo;
7 changes: 7 additions & 0 deletions wallet/src/ui/app/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

export { default as useAppDispatch } from './useAppDispatch';
export { default as useAppSelector } from './useAppSelector';
export { default as useInitializedGuard } from './useInitializedGuard';
export { default as useFullscreenGuard } from './useFullscreenGuard';
10 changes: 10 additions & 0 deletions wallet/src/ui/app/hooks/useAppDispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useDispatch } from 'react-redux';

import type { AppDispatch } from '_store';

export default function useAppDispatch() {
return useDispatch<AppDispatch>();
}
11 changes: 11 additions & 0 deletions wallet/src/ui/app/hooks/useAppSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useSelector } from 'react-redux';

import type { RootState } from '_store';
import type { TypedUseSelectorHook } from 'react-redux';

const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

export default useAppSelector;
18 changes: 18 additions & 0 deletions wallet/src/ui/app/hooks/useFullscreenGuard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useEffect } from 'react';

import useAppSelector from './useAppSelector';
import { AppType } from '_redux/slices/app/AppType';
import { openInNewTab } from '_shared/utils';

export default function useFullscreenGuard() {
const appType = useAppSelector((state) => state.app.appType);
useEffect(() => {
if (appType === AppType.popup) {
openInNewTab().finally(() => window.close());
}
}, [appType]);
return appType === AppType.unknown;
}
19 changes: 19 additions & 0 deletions wallet/src/ui/app/hooks/useInitializedGuard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import useAppSelector from './useAppSelector';

export default function useInitializedGuard(initializedRequired: boolean) {
const loading = useAppSelector((state) => state.account.loading);
const isInitialized = useAppSelector((state) => !!state.account.mnemonic);
const navigate = useNavigate();
useEffect(() => {
if (!loading && initializedRequired !== isInitialized) {
navigate(isInitialized ? '/' : '/welcome', { replace: true });
}
}, [loading, initializedRequired, isInitialized, navigate]);
return loading;
}
32 changes: 26 additions & 6 deletions wallet/src/ui/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import logo from '../assets/images/sui-icon.png';
import { useEffect } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';

import st from './App.module.scss';
import HomePage from './pages/home';
import InitializePage from './pages/initialize';
import BackupPage from './pages/initialize/backup';
import CreatePage from './pages/initialize/create';
import ImportPage from './pages/initialize/import';
import SelectPage from './pages/initialize/select';
import WelcomePage from './pages/welcome';
import { useAppDispatch } from '_hooks';
import { loadAccountFromStorage } from '_redux/slices/account';

const App = () => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(loadAccountFromStorage());
});
return (
<div className={st.container}>
<img className={st.logo} src={logo} alt="logo" />
<h2>Under Construction</h2>
</div>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="welcome" element={<WelcomePage />} />
<Route path="/initialize" element={<InitializePage />}>
<Route path="select" element={<SelectPage />} />
<Route path="create" element={<CreatePage />} />
<Route path="import" element={<ImportPage />} />
<Route path="backup" element={<BackupPage />} />
</Route>
<Route path="*" element={<Navigate to="/" replace={true} />} />
</Routes>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.logo {
width: 60px;
}

.container {
display: flex;
flex-flow: column nowrap;
align-items: center;
padding: 12px;
}

.logo {
width: 60px;
min-width: 300px;
padding: 8px;
}
22 changes: 22 additions & 0 deletions wallet/src/ui/app/pages/home/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import Loading from '_components/loading';
import { useInitializedGuard } from '_hooks';
import logo from '_images/sui-icon.png';

import st from './Home.module.scss';

const HomePage = () => {
const guardChecking = useInitializedGuard(true);
return (
<Loading loading={guardChecking}>
<div className={st.container}>
<img className={st.logo} src={logo} alt="logo" />
<h2>Under Construction</h2>
</div>
</Loading>
);
};

export default HomePage;
17 changes: 17 additions & 0 deletions wallet/src/ui/app/pages/initialize/InitializePage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.container {
display: flex;
flex-flow: column nowrap;
align-items: center;
margin: 0 auto;
max-width: 800px;
padding: 16px 8px 8px;
}

.header {
align-self: stretch;
margin-bottom: 16px;
}

.content {
padding: 8px;
}
8 changes: 8 additions & 0 deletions wallet/src/ui/app/pages/initialize/backup/Backup.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.mnemonic {
padding: 12px;
border: 1px solid #90c2d866;
border-radius: 6px;
margin-bottom: 16px;
font-size: 16px;
font-weight: 400;
}
35 changes: 35 additions & 0 deletions wallet/src/ui/app/pages/initialize/backup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';

import { useAppDispatch, useAppSelector } from '_src/ui/app/hooks';
import { setMnemonic } from '_src/ui/app/redux/slices/account';

import st from './Backup.module.scss';

const BackupPage = () => {
const mnemonic = useAppSelector(
({ account }) => account.createdMnemonic || account.mnemonic
);
const navigate = useNavigate();
const dispatch = useAppDispatch();
const handleOnClick = useCallback(() => {
if (mnemonic) {
navigate('/');
dispatch(setMnemonic(mnemonic));
}
}, [navigate, dispatch, mnemonic]);
return (
<>
<h1>Backup Recovery Passphrase</h1>
<div className={st.mnemonic}>{mnemonic}</div>
<button type="button" onClick={handleOnClick} className="btn">
Done
</button>
</>
);
};

export default BackupPage;
15 changes: 15 additions & 0 deletions wallet/src/ui/app/pages/initialize/create/Create.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.desc {
font-size: 16px;
font-weight: 500;
margin-bottom: 16px;
}

.terms {
display: flex;
align-items: center;
margin-bottom: 8px;

> input {
margin: 0 8px 0 0;
}
}
Loading

0 comments on commit d3ff5d6

Please sign in to comment.