Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cheatsheet #211

Merged
merged 28 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4bddaed
feat: add cheatsheet
theborakompanioni Apr 4, 2022
8a876b5
Merge branch 'master' into cheatsheet
theborakompanioni Apr 8, 2022
ffb9fac
add links to cheatsheet items
theborakompanioni Apr 8, 2022
c903300
adapt size of cheatsheet overlay to match figma
theborakompanioni Apr 8, 2022
f734cf4
use dash instead of hyphen in cheatsheet
theborakompanioni Apr 8, 2022
f9a6822
show cheatsheet only when wallet is active
theborakompanioni Apr 8, 2022
8b9679a
display cheatsheat in dev mode only for now
theborakompanioni Apr 8, 2022
44e7716
enable i18n for cheatsheet component
theborakompanioni Apr 8, 2022
d46b623
fix: add max_height to cheatsheet overlay
theborakompanioni Apr 8, 2022
3238c28
add close button to cheatsheet overlay (for mobile users)
theborakompanioni Apr 8, 2022
e1e94fd
ui: fix cheatsheet footer nav link on mobile
theborakompanioni Apr 8, 2022
524848a
review: remove brackets from function prop in CheatsheetProps
theborakompanioni Apr 10, 2022
28926d1
ui: visualize upcoming features in cheatsheet
theborakompanioni Apr 11, 2022
e33ab57
ui: show cheatsheet once after first wallet was created
theborakompanioni Apr 11, 2022
1aea5df
Merge branch 'master' into cheatsheet
theborakompanioni Apr 11, 2022
8457913
fix: use route constants in links in cheatsheet component
theborakompanioni Apr 11, 2022
252bb69
Merge branch 'master' into cheatsheet
theborakompanioni Apr 12, 2022
04b953a
review: improve wording - 'very private' => 'more private'
theborakompanioni Apr 12, 2022
c660ab4
fix: remove duplicate import after merge
theborakompanioni Apr 12, 2022
14db0c4
review: improve wording - 'add more noise' => 'increase the privacy o…
theborakompanioni Apr 12, 2022
9cca4eb
review: improve wording in cheatsheet component
theborakompanioni Apr 12, 2022
2bd6ab6
review: improve wording in cheatsheet component
theborakompanioni Apr 12, 2022
ef30a40
Merge branch 'master' into cheatsheet
theborakompanioni Apr 12, 2022
dfb7c01
review: adopt review suggestions
theborakompanioni Apr 12, 2022
500a973
review: align cheatsheet numbered items to top
theborakompanioni Apr 13, 2022
99de5cd
review: position links on start of heading in cheatsheet
theborakompanioni Apr 13, 2022
1c20b43
feat: add simple feature flags
theborakompanioni Apr 13, 2022
37f1e9b
review: improve wording in cheatsheet component
theborakompanioni Apr 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/sprite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
131 changes: 84 additions & 47 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,31 @@ import CurrentWalletAdvanced from './CurrentWalletAdvanced'
import Settings from './Settings'
import Navbar from './Navbar'
import Layout from './Layout'
import { useSettings } from '../context/SettingsContext'
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 Sprite from './Sprite'
import Cheatsheet from './Cheatsheet'
import { routes } from '../constants/routes'
import { isFeatureEnabled } from '../constants/featureFlags'

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 [showAlphaWarning, setShowAlphaWarning] = useState(false)
const settings = useSettings()
const websocketState = useWebsocketState()
const [showCheatsheet, setShowCheatsheet] = useState(false)

const devMode = process.env.NODE_ENV === 'development'
const cheatsheetEnabled = currentWallet && isFeatureEnabled('cheatsheet')

