Skip to content

Commit

Permalink
feat: improve wallet control in settings (#325)
Browse files Browse the repository at this point in the history
* feat: add lock and new wallet controls
  • Loading branch information
Daniel committed Jun 15, 2022
1 parent f5e8227 commit 9d00212
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 71 deletions.
19 changes: 12 additions & 7 deletions public/sprite.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export default function App() {
<Route path={routes.send} element={<Send />} />
<Route path={routes.earn} element={<Earn />} />
<Route path={routes.receive} element={<Receive />} />
<Route path={routes.settings} element={<Settings />} />
<Route path={routes.settings} element={<Settings stopWallet={stopWallet} />} />
</>
)}
</Route>
Expand Down
41 changes: 41 additions & 0 deletions src/components/Modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import * as rb from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import styles from './Modal.module.css'
import Sprite from './Sprite'

const ConfirmModal = ({ isShown, title, body, onCancel, onConfirm }) => {
const { t } = useTranslation()

return (
<rb.Modal
show={isShown}
keyboard={true}
onEscapeKeyDown={onCancel}
centered={true}
animation={true}
backdrop="static"
className={styles['modal']}
>
<rb.Modal.Header className={styles['modal-header']}>
<rb.Modal.Title className={styles['modal-title']}>{title}</rb.Modal.Title>
</rb.Modal.Header>
<rb.Modal.Body className={styles['modal-body']}>{body}</rb.Modal.Body>
<rb.Modal.Footer className={styles['modal-footer']}>
<rb.Button
variant="outline-dark"
onClick={onCancel}
className="d-flex justify-content-center align-items-center"
>
<Sprite symbol="cancel" width="26" height="26" />
<div>{t('modal.confirm_button_reject')}</div>
</rb.Button>
<rb.Button variant="outline-dark" onClick={onConfirm}>
{t('modal.confirm_button_accept')}
</rb.Button>
</rb.Modal.Footer>
</rb.Modal>
)
}

export { ConfirmModal }
45 changes: 45 additions & 0 deletions src/components/Modal.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
:global .modal-backdrop {
background-color: rgba(0, 0, 0, 0.5) !important;
}

.modal :global .modal-content {
background-color: var(--bs-body-bg) !important;
border-radius: 1rem !important;
box-shadow: 0px 0px 24px rgba(0, 0, 0, 0.25) !important;
}

.modal-header {
display: flex !important;
justify-content: center !important;
background-color: transparent !important;
border: none !important;
padding: 1.25rem 1.25rem 0 1.25rem !important;
}

.modal-title {
font-size: 1.3rem !important;
font-weight: 600 !important;
color: var(--bs-body-color) !important;
}

.modal-body {
text-align: center !important;
font-size: 1rem !important;
font-weight: 400 !important;
padding: 0.25rem 1.25rem 1rem 1.25rem !important;
}

.modal-footer {
display: flex !important;
justify-content: center !important;
gap: 1rem;
background-color: transparent !important;
padding: 1rem 1.25rem 1.25rem 1.25rem !important;
}

.modal-footer :global .btn {
flex-grow: 1;
min-height: 2.8rem;
font-weight: 500;
border-color: rgba(222, 222, 222, 1);
}
98 changes: 94 additions & 4 deletions src/components/Settings.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import React, { useEffect, useState, useCallback } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import * as rb from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import Sprite from './Sprite'
import Seedphrase from './Seedphrase'
import ToggleSwitch from './ToggleSwitch'
import Alert from './Alert'
import { ConfirmModal } from './Modal'
import { useSettings, useSettingsDispatch } from '../context/SettingsContext'
import { useCurrentWallet } from '../context/WalletContext'
import { useServiceInfo } from '../context/ServiceInfoContext'
import { SATS, BTC, walletDisplayName } from '../utils'
import * as Api from '../libs/JmWalletApi'
import { routes } from '../constants/routes'
Expand Down Expand Up @@ -80,12 +83,19 @@ function SeedModal({ show = false, onHide }) {
)
}

