chore: small ui changes suggested by @editwentyone (#709)

* chore(main): change wallet distribution popover placement

* chore(receive): move new address button below qrcode

* chore(footer): hide app info on small screens

* chore: use standard modal for app info

* ui(menu): increase font size on mobile in side menu

* ui(receive): fix amount input field addon border
This commit is contained in:
Thebora Kompanioni 2023-12-14 11:39:42 +01:00 committed by GitHub
parent 643db8570b
commit 06bf4dbb20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 5566 additions and 1514 deletions

6702
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -56,6 +56,7 @@ function Copyable({
<>
<button
{...props}
type="button"
disabled={disabled}
className={className}
onClick={() => copyToClipboard(value, valueFallbackInputRef.current!).then(onSuccess, onError)}

View file

@ -575,7 +575,7 @@ export default function Earn({ wallet }: EarnProps) {
}, [stopMakerService, isLoading, isSending, isWaitingMakerStart, isWaitingMakerStop, t])
return (
<div className={styles['earn']}>
<div className={styles.earn}>
<PageTitle title={t('earn.title')} subtitle={t('earn.subtitle')} />
<rb.Row className="mb-2">

View file

@ -1,4 +1,5 @@
import { useState, useEffect, useMemo } from 'react'
import { Link } from 'react-router-dom'
import * as rb from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useSettings, useSettingsDispatch } from '../context/SettingsContext'
@ -7,11 +8,11 @@ import { useWebsocketState } from '../context/WebsocketContext'
import { useCurrentWallet } from '../context/WalletContext'
import Sprite from './Sprite'
import Cheatsheet from './Cheatsheet'
import packageInfo from '../../package.json'
import { InfoModal } from './Modal'
import { isDebugFeatureEnabled, isDevMode } from '../constants/debugFeatures'
import { toSemVer } from '../utils'
import { Link } from 'react-router-dom'
import { routes } from '../constants/routes'
import { toSemVer } from '../utils'
import packageInfo from '../../package.json'
const APP_DISPLAY_VERSION = (() => {
const version = toSemVer(packageInfo.version)
@ -48,29 +49,26 @@ export default function Footer() {
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>{t('footer.warning_alert_text')}</p>
<p className="text-secondary">
JoinMarket: v{serviceInfo?.server?.version?.raw || '_unknown'}
<br />
Jam: v{APP_DISPLAY_VERSION}
</p>
<div className="text-center mt-3">
<rb.Button variant="dark" onClick={() => setShowBetaWarning(false)}>
{t('footer.warning_alert_button_ok')}
</rb.Button>
</div>
</rb.Card.Body>
</rb.Card>
</div>
<InfoModal
isShown={showBetaWarning}
size="sm"
title={t('footer.warning_alert_title')}
submitButtonText={t('footer.warning_alert_button_ok')}
onCancel={() => setShowBetaWarning(false)}
onSubmit={() => setShowBetaWarning(false)}
>
<p>{t('footer.warning_alert_text')}</p>
<p className="mb-0 text-secondary">
JoinMarket: v{serviceInfo?.server?.version?.raw || '_unknown'}
<br />
Jam: v{APP_DISPLAY_VERSION}
</p>
</InfoModal>
)}
<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="flex-1 order-0 justify-content-start align-items-center">
<div className="d-none d-md-flex flex-1 order-0 justify-content-start align-items-center">
<div className="text-small text-start text-secondary">
<Trans i18nKey="footer.warning">
This is pre-alpha software.
@ -84,12 +82,13 @@ export default function Footer() {
</Trans>
</div>
</div>
<div className="d-flex order-1 flex-1 flex-grow-0 justify-content-center align-items-center pt-0">
<div className="d-flex order-1 flex-1 flex-grow-0 justify-content-center align-items-center">
{cheatsheetEnabled && (
<div className="order-1 order-sm-0">
<>
<Cheatsheet show={showCheatsheet} onHide={() => setShowCheatsheet(false)} />
<rb.Nav.Item>
<rb.Button
type="button"
variant="link"
className="cheatsheet-link nav-link text-start border-0 px-2"
onClick={() => setShowCheatsheet(true)}
@ -100,7 +99,7 @@ export default function Footer() {
</div>
</rb.Button>
</rb.Nav.Item>
</div>
</>
)}
</div>
<div className="d-flex flex-1 order-2 justify-content-end align-items-center gap-1">

View file

@ -2,10 +2,6 @@
background-color: rgba(0, 0, 0, 0.5) !important;
}
:global .modal-dialog {
max-width: 45rem !important;
}
.modal :global .modal-content {
background-color: var(--bs-body-bg) !important;
border-radius: 1rem !important;

View file

@ -59,6 +59,7 @@ export default function JarSelectorModal({
centered={true}
animation={true}
className={styles.modal}
size="lg"
>
<rb.Modal.Header className={styles.modalHeader}>
<rb.Modal.Title className={styles.modalTitle}>

View file

@ -24,7 +24,7 @@ const Jars = ({ accountBalances, totalBalance, onClick }: JarsProps) => {
return (
<div className="d-flex flex-column align-items-center gap-5">
<rb.OverlayTrigger
placement="right"
placement="bottom"
overlay={
<rb.Popover>
<rb.Popover.Body>{t('current_wallet.jars_title_popover')}</rb.Popover.Body>

View file

@ -4,10 +4,68 @@ import { useTranslation } from 'react-i18next'
import styles from './Modal.module.css'
import Sprite from './Sprite'
export interface ConfirmModalProps {
type BaseModalProps = {
isShown: boolean
title: ReactNode | string
onCancel: () => void
backdrop?: rb.ModalProps['backdrop']
size?: rb.ModalProps['size']
}
const BaseModal = ({
isShown,
title,
children,
onCancel,
size,
backdrop = 'static',
}: PropsWithChildren<BaseModalProps>) => {
return (
<rb.Modal
show={isShown}
keyboard={true}
onEscapeKeyDown={() => onCancel()}
onHide={() => onCancel()}
centered={true}
animation={true}
backdrop={backdrop}
size={size}
className={styles.modal}
>
<rb.Modal.Header className={styles['modal-header']}>
<rb.Modal.Title className={styles['modal-title']}>{title}</rb.Modal.Title>
</rb.Modal.Header>
{children}
</rb.Modal>
)
}
export type InfoModalProps = Omit<BaseModalProps, 'backdrop'> & {
onSubmit: () => void
submitButtonText: React.ReactNode | string
}
const InfoModal = ({
isShown,
title,
children,
onCancel,
onSubmit,
submitButtonText,
size,
}: PropsWithChildren<InfoModalProps>) => {
return (
<BaseModal isShown={isShown} title={title} onCancel={onCancel} backdrop={true} size={size}>
<rb.Modal.Body className={styles['modal-body']}>{children}</rb.Modal.Body>
<rb.Modal.Footer className={styles['modal-footer']}>
<rb.Button variant="outline-dark" onClick={() => onSubmit()}>
{submitButtonText}
</rb.Button>
</rb.Modal.Footer>
</BaseModal>
)
}
export type ConfirmModalProps = Omit<InfoModalProps, 'onSubmit' | 'submitButtonText'> & {
onConfirm: () => void
}
@ -15,18 +73,7 @@ const ConfirmModal = ({ isShown, title, children, onCancel, onConfirm }: PropsWi
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>
<BaseModal isShown={isShown} title={title} onCancel={onCancel}>
<rb.Modal.Body className={styles['modal-body']}>{children}</rb.Modal.Body>
<rb.Modal.Footer className={styles['modal-footer']}>
<rb.Button
@ -41,8 +88,8 @@ const ConfirmModal = ({ isShown, title, children, onCancel, onConfirm }: PropsWi
{t('modal.confirm_button_accept')}
</rb.Button>
</rb.Modal.Footer>
</rb.Modal>
</BaseModal>
)
}
export { ConfirmModal }
export { InfoModal, ConfirmModal }

View file

@ -18,3 +18,11 @@
:global(.jam-reload-wallet-info-in-progress) .loadingIndicator {
display: inline-block !important;
}
.offcanvasBody {
font-size: calc(var(--bs-body-font-size) * 1.5);
}
.offcanvasBody :global(.nav-link) {
width: 100%;
}

View file

@ -11,9 +11,9 @@ import { CurrentWallet, useCurrentWallet, useCurrentWalletInfo } from '../contex
import { useServiceInfo, useSessionConnectionError } from '../context/ServiceInfoContext'
import { routes } from '../constants/routes'
import { AmountSats } from '../libs/JmWalletApi'
import { isDebugFeatureEnabled } from '../constants/debugFeatures'
import styles from './Navbar.module.css'
import { isDebugFeatureEnabled } from '../constants/debugFeatures'
const BalanceLoadingIndicator = () => {
return (
@ -174,21 +174,23 @@ const TrailingNav = ({ joiningRoute, onClick }: TrailingNavProps) => {
return (
<rb.Nav className="justify-content-center align-items-stretch">
{joiningRoute && (
<rb.Nav.Item className="d-flex align-items-center pe-2">
<div className="d-flex align-items-center px-0">
<NavLink to={joiningRoute} onClick={onClick} className="nav-link">
<rb.Navbar.Text className="d-md-none">{t('navbar.joining_in_progress')}</rb.Navbar.Text>
<JoiningIndicator
isOn={true}
className="navbar-text text-success"
title={t('navbar.joining_in_progress')}
/>
</NavLink>
</div>
<rb.Nav.Item className="d-flex align-items-center">
<NavLink
to={joiningRoute}
onClick={onClick}
className="nav-link d-flex align-items-center justify-content-center"
>
<rb.Navbar.Text className="d-md-none">{t('navbar.joining_in_progress')}</rb.Navbar.Text>
<JoiningIndicator
isOn={true}
className="navbar-text text-success"
title={t('navbar.joining_in_progress')}
/>
</NavLink>
</rb.Nav.Item>
)}
{isDebugFeatureEnabled('fastThemeToggle') && (
<rb.Nav.Item className="d-none d-md-flex align-items-center pe-2">
<rb.Nav.Item className="d-none d-md-flex align-items-center justify-content-center">
<FastThemeToggle />
</rb.Nav.Item>
)}
@ -197,7 +199,7 @@ const TrailingNav = ({ joiningRoute, onClick }: TrailingNavProps) => {
to={routes.settings}
onClick={onClick}
className={({ isActive }) =>
'nav-link d-flex align-items-center justify-content-center px-0' + (isActive ? ' active' : '')
'nav-link d-flex align-items-center justify-content-center' + (isActive ? ' active' : '')
}
>
<Sprite symbol="gear" width="30" height="30" className="d-none d-md-inline-block" />
@ -308,25 +310,25 @@ export default function Navbar() {
<rb.Offcanvas.Header>
<rb.Offcanvas.Title>{t('navbar.title')}</rb.Offcanvas.Title>
</rb.Offcanvas.Header>
<rb.Offcanvas.Body>
<rb.Offcanvas.Body className={styles.offcanvasBody}>
<rb.Nav className="ms-auto">
<rb.Nav.Item>
<Link
<NavLink
to={routes.createWallet}
onClick={() => isExpanded && setIsExpanded(false)}
className="nav-link"
className="nav-link d-flex align-items-center justify-content-center"
>
{t('navbar.button_create_wallet')}
</Link>
</NavLink>
</rb.Nav.Item>
<rb.Nav.Item>
<Link
<NavLink
to={routes.importWallet}
onClick={() => isExpanded && setIsExpanded(false)}
className="nav-link"
className="nav-link d-flex align-items-center justify-content-center"
>
{t('navbar.button_import_wallet')}
</Link>
</NavLink>
</rb.Nav.Item>
</rb.Nav>
</rb.Offcanvas.Body>
@ -362,7 +364,7 @@ export default function Navbar() {
<rb.Offcanvas.Header>
<rb.Offcanvas.Title>{t('navbar.title')}</rb.Offcanvas.Title>
</rb.Offcanvas.Header>
<rb.Offcanvas.Body>
<rb.Offcanvas.Body className={styles.offcanvasBody}>
<CenterNav
makerRunning={makerRunning}
schedulerRunning={schedulerRunning}

View file

@ -61,7 +61,7 @@
}
.address {
font-size: 0.75rem;
font-size: 0.8rem;
padding: 1rem 0 1rem;
}
@ -73,7 +73,7 @@
align-items: center;
gap: 2rem;
color: var(--bs-body-color);
margin-bottom: 3rem;
margin-bottom: 2rem;
}
.jarsPlaceholder {

View file

@ -14,6 +14,7 @@ import { ShareButton, checkIsWebShareAPISupported } from './ShareButton'
import { SelectableJar, jarFillLevel } from './jars/Jar'
import styles from './Receive.module.css'
import Accordion from './Accordion'
import { isDevMode } from '../constants/debugFeatures'
interface ReceiveProps {
wallet: CurrentWallet
@ -33,8 +34,8 @@ export default function Receive({ wallet }: ReceiveProps) {
const [amount, setAmount] = useState('')
const [selectedJarIndex, setSelectedJarIndex] = useState(parseInt(location.state?.account, 10) || 0)
const [addressCount, setAddressCount] = useState(0)
const isFormEnabled = useMemo(() => serviceInfo?.rescanning !== true, [serviceInfo])
const [addressCount, setAddressCount] = useState(0)
const [validated, setValidated] = useState(false)
const sortedAccountBalances = useMemo(() => {
@ -85,51 +86,83 @@ export default function Receive({ wallet }: ReceiveProps) {
<PageTitle title={t('receive.title')} subtitle={t('receive.subtitle')} />
{alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
{serviceInfo?.rescanning === true && <rb.Alert variant="success">{t('app.alert_rescan_in_progress')}</rb.Alert>}
<div className={`mb-4 ${styles.cardContainer}`}>
<rb.Card className={`${settings.theme === 'light' ? 'pt-2' : 'pt-4'} pb-4`}>
<div className={styles['qr-container']}>
{!isLoading && address && <BitcoinQR address={address} amount={parseInt(amount, 10) || 0} />}
{(isLoading || !address) && (
<rb.Placeholder as="div" animation="wave" className={styles['receive-placeholder-qr-container']}>
<rb.Placeholder className={styles['receive-placeholder-qr']} />
</rb.Placeholder>
)}
</div>
<rb.Card.Body
className={`${settings.theme === 'light' ? 'pt-0' : 'pt-3'} pb-0 d-flex flex-column align-items-center`}
>
{!address ? (
<rb.Placeholder as="p" animation="wave" className={styles['receive-placeholder-container']}>
<rb.Placeholder xs={12} sm={10} md={8} className={styles['receive-placeholder']} />
</rb.Placeholder>
) : (
<rb.Card.Text className={`${styles['address']} text-center slashed-zeroes`}>{address}</rb.Card.Text>
)}
<div className="d-flex justify-content-center gap-3 w-75">
<CopyButton
className="btn btn-outline-dark flex-1"
disabled={!address || isLoading}
value={address}
text={
<>
<Sprite symbol="copy" className="me-1" width="24" height="24" />
{t('receive.button_copy_address')}
</>
}
successText={
<>
<Sprite color="green" symbol="checkmark" className="me-1" width="24" height="24" />
{t('receive.text_copy_address_confirmed')}
</>
}
/>
{checkIsWebShareAPISupported() && <ShareButton value={address} className="flex-1" />}
</div>
</rb.Card.Body>
</rb.Card>
</div>
<rb.Form className={styles.receiveForm} onSubmit={onSubmit} validated={validated} noValidate>
<div className={`mb-4 ${styles.cardContainer}`}>
<rb.Card className={`${settings.theme === 'light' ? 'pt-2' : 'pt-4'} pb-4`}>
<div className={styles['qr-container']}>
{!isLoading && address && <BitcoinQR address={address} amount={parseInt(amount, 10) || 0} />}
{(isLoading || !address) && (
<rb.Placeholder as="div" animation="wave" className={styles['receive-placeholder-qr-container']}>
<rb.Placeholder className={styles['receive-placeholder-qr']} />
</rb.Placeholder>
)}
</div>
<rb.Card.Body
className={`${settings.theme === 'light' ? 'pt-0' : 'pt-3'} p-0 d-flex flex-column align-items-center`}
>
{!address ? (
<rb.Placeholder as="p" animation="wave" className={styles['receive-placeholder-container']}>
<rb.Placeholder xs={12} sm={10} md={8} className={styles['receive-placeholder']} />
</rb.Placeholder>
) : (
<rb.Card.Text className={`${styles.address} text-center slashed-zeroes break-word`}>
{address}
</rb.Card.Text>
)}
<div className="d-flex flex-column flex-sm-row justify-content-center gap-2 px-2">
<rb.Button
variant="outline-dark"
type="submit"
disabled={!isFormEnabled || isLoading}
className="d-flex justify-content-center align-items-center"
>
{isLoading ? (
<>
<rb.Spinner
as="span"
animation="border"
size="sm"
role="status"
aria-hidden="true"
className="me-2"
/>
{t('receive.text_getting_address')}
</>
) : (
<>
<Sprite symbol="refresh" className="me-2" width="24" height="24" />
{t('receive.button_new_address')}
</>
)}
</rb.Button>
<CopyButton
className="btn btn-outline-dark flex-1"
disabled={!address || isLoading}
value={address}
text={
<>
<Sprite symbol="copy" className="me-1" width="24" height="24" />
{t('receive.button_copy_address')}
</>
}
successText={
<>
<Sprite color="green" symbol="checkmark" className="me-1" width="24" height="24" />
{t('receive.text_copy_address_confirmed')}
</>
}
/>
{!isDevMode() ? (
checkIsWebShareAPISupported() && <ShareButton value={address} className="flex-1" />
) : (
<ShareButton value={address} className="flex-1" disabled={!checkIsWebShareAPISupported()} />
)}
</div>
</rb.Card.Body>
</rb.Card>
</div>
<Accordion title={t('receive.button_settings')} disabled={!isFormEnabled}>
<div className="mb-4">
{!walletInfo || sortedAccountBalances.length === 0 ? (
@ -157,7 +190,7 @@ export default function Receive({ wallet }: ReceiveProps) {
)}
<rb.Form.Group controlId="amountSats">
<rb.Form.Label>{t('receive.label_amount')}</rb.Form.Label>
<rb.InputGroup>
<rb.InputGroup hasValidation={true}>
<rb.InputGroup.Text id="amountSats-addon1" className={styles.inputGroupText}>
<Sprite symbol="sats" width="24" height="24" />
</rb.InputGroup.Text>
@ -173,32 +206,13 @@ export default function Receive({ wallet }: ReceiveProps) {
min={0}
step={1}
/>
<rb.Form.Control.Feedback type="invalid">
{t('receive.feedback_invalid_amount')}
</rb.Form.Control.Feedback>
</rb.InputGroup>
<rb.Form.Control.Feedback type="invalid">{t('receive.feedback_invalid_amount')}</rb.Form.Control.Feedback>
</rb.Form.Group>
</div>
</Accordion>
<div className="d-flex justify-content-center">
<rb.Button
variant="outline-dark"
type="submit"
disabled={!isFormEnabled || isLoading}
className="d-flex justify-content-center align-items-center"
>
{isLoading ? (
<>
<rb.Spinner as="span" animation="border" size="sm" role="status" aria-hidden="true" className="me-2" />
{t('receive.text_getting_address')}
</>
) : (
<>
<Sprite symbol="refresh" className="me-2" width="24" height="24" />
{t('receive.button_new_address')}
</>
)}
</rb.Button>
</div>
</rb.Form>
</div>
)

View file

@ -5,12 +5,11 @@ const checkIsWebShareAPISupported = () => {
return !!navigator.share
}
type ShareButtonProps = {
type ShareButtonProps = Omit<rb.ButtonProps, 'value' | 'onClick' | 'type'> & {
value: string
className?: string
}
const ShareButton = ({ value, className }: ShareButtonProps) => {
const ShareButton = ({ value, ...buttonProps }: ShareButtonProps) => {
const handleShare = async () => {
if (!checkIsWebShareAPISupported()) {
console.error('Sharing failed: Web Share API not supported.')
@ -27,7 +26,7 @@ const ShareButton = ({ value, className }: ShareButtonProps) => {
}
return (
<rb.Button variant="outline-dark" className={className} onClick={handleShare}>
<rb.Button type="button" variant="outline-dark" onClick={handleShare} {...buttonProps}>
<div className="d-flex align-items-center justify-content-center">
<Sprite symbol="share" className="me-1" width="20" height="20" />
Share

View file

@ -567,35 +567,6 @@ h2 {
text-decoration: underline;
}
/* Alpha Warning */
.warning-card-wrapper {
position: fixed;
height: 100%;
width: 100%;
z-index: 1000;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(1px);
}
.warning-card {
position: fixed;
top: 50%;
left: 50%;
width: 75%;
}
@media only screen and (min-width: 576px) {
.warning-card {
width: 50%;
}
}
@media only screen and (min-width: 992px) {
.warning-card {
width: 25%;
}
}
.btn:disabled:hover {
cursor: not-allowed;
}