const startWallet = useCallback(
(name, token) => {
Expand All @@ -52,6 +56,19 @@ export default function App() {
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">
Expand Down Expand Up @@ -95,7 +112,7 @@ export default function App() {
* that it stays visible in case the backend becomes unavailable.
*/}
<Route element={<Layout />}>
<Route path={routes.createWallet} element={<CreateWallet startWallet={startWallet} devMode={devMode} />} />
<Route path={routes.createWallet} element={<CreateWallet startWallet={startWallet} />} />
</Route>
{/**
* This section defines all routes that are displayed only if the backend is reachable.
Expand Down Expand Up @@ -128,6 +145,7 @@ export default function App() {
)}
</Routes>
</rb.Container>

<rb.Nav as="footer" className="border-top py-2">
<rb.Container fluid="xl" className="d-flex flex-column flex-md-row justify-content-center py-2 px-4">
<div className="d-flex flex-1 order-2 order-md-0 flex-column justify-content-center align-items-center align-items-md-start">
Expand All @@ -144,47 +162,66 @@ export default function App() {
</Trans>
</div>
</div>
<div className="d-flex order-1 flex-1 flex-grow-0 justify-content-center align-items-center px-4">
<rb.Nav.Item>
<a
href="https://github.com/joinmarket-webui/joinmarket-webui/wiki"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.docs')}
</a>
</rb.Nav.Item>
<rb.Nav.Item>
<a
href="https://github.com/joinmarket-webui/joinmarket-webui#-features"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.features')}
</a>
</rb.Nav.Item>
<rb.Nav.Item>
<a
href="https://github.com/joinmarket-webui/joinmarket-webui"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.github')}
</a>
</rb.Nav.Item>
<rb.Nav.Item>
<a
href="https://twitter.com/joinmarket"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.twitter')}
</a>
</rb.Nav.Item>
<div className="d-flex order-1 flex-1 flex-grow-0 flex-column flex-sm-row justify-content-center align-items-center pt-2 pt-sm-0 px-4">
{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 className="d-flex flex-row">
<rb.Nav.Item>
<a
href="https://github.com/joinmarket-webui/joinmarket-webui/wiki"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.docs')}
</a>
</rb.Nav.Item>
<rb.Nav.Item>
<a
href="https://github.com/joinmarket-webui/joinmarket-webui#-features"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.features')}
</a>
</rb.Nav.Item>
<rb.Nav.Item>
<a
href="https://github.com/joinmarket-webui/joinmarket-webui"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.github')}
</a>
</rb.Nav.Item>
<rb.Nav.Item>
<a
href="https://twitter.com/joinmarket"
target="_blank"
rel="noopener noreferrer"
className="nav-link text-secondary px-2"
>
{t('footer.twitter')}
</a>
</rb.Nav.Item>
</div>
</div>
<div className="d-flex order-0 order-md-2 flex-1 justify-content-center justify-content-md-end align-items-center">
<span
Expand Down
135 changes: 135 additions & 0 deletions src/components/Cheatsheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { PropsWithChildren } from 'react'
import * as rb from 'react-bootstrap'
import { Link } from 'react-router-dom'
import { Trans, useTranslation } from 'react-i18next'
import { routes } from '../constants/routes'

interface CheatsheetProps {
show: boolean
onHide: () => void
}

type NumberedProps = {
number: number
className?: string
}

function Numbered({ number }: { number: number }) {
return <div className="numbered">{number}</div>
}

function ListItem({ number, children, ...props }: PropsWithChildren<NumberedProps>) {
return (
<rb.Stack className={`cheatsheet-list-item ${props.className || ''}`} direction="horizontal" gap={3}>
<Numbered number={number} />
<rb.Stack gap={0}>{children}</rb.Stack>
</rb.Stack>
)
}

export default function Cheatsheet({ show = false, onHide }: CheatsheetProps) {
const { t } = useTranslation()

return (
<rb.Offcanvas className="cheatsheet" show={show} onHide={onHide} placement="bottom">
<rb.Offcanvas.Header closeButton>
<rb.Stack>
<rb.Offcanvas.Title>{t('cheatsheet.title')}</rb.Offcanvas.Title>
<div className="small text-secondary">
<Trans i18nKey="cheatsheet.description">
Follow the steps below to increase your financial privacy. It is advisable to switch from{' '}
<a
href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#the-maker-role"
target="_blank"
rel="noopener noreferrer"
>
earning as a maker
</a>{' '}
to{' '}
<a
href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#the-taker-role"
target="_blank"
rel="noopener noreferrer"
>
sending as a taker
</a>{' '}
back and forth.{' '}
<a
href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#a-private-flow-through-joinmarket"
target="_blank"
rel="noopener noreferrer"
>
Learn more.
</a>
</Trans>
</div>
</rb.Stack>
</rb.Offcanvas.Header>
<rb.Offcanvas.Body>
<rb.Stack className="mb-4" gap={4}>
<ListItem number={1}>
<h6>
<Trans i18nKey="cheatsheet.item_1.title">
<Link to={routes.receive}>Fund</Link> your wallet.
</Trans>
</h6>
<div className="small text-secondary">{t('cheatsheet.item_1.description')}</div>
</ListItem>
<ListItem number={2}>
<h6>
<Trans i18nKey="cheatsheet.item_2.title">
<Link to={routes.send}>Send</Link> a collaborative transaction to yourself.
</Trans>
</h6>
<div className="small text-secondary">{t('cheatsheet.item_2.description')}</div>
</ListItem>
<ListItem number={3} className="upcoming-feature">
<h6>
<Trans i18nKey="cheatsheet.item_3.title">
Optional: <Link to={routes.earn}>Lock</Link> funds in a fidelity bond.
</Trans>
</h6>
<div className="small text-secondary">
{t('cheatsheet.item_3.description')}
<br />
{/* the following phrase is intentionally not translated because it will be removed soon */}
<strong>Feature not implemented yet. Coming soon!</strong>
</div>
</ListItem>
<ListItem number={4}>
<h6>
<Trans i18nKey="cheatsheet.item_4.title">
<Link to={routes.earn}>Earn</Link> yield by providing liquidity.
</Trans>
</h6>
<div className="small text-secondary">{t('cheatsheet.item_4.description')}</div>
</ListItem>
<ListItem number={5}>
<h6>
<Trans i18nKey="cheatsheet.item_5.title">
<Link to={routes.send}>Schedule</Link> transactions.
</Trans>
</h6>
<div className="small text-secondary">{t('cheatsheet.item_5.description')}</div>
</ListItem>
<ListItem number={6}>
<h6>{t('cheatsheet.item_6.title')}</h6>
<div className="small text-secondary">
<Trans i18nKey="cheatsheet.item_6.description">
Still confused?{' '}
<a
href="https://github.com/openoms/bitcoin-tutorials/blob/master/joinmarket/joinmarket_private_flow.md#a-private-flow-through-joinmarket"
target="_blank"
rel="noopener noreferrer"
>
Dig into the documentation
</a>
.
</Trans>
</div>
</ListItem>
</rb.Stack>
</rb.Offcanvas.Body>
</rb.Offcanvas>
)
}
14 changes: 6 additions & 8 deletions src/components/CreateWallet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useServiceInfo } from '../context/ServiceInfoContext'
import * as Api from '../libs/JmWalletApi'
import './CreateWallet.css'
import { routes } from '../constants/routes'
import { isFeatureEnabled } from '../constants/featureFlags'