export default function Settings() {
const { t } = useTranslation()
export default function Settings({ stopWallet }) {
const [showingSeed, setShowingSeed] = useState(false)
const [lockingWallet, setLockingWallet] = useState(false)
const [showConfirmLockModal, setShowConfirmLockModal] = useState(null)
const [alert, setAlert] = useState(null)

const { t } = useTranslation()
const settings = useSettings()
const settingsDispatch = useSettingsDispatch()
const { i18n } = useTranslation()
const currentWallet = useCurrentWallet()
const navigate = useNavigate()
const serviceInfo = useServiceInfo()

const setTheme = (theme) => {
if (window.JM.THEMES.includes(theme)) {
Expand All @@ -97,10 +107,56 @@ export default function Settings() {
const isSats = settings.unit === SATS
const isLightTheme = settings.theme === window.JM.THEMES[0]

const lockWallet = useCallback(
async ({ force, destination }) => {
if (!force && (serviceInfo.coinjoinInProgress || serviceInfo.makerRunning)) {
setShowConfirmLockModal({ destination })
return
}

setLockingWallet(true)

try {
const { name: walletName, token } = currentWallet
const res = await Api.getWalletLock({ walletName, token })

if (res.ok || res.status === 401) {
stopWallet()
} else {
await Api.Helper.throwError(res)
}

setLockingWallet(false)
navigate(destination)
} catch (e) {
setLockingWallet(false)
setAlert({ variant: 'danger', dismissible: false, message: e.message })
}
},
[currentWallet, stopWallet, navigate, serviceInfo]
)

return (
<div className={styles.settings}>
<div className="d-flex flex-column gap-3">
<div className={styles['section-title']}>{t('settings.title')}</div>
{alert && <Alert {...alert} />}
<ConfirmModal
isShown={showConfirmLockModal}
title={t('wallets.wallet_preview.modal_lock_wallet_title')}
body={
(serviceInfo?.makerRunning
? t('wallets.wallet_preview.modal_lock_wallet_maker_running_text')
: t('wallets.wallet_preview.modal_lock_wallet_coinjoin_in_progress_text')) +
' ' +
t('wallets.wallet_preview.modal_lock_wallet_alternative_action_text')
}
onCancel={() => setShowConfirmLockModal(null)}
onConfirm={() => {
setShowConfirmLockModal(null)
lockWallet({ force: true, destination: showConfirmLockModal?.destination })
}}
/>
<div className={styles['settings-group-container']}>
<rb.Button
variant="outline-dark"
Expand Down Expand Up @@ -172,10 +228,44 @@ export default function Settings() {
</rb.Button>
{showingSeed && <SeedModal show={showingSeed} onHide={() => setShowingSeed(false)} />}

<rb.Button
variant="outline-dark"
className={styles['settings-btn']}
onClick={() => lockWallet({ force: false, destination: routes.walletList })}
>
{lockingWallet ? (
<>
<rb.Spinner as="span" animation="border" size="sm" role="status" aria-hidden="true" className="mx-1" />
{t('settings.button_locking_wallet')}
</>
) : (
<>
<Sprite symbol="lock" width="24" height="24" />
{t('settings.button_lock_wallet')}
</>
)}
</rb.Button>
<Link to={routes.walletList} className={`btn btn-outline-dark ${styles['settings-btn']}`}>
<Sprite symbol="wallet" width="24" height="24" />
{t('settings.button_switch_wallet')}
</Link>
<rb.Button
variant="outline-dark"
className={styles['settings-btn']}
onClick={() => lockWallet({ force: false, destination: routes.createWallet })}
>
{lockingWallet ? (
<>
<rb.Spinner as="span" animation="border" size="sm" role="status" aria-hidden="true" className="mx-1" />
{t('settings.button_locking_wallet')}
</>
) : (
<>
<Sprite symbol="plus" width="24" height="24" />
{t('settings.button_create_wallet')}
</>
)}
</rb.Button>
</div>

<div className={styles['section-title']}>{t('settings.section_title_community')}</div>
Expand Down
62 changes: 12 additions & 50 deletions src/components/Wallet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,14 @@ import React, { useState, useCallback, useEffect } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Formik } from 'formik'
import * as rb from 'react-bootstrap'
import { ConfirmModal } from './Modal'
import { useTranslation } from 'react-i18next'
import { walletDisplayName } from '../utils'
import * as Api from '../libs/JmWalletApi'
import { TabActivityIndicator, JoiningIndicator } from './ActivityIndicators'
import { routes } from '../constants/routes'
import styles from './Wallet.module.css'

function ConfirmModal({ show = false, onHide, title, body, footer }) {
return (
<rb.Modal show={show} onHide={onHide} keyboard={false} centered={true} animation={true}>
<rb.Modal.Header closeButton>
<rb.Modal.Title>{title}</rb.Modal.Title>
</rb.Modal.Header>
<rb.Modal.Body>{body}</rb.Modal.Body>
<rb.Modal.Footer>{footer}</rb.Modal.Footer>
</rb.Modal>
)
}

function ConfirmLockModal({ show = false, body, onHide, onConfirm }) {
const { t } = useTranslation()

const title = t('wallets.wallet_preview.modal_lock_wallet_title')
const footer = (
<>
<rb.Button variant="outline-dark" onClick={onHide}>
{t('wallets.wallet_preview.modal_lock_wallet_button_cancel')}
</rb.Button>
<rb.Button variant="dark" onClick={onConfirm}>
{t('wallets.wallet_preview.modal_lock_wallet_button_confirm')}
</rb.Button>
</>
)
return <ConfirmModal show={show} onHide={onHide} title={title} body={body} footer={footer} />
}

const WalletLockForm = ({ walletName, lockWallet }) => {
const { t } = useTranslation()

Expand Down Expand Up @@ -145,7 +117,6 @@ export default function Wallet({
}) {
const { t } = useTranslation()
const [showLockConfirmModal, setShowLockConfirmModal] = useState(false)
const [confirmLockBody, setConfirmLockBody] = useState(<></>)

const navigate = useNavigate()

Expand Down Expand Up @@ -242,20 +213,6 @@ export default function Wallet({
[currentWallet, coinjoinInProgress, makerRunning, setAlert, stopWallet, t]
)

useEffect(() => {
const serviceRunningInfoText =
(makerRunning && t('wallets.wallet_preview.modal_lock_wallet_maker_running_text')) ||
(coinjoinInProgress && t('wallets.wallet_preview.modal_lock_wallet_coinjoin_in_progress_text'))

setConfirmLockBody(
<>
{serviceRunningInfoText}
{serviceRunningInfoText ? ' ' : ''}
{t('wallets.wallet_preview.modal_lock_wallet_alternative_action_text')}
</>
)
}, [makerRunning, coinjoinInProgress, t])

useEffect(() => {
if (!currentWallet) {
setShowLockConfirmModal(false)
Expand All @@ -274,13 +231,18 @@ export default function Wallet({

return (
<>
<ConfirmLockModal
show={showLockConfirmModal}
body={confirmLockBody}
<ConfirmModal
isShown={showLockConfirmModal}
title={t('wallets.wallet_preview.modal_lock_wallet_title')}
body={
(makerRunning
? t('wallets.wallet_preview.modal_lock_wallet_maker_running_text')
: t('wallets.wallet_preview.modal_lock_wallet_coinjoin_in_progress_text')) +
' ' +
t('wallets.wallet_preview.modal_lock_wallet_alternative_action_text')
}
onCancel={() => setShowLockConfirmModal(false)}
onConfirm={onLockConfirmed}
onHide={() => {
setShowLockConfirmModal(false)
}}
/>
<rb.Card {...props}>
<rb.Card.Body>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Wallet.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,12 @@ describe('<Wallet />', () => {
// modal appeared
expect(screen.getByText('wallets.wallet_preview.modal_lock_wallet_title')).toBeInTheDocument()
expect(screen.getByText(expectedModalBody)).toBeInTheDocument()
expect(screen.getByText('wallets.wallet_preview.modal_lock_wallet_button_cancel')).toBeInTheDocument()
expect(screen.getByText('wallets.wallet_preview.modal_lock_wallet_button_confirm')).toBeInTheDocument()
expect(screen.getByText('modal.confirm_button_accept')).toBeInTheDocument()
expect(screen.getByText('modal.confirm_button_reject')).toBeInTheDocument()

act(() => {
// click on the modal's "cancel" button
const lockWalletButton = screen.getByText('wallets.wallet_preview.modal_lock_wallet_button_cancel')
const lockWalletButton = screen.getByText('modal.confirm_button_reject')
user.click(lockWalletButton)
})
await waitForElementToBeRemoved(screen.getByText('wallets.wallet_preview.modal_lock_wallet_title'))
Expand All @@ -339,7 +339,7 @@ describe('<Wallet />', () => {

act(() => {
// click on the modal's "confirm" button
const lockWalletButton = screen.getByText('wallets.wallet_preview.modal_lock_wallet_button_confirm')
const lockWalletButton = screen.getByText('modal.confirm_button_accept')
user.click(lockWalletButton)
})
await waitForElementToBeRemoved(screen.getByText('wallets.wallet_preview.modal_lock_wallet_title'))
Expand Down

0 comments on commit 9d00212

Please sign in to comment.