mirror of
https://github.com/joinmarket-webui/jam.git
synced 2026-08-19 13:18:30 +02:00
chore(receive): increase qrcode size + fade in
This commit is contained in:
parent
b027b22262
commit
505c02dc00
5 changed files with 68 additions and 31 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue