chore(ui): improve footer on small screens (#1243)

* chore(footer): align blockheight

* ui(footer): improve footer on small screens

* chore(storybook): add BetaWarningDialog story

* review: remove flex container of footer warning
This commit is contained in:
Thebora Kompanioni 2026-05-17 21:38:08 +02:00 committed by GitHub
parent 0eea853109
commit ee5770eeeb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 125 additions and 54 deletions

View file

@ -1,16 +1,15 @@
import { useState, type ComponentProps } from 'react'
import { BlocksIcon, BookOpenIcon, FileQuestionMarkIcon, ScrollTextIcon } from 'lucide-react'
import { useTranslation, Trans } from 'react-i18next'
import { useStore } from 'zustand'
import { Button } from '@/components/ui/button'
import { JmWebsocketInfo } from '@/components/ui/jam/JmWebsocketInfo'
import type { JmWebsocket } from '@/hooks/useJmWebsocket'
import { jmSessionStore } from '@/store/jmSessionStore'
import { BetaWarningDialog } from './footer/BetaWarningDialog'
type JmWebsocketInfo = Pick<JmWebsocket, 'isOpen' | 'isAuthenticated'>
type AppFooterProps = Pick<ComponentProps<typeof BetaWarningDialog>, 'jamVersion' | 'joinmarketVersion'> & {
blockHeight?: number
websocketInfo?: JmWebsocketInfo
onClickCheatsheet: () => void
onClickOrderbook: () => void
@ -18,6 +17,7 @@ type AppFooterProps = Pick<ComponentProps<typeof BetaWarningDialog>, 'jamVersion
}
export function AppFooter({
blockHeight,
websocketInfo,
jamVersion,
joinmarketVersion,
@ -27,7 +27,6 @@ export function AppFooter({
}: AppFooterProps) {
const { t } = useTranslation()
const blockHeight = useStore(jmSessionStore, (state) => state.state?.block_height)
const [isShowBetaWarning, setShowBetaWarning] = useState(false)
return (
@ -38,57 +37,65 @@ export function AppFooter({
joinmarketVersion={joinmarketVersion}
jamVersion={jamVersion}
/>
<footer className="flex items-center justify-between gap-2 p-4">
<div className="hidden flex-1 text-xs sm:block">
<Trans i18nKey="footer.warning">
This is pre-alpha software.
<Button
variant="link"
size="sm"
className="h-auto p-0 text-xs font-semibold"
onClick={() => setShowBetaWarning(true)}
>
Read this before using.
<footer>
<div className="flex flex-col items-center justify-between gap-8 px-4 py-12 sm:flex-row sm:gap-2 sm:p-4">
<div
className="flex flex-col justify-start gap-2 sm:flex-row sm:items-center sm:justify-center"
data-tour-id="footer-tools"
>
<Button variant="outline" size="sm" onClick={onClickCheatsheet} title={t('footer.cheatsheet')}>
<FileQuestionMarkIcon />
<span className="">{t('footer.cheatsheet')}</span>
</Button>
</Trans>
</div>
<div className="flex flex-1 items-center justify-start gap-2 sm:justify-center" data-tour-id="footer-tools">
<Button variant="outline" size="sm" onClick={onClickCheatsheet} title={t('footer.cheatsheet')}>
<FileQuestionMarkIcon />
<span className="hidden sm:inline-block">{t('footer.cheatsheet')}</span>
</Button>
<Button variant="outline" size="sm" onClick={onClickOrderbook} title={t('footer.orderbook')}>
<BookOpenIcon />
<span className="hidden sm:inline-block">{t('footer.orderbook')}</span>
</Button>
{onClickLogs && (
<Button variant="outline" size="sm" onClick={onClickLogs} title={t('footer.logs')}>
<ScrollTextIcon />
<span className="hidden sm:inline-block">{t('footer.logs')}</span>
<Button variant="outline" size="sm" onClick={onClickOrderbook} title={t('footer.orderbook')}>
<BookOpenIcon />
<span className="">{t('footer.orderbook')}</span>
</Button>
)}
</div>
<div className="flex flex-1 items-center justify-end gap-4 text-xs">
{websocketInfo !== undefined && (
<div className="flex flex-col gap-1">
<JmWebsocketInfo isOpen={websocketInfo.isOpen} isAuthenticated={websocketInfo.isAuthenticated} />
</div>
)}
<div className="flex flex-col gap-1">
{blockHeight && (
<span className="flex items-center gap-1">
<BlocksIcon className="size-4" /> {blockHeight}
</span>
{onClickLogs && (
<Button variant="outline" size="sm" onClick={onClickLogs} title={t('footer.logs')}>
<ScrollTextIcon />
<span className="">{t('footer.logs')}</span>
</Button>
)}
<a
href="https://github.com/joinmarket-webui/jam/tags"
target="_blank"
rel="noopener noreferrer"
className="text-right underline opacity-80"
>
v{jamVersion?.raw}
</a>
</div>
<div className="flex flex-1 flex-col items-center justify-end gap-4 break-normal sm:flex-row">
{websocketInfo !== undefined && (
<div className="order-first flex flex-col sm:order-last">
<JmWebsocketInfo isOpen={websocketInfo.isOpen} isAuthenticated={websocketInfo.isAuthenticated} />
</div>
)}
<div className="flex flex-col items-center gap-0.25 text-end text-xs sm:items-end">
{blockHeight && (
<span className="flex items-center gap-1">
<BlocksIcon className="size-3" />
<span className="break-keep slashed-zero tabular-nums select-all">
{blockHeight.toLocaleString()}
</span>
</span>
)}
<a
href="https://github.com/joinmarket-webui/jam/tags"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground underline"
>
v{jamVersion?.raw}
</a>
</div>
</div>
<div className="order-last flex-1 text-xs sm:order-first">
<Trans i18nKey="footer.warning">
This is pre-alpha software.
<Button
variant="link"
size="sm"
className="h-auto p-0 text-xs font-semibold"
onClick={() => setShowBetaWarning(true)}
>
Read this before using.
</Button>
</Trans>
</div>
</div>
</footer>

View file

@ -90,6 +90,7 @@ export function LayoutInner({ onLogout, onLockWallet, children }: LayoutInnerPro
<main className="flex-1">{children}</main>
<AppFooter
websocketInfo={websocket}
blockHeight={jmSession?.block_height}
jamVersion={APP_DISPLAY_VERSION}
joinmarketVersion={joinmarketVersion}
onClickCheatsheet={() => cheatsheet.onOpenChange(true)}

View file

@ -32,10 +32,10 @@ export const BetaWarningDialog = ({ jamVersion, joinmarketVersion, ...dialogProp
</DialogHeader>
<div className="items-end-safe text-sm">
<span className="text-muted-foreground">JoinMarket: </span>
<span className="font-mono font-semibold">v{joinmarketVersion?.raw || '_unknown'}</span>
<span className="font-mono font-semibold select-all">v{joinmarketVersion?.raw || '_unknown'}</span>
<br />
<span className="text-muted-foreground">Jam: </span>
<span className="font-mono font-semibold">v{jamVersion.raw || '_unknown'}</span>
<span className="font-mono font-semibold select-all">v{jamVersion.raw || '_unknown'}</span>
</div>
<DialogFooter>
<Button onClick={() => dialogProps.onOpenChange(false)}>{t('footer.warning_alert_button_ok')}</Button>

View file

@ -16,7 +16,8 @@ export const Default: Story = {
render: () => (
<AppFooter
websocketInfo={{ isOpen: true, isAuthenticated: true }}
jamVersion={APP_DISPLAY_VERSION}
blockHeight={21_000_000}
jamVersion={parseSemanticVersion('1.2.3')}
joinmarketVersion={parseSemanticVersion('0.9.12')}
onClickCheatsheet={() => alert('Cheatsheet clicked!')}
onClickOrderbook={() => alert('Orderbook clicked!')}
@ -24,3 +25,28 @@ export const Default: Story = {
/>
),
}
export const WithoutLogs: Story = {
render: () => (
<AppFooter
websocketInfo={{ isOpen: true, isAuthenticated: false }}
blockHeight={21_000_000}
jamVersion={APP_DISPLAY_VERSION}
joinmarketVersion={parseSemanticVersion('0.9.12')}
onClickCheatsheet={() => alert('Cheatsheet clicked!')}
onClickOrderbook={() => alert('Orderbook clicked!')}
/>
),
}
export const Minimal: Story = {
render: () => (
<AppFooter
websocketInfo={{ isOpen: false, isAuthenticated: false }}
blockHeight={21_000_000}
jamVersion={parseSemanticVersion('99.99.99-SNAPSHOT')}
onClickCheatsheet={() => alert('Cheatsheet clicked!')}
onClickOrderbook={() => alert('Orderbook clicked!')}
/>
),
}

View file

@ -0,0 +1,37 @@
import { useState } from 'react'
import type { Meta, StoryObj } from '@storybook/react-vite'
import { BetaWarningDialog } from '@/components/layout/footer/BetaWarningDialog'
import { Button } from '@/components/ui/button'
import { APP_DISPLAY_VERSION } from '@/constants/jam'
import { parseSemanticVersion } from '@/lib/utils'
const meta: Meta<typeof BetaWarningDialog> = {
title: 'Dialog/BetaWarningDialog',
component: BetaWarningDialog,
tags: ['autodocs'],
render: (args) => {
const [open, setOpen] = useState(args.defaultOpen || false)
return (
<>
<Button onClick={() => setOpen(true)}>Open</Button>
<BetaWarningDialog {...args} open={open} onOpenChange={() => setOpen(false)} />
</>
)
},
}
export default meta
type Story = StoryObj<typeof BetaWarningDialog>
export const Default: Story = {
args: {
jamVersion: APP_DISPLAY_VERSION,
joinmarketVersion: parseSemanticVersion('0.9.12'),
},
}
export const UnknownBackendVersion: Story = {
args: {
jamVersion: APP_DISPLAY_VERSION,
},
}