const PreventLeavingPageByMistake = () => {
// prompt users before refreshing or closing the page when this component is present.
Expand Down Expand Up @@ -173,13 +174,13 @@ const SeedWordInput = ({ number, targetWord, isValid, setIsValid }) => {
)
}

const BackupConfirmation = ({ createdWallet, walletConfirmed, parentStepSetter, devMode }) => {
const BackupConfirmation = ({ createdWallet, walletConfirmed, parentStepSetter }) => {
const seedphrase = createdWallet.seedphrase.split(' ')

const { t } = useTranslation()
const [seedBackup, setSeedBackup] = useState(false)
const [seedWordConfirmations, setSeedWordConfirmations] = useState(new Array(seedphrase.length).fill(false))
const [showSkipButton] = useState(devMode)
const [showSkipButton] = useState(isFeatureEnabled('skipWalletBackupConfirmation'))

useEffect(() => {
setSeedBackup(seedWordConfirmations.every((wordConfirmed) => wordConfirmed))
Expand Down Expand Up @@ -250,7 +251,7 @@ const BackupConfirmation = ({ createdWallet, walletConfirmed, parentStepSetter,
)
}

const WalletCreationConfirmation = ({ createdWallet, walletConfirmed, devMode }) => {
const WalletCreationConfirmation = ({ createdWallet, walletConfirmed }) => {
const { t } = useTranslation()
const [userConfirmed, setUserConfirmed] = useState(false)
const [revealSensitiveInfo, setRevealSensitiveInfo] = useState(false)
Expand Down Expand Up @@ -306,14 +307,13 @@ const WalletCreationConfirmation = ({ createdWallet, walletConfirmed, devMode })
parentStepSetter={childStepSetter}
createdWallet={createdWallet}
walletConfirmed={walletConfirmed}
devMode={devMode}
/>
)}
</>
)
}

export default function CreateWallet({ startWallet, devMode = false }) {
export default function CreateWallet({ startWallet }) {
const { t } = useTranslation()
const serviceInfo = useServiceInfo()
const navigate = useNavigate()
Expand Down Expand Up @@ -362,9 +362,7 @@ export default function CreateWallet({ startWallet, devMode = false }) {
)}
{alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
{canCreate && <WalletCreationForm createWallet={createWallet} />}
{isCreated && (
<WalletCreationConfirmation createdWallet={createdWallet} walletConfirmed={walletConfirmed} devMode={devMode} />
)}
{isCreated && <WalletCreationConfirmation createdWallet={createdWallet} walletConfirmed={walletConfirmed} />}
{!canCreate && !isCreated && (
<rb.Alert variant="warning">
<Trans i18nKey="create_wallet.alert_other_wallet_unlocked">
Expand Down
Loading