Skip to content

Commit c43a179

Browse files
author
sim
committed
feature(auth): Lock a wallet
1 parent bbaf9a7 commit c43a179

13 files changed

Lines changed: 199 additions & 66 deletions

File tree

src/app/sections/ConnectWallet.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const ConnectWallet = ({ renderButton }: Props) => {
5757
<ModalButton
5858
title={t("Connect wallet")}
5959
renderButton={renderButton ?? defaultRenderButton}
60+
maxHeight
6061
>
6162
<Grid gap={20}>
6263
<SwitchWallet />

src/auth/auth.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type LocalWallet = SingleWallet | MultisigWallet // wallet with name
66
interface SingleWallet {
77
address: string
88
name: string
9+
lock?: boolean
910
}
1011

1112
interface MultisigWallet extends SingleWallet {

src/auth/components/AuthButton.module.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
.button {
22
border: var(--border-width) solid var(--card-border);
33
border-radius: var(--border-radius);
4+
color: inherit;
45
font-weight: var(--normal);
56
transition: all var(--transition);
67

78
&:hover,
89
&.active {
910
background: var(--bg-muted);
11+
text-decoration: none;
1012
}
1113

1214
&.active {

src/auth/components/AuthButton.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
import { ButtonHTMLAttributes } from "react"
2+
import { Link, LinkProps } from "react-router-dom"
23
import classNames from "classnames/bind"
34
import styles from "./AuthButton.module.scss"
45

56
const cx = classNames.bind(styles)
67

7-
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
8+
interface ButtonAttrs extends ButtonHTMLAttributes<HTMLButtonElement> {
89
active: boolean
910
}
1011

12+
interface LinkAttrs extends LinkProps {
13+
active: boolean
14+
}
15+
16+
type Props = ButtonAttrs | LinkAttrs
17+
1118
const AuthButton = ({ active, ...attrs }: Props) => {
12-
return (
13-
<button
14-
{...attrs}
15-
type="button"
16-
className={cx(styles.button, { active }, attrs.className)}
17-
/>
18-
)
19+
const className = cx(styles.button, { active }, attrs.className)
20+
if (active) return <span {...attrs} className={className} />
21+
if ("to" in attrs) return <Link {...attrs} className={className} />
22+
return <button {...attrs} type="button" className={className} />
1923
}
2024

2125
export default AuthButton

src/auth/components/AuthList.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@ import { ReactNode } from "react"
22
import { Link } from "react-router-dom"
33
import styles from "./AuthList.module.scss"
44

5-
interface Item {
6-
to: string
7-
children: string
8-
icon: ReactNode
9-
}
5+
type Item =
6+
| { to: string; children: string; icon: ReactNode }
7+
| { onClick: () => void; children: string; icon: ReactNode }
108

119
const AuthList = ({ list }: { list: Item[] }) => {
12-
const renderItem = ({ to, children, icon }: Item) => {
10+
const renderItem = ({ children, icon, ...props }: Item) => {
11+
if ("onClick" in props) {
12+
const { onClick } = props
13+
return (
14+
<button className={styles.link} onClick={onClick} key={children}>
15+
{children}
16+
{icon}
17+
</button>
18+
)
19+
}
20+
21+
const { to } = props
1322
return (
14-
<Link to={to} className={styles.link} key={to}>
23+
<Link to={to} className={styles.link} key={children}>
1524
{children}
1625
{icon}
1726
</Link>

src/auth/hooks/useAuth.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { useLCDClient } from "data/queries/lcdClient"
99
import is from "../scripts/is"
1010
import { PasswordError } from "../scripts/keystore"
1111
import { getDecryptedKey, testPassword } from "../scripts/keystore"
12-
import { getWallet, storeWallet, clearWallet } from "../scripts/keystore"
12+
import { getWallet, storeWallet } from "../scripts/keystore"
13+
import { clearWallet, lockWallet } from "../scripts/keystore"
1314
import { getStoredWallet, getStoredWallets } from "../scripts/keystore"
1415
import encrypt from "../scripts/encrypt"
1516
import * as ledger from "../ledger/ledger"
@@ -28,11 +29,13 @@ const useAuth = () => {
2829
const [wallet, setWallet] = useRecoilState(walletState)
2930
const wallets = getStoredWallets()
3031

31-
/* connect | disconnect */
32+
/* connect */
3233
const connect = useCallback(
3334
(name: string) => {
3435
const storedWallet = getStoredWallet(name)
35-
const { address } = storedWallet
36+
const { address, lock } = storedWallet
37+
38+
if (lock) throw new Error("Wallet is locked")
3639

3740
const wallet = is.multisig(storedWallet)
3841
? { name, address, multisig: true }
@@ -53,22 +56,30 @@ const useAuth = () => {
5356
[setWallet]
5457
)
5558

56-
const disconnect = useCallback(() => {
57-
clearWallet()
58-
setWallet(undefined)
59-
}, [setWallet])
60-
61-
/* helpers */
59+
/* connected */
6260
const connectedWallet = useMemo(() => {
6361
if (!is.local(wallet)) return
6462
return wallet
6563
}, [wallet])
6664

67-
const getConnectedWallet = () => {
65+
const getConnectedWallet = useCallback(() => {
6866
if (!connectedWallet) throw new Error("Wallet is not defined")
6967
return connectedWallet
70-
}
68+
}, [connectedWallet])
7169

70+
/* disconnected */
71+
const disconnect = useCallback(() => {
72+
clearWallet()
73+
setWallet(undefined)
74+
}, [setWallet])
75+
76+
const lock = useCallback(() => {
77+
const { name } = getConnectedWallet()
78+
lockWallet(name)
79+
disconnect()
80+
}, [disconnect, getConnectedWallet])
81+
82+
/* helpers */
7283
const getKey = (password: string) => {
7384
const { name } = getConnectedWallet()
7485
return getDecryptedKey({ name, password })
@@ -198,6 +209,7 @@ const useAuth = () => {
198209
connect,
199210
connectLedger,
200211
disconnect,
212+
lock,
201213
available,
202214
encodeEncryptedWallet,
203215
validatePassword,

src/auth/modules/Auth.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Route, Routes } from "react-router-dom"
22

33
/* connect */
4+
import UnlockPage from "./select/UnlockPage"
45
import AccessWithLedgerPage from "../ledger/AccessWithLedgerPage"
56

67
/* create */
@@ -14,14 +15,14 @@ import ManageWallets from "./manage/ManageWallets"
1415
import ExportWalletPage from "./manage/ExportWalletPage"
1516
import ChangePasswordPage from "./manage/ChangePasswordPage"
1617
import DeleteWalletPage from "./manage/DeleteWalletPage"
17-
import Disconnect from "./manage/Disconnect"
1818

1919
const Auth = () => {
2020
return (
2121
<Routes>
2222
<Route index element={<ManageWallets />} />
2323

2424
{/* connect */}
25+
<Route path="unlock/:name" element={<UnlockPage />} />
2526
<Route path="ledger" element={<AccessWithLedgerPage />} />
2627

2728
{/* create */}
@@ -34,7 +35,6 @@ const Auth = () => {
3435
<Route path="export" element={<ExportWalletPage />} />
3536
<Route path="password" element={<ChangePasswordPage />} />
3637
<Route path="delete" element={<DeleteWalletPage />} />
37-
<Route path="disconnect" element={<Disconnect />} />
3838
</Routes>
3939
)
4040
}

src/auth/modules/manage/Disconnect.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/auth/modules/manage/ManageWallets.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useTranslation } from "react-i18next"
2+
import { useNavigate } from "react-router-dom"
23
import QrCodeIcon from "@mui/icons-material/QrCode"
34
import PasswordIcon from "@mui/icons-material/Password"
45
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"
56
import FactCheckOutlinedIcon from "@mui/icons-material/FactCheckOutlined"
67
import LogoutIcon from "@mui/icons-material/Logout"
8+
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
79
import { Col, Page } from "components/layout"
810
import is from "../../scripts/is"
911
import useAuth from "../../hooks/useAuth"
@@ -12,7 +14,8 @@ import ConnectedWallet from "./ConnectedWallet"
1214

1315
export const useManageWallet = () => {
1416
const { t } = useTranslation()
15-
const { wallet } = useAuth()
17+
const navigate = useNavigate()
18+
const { wallet, disconnect, lock } = useAuth()
1619

1720
const toExport = {
1821
to: "/auth/export",
@@ -44,19 +47,31 @@ export const useManageWallet = () => {
4447
icon: <FactCheckOutlinedIcon />,
4548
}
4649

47-
const toDisconnect = {
48-
to: "/auth/disconnect",
50+
const disconnectWallet = {
51+
onClick: () => {
52+
disconnect()
53+
navigate("/", { replace: true })
54+
},
4955
children: t("Disconnect"),
5056
icon: <LogoutIcon />,
5157
}
5258

59+
const lockWallet = {
60+
onClick: () => {
61+
lock()
62+
navigate("/", { replace: true })
63+
},
64+
children: t("Lock"),
65+
icon: <LockOutlinedIcon />,
66+
}
67+
5368
if (!wallet) return
5469

55-
return is.ledger(wallet)
56-
? [toSignMultisig, toDisconnect]
57-
: is.multisig(wallet)
58-
? [toPostMultisig, toDelete, toDisconnect]
59-
: [toExport, toPassword, toDelete, toSignMultisig, toDisconnect]
70+
return is.multisig(wallet)
71+
? [toPostMultisig, toDelete, disconnectWallet]
72+
: is.ledger(wallet)
73+
? [toSignMultisig, disconnectWallet]
74+
: [toExport, toPassword, toDelete, toSignMultisig, lockWallet]
6075
}
6176

6277
const ManageWallets = () => {

src/auth/modules/select/SwitchWallet.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"
12
import { truncate } from "@terra.kitchen/utils"
23
import classNames from "classnames/bind"
34
import { Flex } from "components/layout"
@@ -15,22 +16,32 @@ const SwitchWallet = () => {
1516
return !wallets.length ? null : (
1617
<ul className={styles.list}>
1718
{wallets.map((wallet) => {
18-
const { name, address } = wallet
19+
const { name, address, lock } = wallet
1920
const active = name === connectedWallet?.name
21+
const children = (
22+
<>
23+
<Flex gap={4}>
24+
{is.multisig(wallet) && <MultisigBadge />}
25+
<strong>{name}</strong>
26+
</Flex>
27+
28+
{lock ? (
29+
<LockOutlinedIcon fontSize="inherit" className="muted" />
30+
) : (
31+
truncate(address)
32+
)}
33+
</>
34+
)
35+
36+
const attrs = { className: cx(styles.wallet), active, children }
2037

2138
return (
2239
<li key={name}>
23-
<AuthButton
24-
className={cx(styles.wallet)}
25-
onClick={() => connect(name)}
26-
active={active}
27-
>
28-
<Flex gap={4}>
29-
{is.multisig(wallet) && <MultisigBadge />}
30-
<strong>{name}</strong>
31-
</Flex>
32-
{truncate(address)}
33-
</AuthButton>
40+
{lock ? (
41+
<AuthButton {...attrs} to={`/auth/unlock/${name}`} />
42+
) : (
43+
<AuthButton {...attrs} onClick={() => connect(name)} />
44+
)}
3445
</li>
3546
)
3647
})}

0 commit comments

Comments
 (0)