chore: pass versions to footer

This commit is contained in:
theborakompanioni 2026-01-17 18:13:28 +01:00
parent 4f1ad18e9f
commit 428cd8d6cd
No known key found for this signature in database
GPG key ID: E8070AF0053AAC0D
8 changed files with 66 additions and 30 deletions

View file

@ -88,7 +88,9 @@ const withTheme = (Story: React.ComponentType, context: GlobalContext) => {
)
}
const withQueryClient = (Story: React.ComponentType) => {
// Use only when necessary! Try to pass data from queries to
// components so they can be tested independently from an API.
export const withQueryClient = (Story: React.ComponentType) => {
const queryClient = new QueryClient()
return (
<QueryClientProvider client={queryClient}>
@ -98,6 +100,6 @@ const withQueryClient = (Story: React.ComponentType) => {
}
// export decorators for storybook to wrap your stories in
export const decorators = [withTheme, withI18next, withQueryClient]
export const decorators = [withTheme, withI18next]
export default preview

View file

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useState, type ComponentProps } from 'react'
import { AlertTriangleIcon, BlocksIcon, BookOpenIcon, FileQuestionMarkIcon } from 'lucide-react'
import { useTranslation, Trans } from 'react-i18next'
import { useStore } from 'zustand'
@ -9,23 +9,27 @@ import { Dialog, DialogContent, DialogFooter } from '@/components/ui/dialog'
import { Cheatsheet } from '@/components/ui/jam/Cheatsheet'
import { JmWebsocketInfo } from '@/components/ui/jam/JmWebsocketInfo'
import { useCheatsheet } from '@/hooks/useCheatsheet'
import { useJmInfo } from '@/hooks/useJmInfo'
import type { JmWebsocket } from '@/hooks/useJmWebsocket'
import { toSemVer } from '@/lib/utils'
import type { SemVer } from '@/lib/utils'
import { jmSessionStore } from '@/store/jmSessionStore'
import packageInfo from '../../../package.json'
const APP_DISPLAY_VERSION = (() => {
const version = toSemVer(packageInfo.version)
return version.raw
})()
type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
[Property in Key]-?: Type[Property]
}
const BetaWarningModal = ({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) => {
type BetaWarningModalProps = WithRequiredProperty<
Omit<ComponentProps<typeof Dialog>, 'children'>,
'open' | 'onOpenChange'
> & {
jamVersion: SemVer
joinmarketVersion?: SemVer
}
const BetaWarningModal = ({ jamVersion, joinmarketVersion, ...dialogProps }: BetaWarningModalProps) => {
const { t } = useTranslation()
const { version } = useJmInfo()
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog {...dialogProps}>
<DialogContent className="pt-12" showCloseButton={true}>
<Alert variant="default">
<AlertTriangleIcon />
@ -34,12 +38,12 @@ const BetaWarningModal = ({ open, onOpenChange }: { open: boolean; onOpenChange:
</Alert>
<p className="text-muted-foreground text-sm">
JoinMarket: v{version?.raw || '_unknown'}
JoinMarket: v{joinmarketVersion?.raw || '_unknown'}
<br />
Jam: v{APP_DISPLAY_VERSION}
Jam: v{jamVersion.raw || '_unknown'}
</p>
<DialogFooter>
<Button onClick={() => onOpenChange(false)}>{t('footer.warning_alert_button_ok')}</Button>
<Button onClick={() => dialogProps.onOpenChange(false)}>{t('footer.warning_alert_button_ok')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
@ -47,11 +51,12 @@ const BetaWarningModal = ({ open, onOpenChange }: { open: boolean; onOpenChange:
}
type JmWebsocketInfo = Pick<JmWebsocket, 'isOpen' | 'isAuthenticated'>
interface AppFooterProps {
type AppFooterProps = Pick<BetaWarningModalProps, 'jamVersion' | 'joinmarketVersion'> & {
websocketInfo?: JmWebsocketInfo
}
export function AppFooter({ websocketInfo }: AppFooterProps) {
export function AppFooter({ websocketInfo, jamVersion, joinmarketVersion }: AppFooterProps) {
const { t } = useTranslation()
const cheatsheet = useCheatsheet()
@ -63,7 +68,12 @@ export function AppFooter({ websocketInfo }: AppFooterProps) {
<>
<footer className="flex items-center justify-between gap-2 p-4">
<div className="hidden flex-1 text-xs sm:block">
<BetaWarningModal open={isShowBetaWarning} onOpenChange={setShowBetaWarning} />
<BetaWarningModal
open={isShowBetaWarning}
onOpenChange={setShowBetaWarning}
joinmarketVersion={joinmarketVersion}
jamVersion={jamVersion}
/>
<Trans i18nKey="footer.warning">
This is pre-alpha software.
<Button variant="link" size="sm" className="h-auto p-0 text-xs" onClick={() => setShowBetaWarning(true)}>
@ -105,7 +115,7 @@ export function AppFooter({ websocketInfo }: AppFooterProps) {
rel="noopener noreferrer"
className="text-right underline opacity-80"
>
v{APP_DISPLAY_VERSION}
v{jamVersion?.raw}
</a>
{blockHeight && (
<span className="flex items-center gap-1">

View file

@ -7,8 +7,10 @@ import { AppFooter } from '@/components/layout/AppFooter'
import { AppNavbar } from '@/components/layout/AppNavbar'
import { Sidebar, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
import { useSidebar } from '@/components/ui/use-sidebar'
import { APP_DISPLAY_VERSION } from '@/constants/jam'
import { useJamDisplayContext } from '@/context/JamDisplayContext'
import { useJamWalletInfoContext } from '@/context/JamWalletInfoContext'
import { useJmInfo } from '@/hooks/useJmInfo'
import { useJmWebsocket } from '@/hooks/useJmWebsocket'
import { jmSessionStore } from '@/store/jmSessionStore'
import { AppSidebar } from './AppSidebar'
@ -26,6 +28,8 @@ export function LayoutInner({ onLogout, onLockWallet, children }: LayoutInnerPro
const navigate = useNavigate()
const jmSessionState = useStore(jmSessionStore, (state) => state.state)
const { version: joinmarketVersion } = useJmInfo()
const { resolvedTheme, setTheme } = useTheme()
const toggleTheme = () => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
@ -53,7 +57,7 @@ export function LayoutInner({ onLogout, onLockWallet, children }: LayoutInnerPro
sidebarInfo={sidebarContext}
/>
<main className="flex-1">{children}</main>
<AppFooter websocketInfo={websocket} />
<AppFooter websocketInfo={websocket} jamVersion={APP_DISPLAY_VERSION} joinmarketVersion={joinmarketVersion} />
</div>
)
}

View file

@ -1,7 +1,12 @@
import { percentageToFactor } from '@/lib/utils'
import { percentageToFactor, toSemVer } from '@/lib/utils'
import type { AmountSats, Milliseconds } from '@/types/global'
import { version as packageInfoVersion } from '../../package.json'
import { JM_API_AUTH_TOKEN_EXPIRY, JM_DUST_THRESHOLD } from './jm'
export const APP_DISPLAY_VERSION = (() => {
return toSemVer(packageInfoVersion)
})()
export const TX_FEES_FACTOR_MIN = 0 // 0%
/**

View file

@ -1,19 +1,25 @@
import { versionOptions } from '@joinmarket-webui/joinmarket-api-ts/@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import type { ErrorMessage, VersionResponse } from '@joinmarket-webui/joinmarket-api-ts/jm'
import { useQuery, type UseQueryResult } from '@tanstack/react-query'
import { useApiClient } from '@/hooks/useApiClient'
import { toSemVer } from '@/lib/utils'
import { toSemVer, type SemVer } from '@/lib/utils'
export const useJmInfo = () => {
type UseJmInfoResult = {
version: SemVer | undefined
queryResult: UseQueryResult<VersionResponse, ErrorMessage>
}
export function useJmInfo(): UseJmInfoResult {
const client = useApiClient()
const { data, isPending } = useQuery({
const queryResult = useQuery({
...versionOptions({ client }),
staleTime: Infinity,
gcTime: Infinity,
})
return {
version: data ? toSemVer(data.version) : undefined,
isLoading: isPending,
version: queryResult.data ? toSemVer(queryResult.data.version) : undefined,
queryResult,
}
}

View file

@ -31,6 +31,7 @@ type UseUtxosResult = {
utxos: Utxo[]
queryResult: UseQueryResult<ListUtxosResponse, ErrorMessage>
}
interface UseUtxosProps {
walletFileName: WalletFileName
}

View file

@ -103,7 +103,7 @@ export const factorToPercentage = (val: number, precision = 6) => {
return Number((val * 100).toFixed(precision))
}
type SemVer = { major: number; minor: number; patch: number; raw?: string }
export type SemVer = { major: number; minor: number; patch: number; raw?: string }
export const UNKNOWN_VERSION: SemVer = { major: 0, minor: 0, patch: 0, raw: 'unknown' }

View file

@ -1,6 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { MemoryRouter } from 'react-router-dom'
import { AppFooter } from '@/components/layout/AppFooter'
import { APP_DISPLAY_VERSION } from '@/constants/jam'
import { toSemVer } from '@/lib/utils'
const meta: Meta<typeof AppFooter> = {
title: 'Layout/AppFooter',
@ -19,5 +21,11 @@ export default meta
type Story = StoryObj<typeof AppFooter>
export const Default: Story = {
render: () => <AppFooter websocketInfo={{ isOpen: true, isAuthenticated: true }} />,
render: () => (
<AppFooter
websocketInfo={{ isOpen: true, isAuthenticated: true }}
jamVersion={APP_DISPLAY_VERSION}
joinmarketVersion={toSemVer('0.9.12')}
/>
),
}