chore(receive): increase qrcode size + fade in

This commit is contained in:
theborakompanioni 2026-01-06 20:56:22 +01:00
parent b027b22262
commit 505c02dc00
No known key found for this signature in database
GPG key ID: E8070AF0053AAC0D
5 changed files with 68 additions and 31 deletions

View file

@ -1,7 +1,7 @@
import { createContext, useContext } from 'react'
import type { ReactNode } from 'react'
import type { ErrorMessage } from '@joinmarket-webui/joinmarket-api-ts/jm'
import type { Currency } from '@/types/global'
import type { AmountSats, Currency } from '@/types/global'
export type JarColor = '#e2b86a' | '#3b5ba9' | '#c94f7c' | '#a67c52' | '#7c3fa6'
@ -19,7 +19,7 @@ export const jarTemplates: Array<{
export interface Jar {
name: string
color: JarColor
balance: number
balance: AmountSats
account?: string
}
@ -32,7 +32,7 @@ export interface DisplayModeContextType {
formatAmount: (amount: number) => string
currencySymbol: (size?: 'sm' | 'lg') => ReactNode
jars: Jar[]
totalBalance: number
totalBalance: AmountSats
walletName: string | null
isLoading: boolean
error: Error | ErrorMessage | null

View file

@ -3,16 +3,18 @@ import { DownloadIcon } from 'lucide-react'
import QRCode from 'qrcode'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import { satsToBtc } from '@/lib/utils'
import { cn, satsToBtc } from '@/lib/utils'
import type { AmountSats, BitcoinAddress } from '@/types/global'
interface BitcoinQRProps {
address: string
amount?: number
className?: string
address: BitcoinAddress
amount?: AmountSats
errorCorrectionLevel?: QRCode.QRCodeErrorCorrectionLevel
width?: number
}
export const BitcoinQR = ({ address, amount, errorCorrectionLevel = 'H', width = 260 }: BitcoinQRProps) => {
export const BitcoinQR = ({ className, address, amount, errorCorrectionLevel = 'H', width = 260 }: BitcoinQRProps) => {
const { t } = useTranslation()
const [data, setData] = useState<string>()
const [image, setImage] = useState<string>()
@ -47,7 +49,10 @@ export const BitcoinQR = ({ address, amount, errorCorrectionLevel = 'H', width =
}
return (
<div className="group/qrcode relative flex items-center justify-center" style={{ height: width, width: width }}>
<div
className={cn('group/qrcode relative flex items-center justify-center', className)}
style={{ height: width, width: width }}
>
{image && (
<>
<img
@ -62,7 +67,7 @@ export const BitcoinQR = ({ address, amount, errorCorrectionLevel = 'H', width =
onClick={downloadQR}
aria-label={t('receive.button_download_qr')}
>
<DownloadIcon size={18} className="animate-bounce" />
<DownloadIcon className="animate-bounce" />
{t('receive.button_download_qr')}
</Button>
</>

View file

@ -1,11 +1,12 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import { useState, useEffect, useCallback } from 'react'
import { getaddressOptions } from '@joinmarket-webui/joinmarket-api-ts/@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { Copy, CopyCheck, RefreshCw, Share } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { useApiClient } from '@/hooks/useApiClient'
import { btcToSats, satsToBtc, type WalletFileName } from '@/lib/utils'
import { btcToSats, cn, satsToBtc, type WalletFileName } from '@/lib/utils'
import type { AmountSats, BitcoinAddress } from '@/types/global'
import { useJamDisplayContext } from '../layout/display-mode-context'
import { SelectableJar } from '../ui/SelectableJar'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../ui/accordion'
@ -14,6 +15,8 @@ import { Skeleton } from '../ui/skeleton'
import { BitcoinAmountInput } from './BitcoinAmountInput'
import { BitcoinQR } from './BitcoinQR'
const QRCODE_WIDTH = 320 // "h-[320px] w-[320px]" <- Comment for tailwind importer (ADAPT THE COMMENT IF YOU CHANGE THE VALUE)
interface ReceiveProps {
walletFileName: WalletFileName
}
@ -21,8 +24,8 @@ interface ReceiveProps {
export const Receive = ({ walletFileName }: ReceiveProps) => {
const { t } = useTranslation()
const [selectedJarIndex, setSelectedJarIndex] = useState(0)
const [amount, setAmount] = useState<number | undefined>()
const [bitcoinAddress, setBitcoinAddress] = useState<string | undefined>()
const [amount, setAmount] = useState<AmountSats | undefined>()
const [bitcoinAddress, setBitcoinAddress] = useState<BitcoinAddress | undefined>()
const [copied, setCopied] = useState(false)
const { jars, currency, isPrivate, totalBalance, toggleCurrencyUnit } = useJamDisplayContext()
@ -51,10 +54,6 @@ export const Receive = ({ walletFileName }: ReceiveProps) => {
}
}, [getAddressQuery.error, t])
const isQrLoading = useMemo(() => {
return getAddressQuery.isFetching
}, [getAddressQuery.isFetching])
const copyToClipboard = () => {
if (bitcoinAddress) {
navigator.clipboard.writeText(bitcoinAddress)
@ -140,35 +139,66 @@ export const Receive = ({ walletFileName }: ReceiveProps) => {
<p className="text-muted-foreground mb-4 text-sm">{t('receive.subtitle')}</p>
<div className="flex w-full flex-col items-center justify-center space-y-2 rounded-lg border p-8">
{isQrLoading ? (
<Skeleton className="h-[260px] w-[260px]" />
{getAddressQuery.isFetching ? (
<Skeleton className={`h-[${QRCODE_WIDTH}px] w-[${QRCODE_WIDTH}px]`} />
) : bitcoinAddress ? (
<BitcoinQR address={bitcoinAddress} amount={amount} width={260} />
<BitcoinQR
className="animate-in fade-in duration-1000"
address={bitcoinAddress}
amount={amount}
width={QRCODE_WIDTH}
/>
) : (
<div className="flex h-[260px] w-[260px] animate-pulse items-center justify-center border text-gray-500">
<div
className={cn(
'flex animate-pulse items-center justify-center border text-gray-500',
`h-[${QRCODE_WIDTH}px] w-[${QRCODE_WIDTH}px]`,
)}
>
{t('receive.error_loading_address_failed')}
</div>
)}
{isQrLoading ? (
<Skeleton className="h-4 w-[65%]" />
{getAddressQuery.isFetching ? (
<Skeleton className="h-5 w-[65%]" />
) : (
<p className="text-center font-mono text-xs break-all select-all">{bitcoinAddress}</p>
<p className="animate-in fade-in text-center font-mono text-sm break-all duration-1000 select-all">
{bitcoinAddress}
</p>
)}
<div className="mt-4 flex gap-2">
<Button variant="outline" size="sm" onClick={getNewAddress} disabled={getAddressQuery.isFetching}>
<RefreshCw className="mr-1" />
{getAddressQuery.isFetching ? t('receive.text_getting_address') : t('receive.button_new_address')}
{getAddressQuery.isFetching ? (
<>
<RefreshCw className="animate-spin motion-reduce:hidden" />
{t('receive.text_getting_address')}
</>
) : (
<>
<RefreshCw />
{t('receive.button_new_address')}
</>
)}
</Button>
<Button variant="outline" size="sm" onClick={copyToClipboard}>
<Button
variant="outline"
size="sm"
onClick={copyToClipboard}
disabled={getAddressQuery.isFetching || !bitcoinAddress}
>
{copied ? <CopyCheck /> : <Copy />}
{copied ? t('global.button_copy_text_confirmed') : t('global.button_copy_text')}
</Button>
{'share' in navigator && (
<Button variant="outline" size="sm" onClick={shareAddress}>
<Button
variant="outline"
size="sm"
onClick={shareAddress}
disabled={getAddressQuery.isFetching || !bitcoinAddress}
>
<Share />
{t('receive.button_share_address')}
</Button>
@ -205,7 +235,7 @@ export const Receive = ({ walletFileName }: ReceiveProps) => {
onChange={handleAmountChange}
toggleCurrencyUnit={toggleCurrencyUnit}
isPrivate={isPrivate}
disabled={isQrLoading || !bitcoinAddress}
disabled={getAddressQuery.isFetching || !bitcoinAddress}
/>
</div>
</AccordionContent>

View file

@ -1,11 +1,12 @@
import type { AmountSats } from '@/types/global'
import { CurrencySymbol } from '../CurrencySymbol'
import { JarIcon } from './JarIcon'
interface SelectableJarProps {
name: string
color: string
balance: number
totalBalance: number
balance: AmountSats
totalBalance: AmountSats
isSelected: boolean
onClick: () => void
}

View file

@ -1,5 +1,6 @@
export type Currency = 'sats' | 'btc'
export type AmountSats = number
export type BitcoinAddress = string
export type Milliseconds = number
export type Seconds = number