mirror of
https://github.com/joinmarket-webui/jam.git
synced 2026-08-20 13:28:21 +02:00
* refactor: move lock/unlock wallet api calls from child component to parent * refactor: control Wallet component appearance from parent * fix: prevent state update on an unmounted component on Settings page
245 lines
9.7 KiB
JavaScript
245 lines
9.7 KiB
JavaScript
import React, { useState, useEffect, useCallback, useMemo } from 'react'
|
|
import { Route, Routes, Navigate } from 'react-router-dom'
|
|
import * as rb from 'react-bootstrap'
|
|
import { Trans, useTranslation } from 'react-i18next'
|
|
import Wallets from './Wallets'
|
|
import CreateWallet from './CreateWallet'
|
|
import Jam from './Jam'
|
|
import Send from './Send'
|
|
import Earn from './Earn'
|
|
import Receive from './Receive'
|
|
import CurrentWalletMagic from './CurrentWalletMagic'
|
|
import CurrentWalletAdvanced from './CurrentWalletAdvanced'
|
|
import FidelityBond from './FidelityBond'
|
|
import Settings from './Settings'
|
|
import Navbar from './Navbar'
|
|
import Layout from './Layout'
|
|
import Sprite from './Sprite'
|
|
import { useSettings, useSettingsDispatch } from '../context/SettingsContext'
|
|
import { useWebsocketState } from '../context/WebsocketContext'
|
|
import { useCurrentWallet, useSetCurrentWallet } from '../context/WalletContext'
|
|
import { useSessionConnectionError } from '../context/ServiceInfoContext'
|
|
import { setSession, clearSession } from '../session'
|
|
import Onboarding from './Onboarding'
|
|
import Cheatsheet from './Cheatsheet'
|
|
import { routes } from '../constants/routes'
|
|
import packageInfo from '../../package.json'
|
|
|
|
export default function App() {
|
|
const { t } = useTranslation()
|
|
const settings = useSettings()
|
|
const settingsDispatch = useSettingsDispatch()
|
|
const websocketState = useWebsocketState()
|
|
const currentWallet = useCurrentWallet()
|
|
const setCurrentWallet = useSetCurrentWallet()
|
|
const sessionConnectionError = useSessionConnectionError()
|
|
|
|
const [websocketConnected, setWebsocketConnected] = useState()
|
|
const [showBetaWarning, setShowBetaWarning] = useState(false)
|
|
const [showCheatsheet, setShowCheatsheet] = useState(false)
|
|
|
|
const cheatsheetEnabled = useMemo(() => !!currentWallet, [currentWallet])
|
|
|
|
const startWallet = useCallback(
|
|
(name, token) => {
|
|
setSession({ name, token })
|
|
setCurrentWallet({ name, token })
|
|
},
|
|
[setCurrentWallet]
|
|
)
|
|
|
|
const stopWallet = useCallback(() => {
|
|
clearSession()
|
|
setCurrentWallet(null)
|
|
}, [setCurrentWallet])
|
|
|
|
// update the connection indicator based on the websocket connection state
|
|
useEffect(() => {
|
|
setWebsocketConnected(websocketState === WebSocket.OPEN)
|
|
}, [websocketState])
|
|
|
|
useEffect(() => {
|
|
let timer
|
|
// show the cheatsheet once after the first wallet has been created
|
|
if (cheatsheetEnabled && settings.showCheatsheet) {
|
|
timer = setTimeout(() => {
|
|
setShowCheatsheet(true)
|
|
settingsDispatch({ showCheatsheet: false })
|
|
}, 1_000)
|
|
}
|
|
|
|
return () => clearTimeout(timer)
|
|
}, [cheatsheetEnabled, settings, settingsDispatch])
|
|
|
|
if (settings.showOnboarding === true) {
|
|
return (
|
|
<rb.Container className="onboarding my-5">
|
|
<rb.Row className="justify-content-center mt-md-5">
|
|
<rb.Col xs={10} sm={10} md={8} lg={6} xl={4}>
|
|
<Onboarding />
|
|
</rb.Col>
|
|
</rb.Row>
|
|
</rb.Container>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{showBetaWarning && (
|
|
<div className="warning-card-wrapper">
|
|
<rb.Card className="warning-card translate-middle shadow-lg">
|
|
<rb.Card.Body>
|
|
<rb.Card.Title className="text-center mb-3">{t('footer.warning_alert_title')}</rb.Card.Title>
|
|
<p className="text-secondary">{t('footer.warning_alert_text')}</p>
|
|
<div className="text-center mt-3">
|
|
<rb.Button variant="secondary" onClick={() => setShowBetaWarning(false)}>
|
|
{t('footer.warning_alert_button_ok')}
|
|
</rb.Button>
|
|
</div>
|
|
</rb.Card.Body>
|
|
</rb.Card>
|
|
</div>
|
|
)}
|
|
<Navbar />
|
|
<rb.Container as="main" className="py-4 py-sm-5">
|
|
{sessionConnectionError && (
|
|
<rb.Alert variant="danger">
|
|
{t('app.alert_no_connection', { connectionError: sessionConnectionError.message })}.
|
|
</rb.Alert>
|
|
)}
|
|
<Routes>
|
|
{/**
|
|
* This sections defines all routes that can be displayed, even if the connection
|
|
* to the backend is down, e.g. "create-wallet" shows the seed quiz and it is important
|
|
* that it stays visible in case the backend becomes unavailable.
|
|
*/}
|
|
<Route element={<Layout />}>
|
|
<Route path={routes.createWallet} element={<CreateWallet startWallet={startWallet} />} />
|
|
</Route>
|
|
{/**
|
|
* This section defines all routes that are displayed only if the backend is reachable.
|
|
*/}
|
|
{!sessionConnectionError && (
|
|
<>
|
|
<Route element={<Layout />}>
|
|
<Route
|
|
path={routes.home}
|
|
element={<Wallets currentWallet={currentWallet} startWallet={startWallet} stopWallet={stopWallet} />}
|
|
/>
|
|
{currentWallet && (
|
|
<>
|
|
<Route path={routes.jam} element={<Jam />} />
|
|
<Route path={routes.send} element={<Send />} />
|
|
<Route path={routes.earn} element={<Earn />} />
|
|
<Route path={routes.receive} element={<Receive />} />
|
|
<Route path={routes.settings} element={<Settings stopWallet={stopWallet} />} />
|
|
<Route path={routes.fidelityBonds} element={<FidelityBond />} />
|
|
</>
|
|
)}
|
|
</Route>
|
|
{currentWallet && !settings.useAdvancedWalletMode && (
|
|
<Route element={<Layout />}>
|
|
<Route path={routes.wallet} element={<CurrentWalletMagic />} />
|
|
</Route>
|
|
)}
|
|
{currentWallet && settings.useAdvancedWalletMode && (
|
|
<Route element={<Layout variant="wide" />}>
|
|
<Route path={routes.wallet} element={<CurrentWalletAdvanced />} />
|
|
</Route>
|
|
)}
|
|
<Route path="*" element={<Navigate to={routes.home} replace={true} />} />
|
|
</>
|
|
)}
|
|
</Routes>
|
|
</rb.Container>
|
|
|
|
<rb.Nav as="footer" className="border-top py-2">
|
|
<rb.Container fluid="xl" className="d-flex justify-content-center py-2 px-4">
|
|
<div className="d-none d-md-flex flex-1 order-0 justify-content-start align-items-center">
|
|
<div className="warning-hint text-start text-secondary">
|
|
<Trans i18nKey="footer.warning">
|
|
This is pre-alpha software.
|
|
<rb.Button
|
|
variant="link"
|
|
className="warning-hint text-start border-0 p-0 text-secondary"
|
|
onClick={() => setShowBetaWarning(true)}
|
|
>
|
|
Read this before using.
|
|
</rb.Button>
|
|
</Trans>
|
|
</div>
|
|
</div>
|
|
<div className="d-flex order-1 flex-1 flex-grow-0 justify-content-center align-items-center pt-0">
|
|
{cheatsheetEnabled && (
|
|
<div className="order-1 order-sm-0">
|
|
<Cheatsheet show={showCheatsheet} onHide={() => setShowCheatsheet(false)} />
|
|
<rb.Nav.Item>
|
|
<rb.Button
|
|
variant="link"
|
|
className="cheatsheet-link nav-link text-start border-0 px-2"
|
|
onClick={() => setShowCheatsheet(true)}
|
|
>
|
|
<div className="d-flex justify-content-center align-items-center">
|
|
<Sprite symbol="file" width="24" height="24" />
|
|
<div className="ps-0">{t('footer.cheatsheet')}</div>
|
|
</div>
|
|
</rb.Button>
|
|
</rb.Nav.Item>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="d-flex flex-1 order-2 justify-content-end align-items-center gap-1">
|
|
<div className="warning-hint text-start text-secondary d-none d-md-block pe-1">
|
|
<a
|
|
href="https://github.com/joinmarket-webui/joinmarket-webui/tags"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="d-flex align-items-center text-secondary"
|
|
>
|
|
{' '}
|
|
v{packageInfo.version}
|
|
</a>
|
|
</div>
|
|
<div className="d-flex gap-2 pe-2">
|
|
<a
|
|
href="https://github.com/joinmarket-webui/joinmarket-webui"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="d-flex align-items-center text-secondary"
|
|
>
|
|
<Sprite symbol="github" width="18px" height="18px" />
|
|
</a>
|
|
<a
|
|
href="https://t.me/JoinMarketWebUI"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="d-flex align-items-center text-secondary"
|
|
>
|
|
<Sprite symbol="telegram" width="18px" height="18px" />
|
|
</a>
|
|
</div>
|
|
<div className="d-flex text-secondary">|</div>
|
|
<div className="d-flex">
|
|
<rb.OverlayTrigger
|
|
delay={{ hide: 300, show: 200 }}
|
|
placement="top"
|
|
overlay={(props) => (
|
|
<rb.Tooltip {...props}>
|
|
{websocketConnected ? t('footer.connected') : t('footer.disconnected')}
|
|
</rb.Tooltip>
|
|
)}
|
|
>
|
|
<span
|
|
className={`mx-1 ${websocketConnected ? 'text-success' : 'text-danger'}`}
|
|
data-testid="connection-indicator-icon"
|
|
>
|
|
<Sprite symbol="node" width="24px" height="24px" />
|
|
</span>
|
|
</rb.OverlayTrigger>
|
|
</div>
|
|
</div>
|
|
</rb.Container>
|
|
</rb.Nav>
|
|
</>
|
|
)
|
|
}
|