Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import { Trans } from 'react-i18next'
import { toast } from 'sonner'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { getJarBadgeVariant } from '@/components/ui/badge-variants'
import { Button } from '@/components/ui/button'
import { buttonVariants } from '@/components/ui/button-variants'
import { Card, CardContent } from '@/components/ui/card'
Expand Down Expand Up @@ -74,6 +76,8 @@ export function CreateFidelityBondDialogSteps({ wizard }: CreateFidelityBondDial
t,
} = wizard

const selectedJar = jarsWithUtxos.find((jar) => jar.jarIndex === selectedJarIndex)

switch (step) {
case 'select_date':
return (
Expand Down Expand Up @@ -414,9 +418,15 @@ export function CreateFidelityBondDialogSteps({ wizard }: CreateFidelityBondDial
</div>
<div className="bg-muted/50 rounded-lg p-4">
<p className="text-muted-foreground mb-1 text-xs">{t('earn.fidelity_bond.review_inputs.label_jar')}</p>
<p className="font-semibold">
{t('earn.fidelity_bond.review_inputs.label_jar_n', { jar: selectedJarIndex })}
</p>
<Badge variant={getJarBadgeVariant(selectedJarIndex)}>
{selectedJar ? (
<>
{selectedJar.name} <span>#{selectedJar.jarIndex}</span>
</>
) : (
t('earn.fidelity_bond.review_inputs.label_jar_n', { jar: selectedJarIndex })
)}
</Badge>
</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion src/components/earn/MoveToJarDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ describe('MoveToJarDialog', () => {
it('moves to the confirm step and shows the destination address', () => {
renderDialog()
goToConfirm()
expect(screen.getByText('earn.fidelity_bond.review_inputs.label_jar_n {"jar":1}')).toBeInTheDocument()
expect(screen.getByText('Jar 1')).toBeInTheDocument()
expect(screen.getByTestId('address')).toHaveTextContent('bc111')
})

Expand Down
16 changes: 10 additions & 6 deletions src/components/earn/MoveToJarDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { buttonVariants } from '@/components/ui/button-variants'
import { Card, CardContent } from '@/components/ui/card'
Expand All @@ -39,6 +40,7 @@ import type { FidelityBondUtxo, Utxo } from '@/hooks/useQueryUtxos'
import { getErrorReason } from '@/lib/errorReason'
import { cn, formatSats, type WalletFileName } from '@/lib/utils'
import type { JarIndex } from '@/types/global'
import { getJarBadgeVariant } from '../ui/badge-variants'
import { Address } from '../ui/jam/Address'

type Step = 'select_jar' | 'confirm' | 'sending' | 'success'
Expand All @@ -62,6 +64,7 @@ export function MoveToJarDialog({ open, onOpenChange, walletFileName, utxo }: Mo
const [error, setError] = useState<string | undefined>()

const sourceJar = walletInfo.jars.find((jar) => jar.jarIndex === utxo.mixdepth)
const destinationJar = walletInfo.jars.find((jar) => jar.jarIndex === selectedJarIndex)

// All jars except the FB's source jar are valid destinations
const destinationJars = useMemo(() => {
Expand Down Expand Up @@ -279,15 +282,16 @@ export function MoveToJarDialog({ open, onOpenChange, walletFileName, utxo }: Mo
<p className="text-muted-foreground mb-1 text-xs">
{t('earn.fidelity_bond.review_inputs.label_jar')}
</p>
<p className="font-semibold">
{t('earn.fidelity_bond.review_inputs.label_jar_n', { jar: utxo.mixdepth })}
</p>
<Badge variant={getJarBadgeVariant(utxo.mixdepth)}>
{sourceJar?.name ?? t('earn.fidelity_bond.review_inputs.label_jar')} <span>#{utxo.mixdepth}</span>
</Badge>
</div>
<div className="bg-muted/50 rounded-lg p-4">
<p className="text-muted-foreground mb-1 text-xs">{t('earn.fidelity_bond.move.label_destination')}</p>
<p className="font-semibold">
{t('earn.fidelity_bond.review_inputs.label_jar_n', { jar: selectedJarIndex })}
</p>
<Badge variant={getJarBadgeVariant(selectedJarIndex)}>
{destinationJar?.name ?? t('earn.fidelity_bond.review_inputs.label_jar')}{' '}
<span>#{selectedJarIndex}</span>
</Badge>
</div>
</div>

Expand Down
8 changes: 5 additions & 3 deletions src/components/earn/RenewBondDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { buttonVariants } from '@/components/ui/button-variants'
import {
Expand All @@ -40,6 +41,7 @@ import { getErrorReason } from '@/lib/errorReason'
import * as fb from '@/lib/fidelityBondUtils'
import { cn, formatSats, type WalletFileName } from '@/lib/utils'
import { useDeveloperMode } from '@/store/jamSettingsStore'
import { getJarBadgeVariant } from '../ui/badge-variants'
import { Address } from '../ui/jam/Address'
import { generateLockdateOptions, getYearOptions, getMonthOptions } from './CreateFidelityBondDialog/types'

Expand Down Expand Up @@ -330,9 +332,9 @@ export function RenewBondDialog({ open, onOpenChange, walletFileName, utxo }: Re
</div>
<div className="bg-muted/50 rounded-lg p-3">
<p className="text-muted-foreground text-xs">{t('earn.fidelity_bond.review_inputs.label_jar')}</p>
<p className="font-semibold">
{t('earn.fidelity_bond.review_inputs.label_jar_n', { jar: utxo.mixdepth })}
</p>
<Badge variant={getJarBadgeVariant(utxo.mixdepth)}>
{sourceJar?.name ?? t('earn.fidelity_bond.review_inputs.label_jar')} <span>#{utxo.mixdepth}</span>
</Badge>
</div>
</div>

Expand Down
79 changes: 48 additions & 31 deletions src/components/receive/ReceivePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,42 @@ import type { Jar } from '@/context/JamWalletInfoContext'
import { flushActUpdates } from '@/test/flushActUpdates'
import { ReceivePage } from './ReceivePage'

const mocks = vi.hoisted(() => ({
developerMode: false,
getAddress: vi.fn(),
share: vi.fn(),
toastError: vi.fn(),
toastSuccess: vi.fn(),
}))
const mocks = vi.hoisted(() => {
const defaultJars = [
{
jarIndex: 0,
name: 'Zero',
color: '#e2b86a',
balanceSummary: {
calculatedTotalBalanceInSats: 10_000,
calculatedAvailableBalanceInSats: 9_000,
calculatedConfirmedAvailableBalanceInSats: 9_000,
calculatedFrozenOrLockedBalanceInSats: 1_000,
},
},
{
jarIndex: 1,
name: 'One',
color: '#3b5ba9',
balanceSummary: {
calculatedTotalBalanceInSats: 8_000,
calculatedAvailableBalanceInSats: 8_000,
calculatedConfirmedAvailableBalanceInSats: 8_000,
calculatedFrozenOrLockedBalanceInSats: 0,
},
},
] as unknown as Jar[]

return {
developerMode: false,
getAddress: vi.fn(),
defaultJars,
jars: defaultJars,
share: vi.fn(),
toastError: vi.fn(),
toastSuccess: vi.fn(),
}
})

type MutationOptions = {
mutationFn: () => Promise<unknown>
Expand Down Expand Up @@ -74,30 +103,7 @@ vi.mock('sonner', () => ({

vi.mock('@/context/JamWalletInfoContext', () => ({
useJars: () => ({
jars: [
{
jarIndex: 0,
name: 'Zero',
color: '#e2b86a',
balanceSummary: {
calculatedTotalBalanceInSats: 10_000,
calculatedAvailableBalanceInSats: 9_000,
calculatedConfirmedAvailableBalanceInSats: 9_000,
calculatedFrozenOrLockedBalanceInSats: 1_000,
},
},
{
jarIndex: 1,
name: 'One',
color: '#3b5ba9',
balanceSummary: {
calculatedTotalBalanceInSats: 8_000,
calculatedAvailableBalanceInSats: 8_000,
calculatedConfirmedAvailableBalanceInSats: 8_000,
calculatedFrozenOrLockedBalanceInSats: 0,
},
},
] as unknown as Jar[],
jars: mocks.jars,
}),
}))

Expand Down Expand Up @@ -142,6 +148,7 @@ vi.mock('./ReceiveForm', () => ({
describe('ReceivePage', () => {
beforeEach(() => {
mocks.developerMode = false
mocks.jars = mocks.defaultJars
mocks.getAddress.mockReset()
mocks.getAddress.mockResolvedValue({ data: { address: 'bc1qexample' } })
mocks.toastError.mockReset()
Expand All @@ -168,6 +175,16 @@ describe('ReceivePage', () => {
await flushActUpdates()
})

it('uses a secondary jar badge when no jar is available', () => {
mocks.jars = []

const { container } = render(<ReceivePage walletFileName="wallet.jmdat" />)

const badge = container.querySelector('[data-slot="badge"]')
expect(badge).toHaveClass('bg-secondary')
expect(badge).not.toHaveClass('bg-primary')
})

it('uses receive form changes for the next address request and sharing', async () => {
const user = userEvent.setup()
mocks.share.mockRejectedValue(new Error('cancelled'))
Expand Down
7 changes: 6 additions & 1 deletion src/components/receive/ReceivePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { cn, type WalletFileName } from '@/lib/utils'
import { useDeveloperMode } from '@/store/jamSettingsStore'
import type { AmountSats, BitcoinAddress } from '@/types/global'
import { Badge } from '../ui/badge'
import { getJarBadgeVariant } from '../ui/badge-variants'
import { buttonVariants } from '../ui/button-variants'
import { Address } from '../ui/jam/Address'
import { BitcoinAddressQrCode } from '../ui/jam/BitcoinQrCode'
Expand Down Expand Up @@ -92,6 +93,7 @@ export const ReceivePage = ({ walletFileName }: ReceivePageProps) => {
if (getAddressMutation.data?.sourceJarIndex === undefined) return
return jars[getAddressMutation.data.sourceJarIndex]
}, [jars, getAddressMutation.data])
const displayedJar = sourceJar ?? selectedSourceJar

const shareAddress = async (bitcoinAddress: BitcoinAddress) => {
if ('share' in navigator) {
Expand Down Expand Up @@ -165,7 +167,10 @@ export const ReceivePage = ({ walletFileName }: ReceivePageProps) => {
<Address value={getAddressMutation.data.address} className="text-sm" copyable={true} />
)}
</div>
<Badge className="min-h-6 text-sm" variant={sourceJar ? 'default' : 'secondary'}>
<Badge
className="min-h-6 text-sm"
variant={displayedJar ? getJarBadgeVariant(displayedJar.jarIndex) : 'secondary'}
>
{sourceJar ? (
<>
{sourceJar.name} <span className="text-xs">#{sourceJar.jarIndex}</span>
Expand Down
15 changes: 10 additions & 5 deletions src/components/send/PaymentConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useMemo, useState, type ComponentProps } from 'react'
import { DialogTitle } from '@radix-ui/react-dialog'
import { InfoIcon } from 'lucide-react'
import { Trans, useTranslation } from 'react-i18next'
import { Badge } from '@/components/ui/badge'
import { getJarBadgeVariant } from '@/components/ui/badge-variants'
import type { Jar } from '@/context/JamWalletInfoContext'
import type { Utxo } from '@/hooks/useQueryUtxos'
import type { JamFeeConfigValues } from '@/lib/feeConfig'
Expand Down Expand Up @@ -109,17 +111,20 @@ export default function PaymentConfirmDialog({
<div className="grid grid-cols-1 gap-x-4 gap-y-1 md:grid-cols-5">
<div className="col-span-1 font-semibold md:text-right">{t('send.confirm_send_modal.label_source_jar')}</div>
<div className="col-span-4">
{meta.sourceJar.name} <span className="text-muted-foreground text-xs">#{meta.sourceJar.jarIndex}</span>
<Badge variant={getJarBadgeVariant(meta.sourceJar.jarIndex)}>
{meta.sourceJar.name} <span>#{meta.sourceJar.jarIndex}</span>
</Badge>
</div>

<div className="col-span-1 font-semibold md:text-right">{t('send.confirm_send_modal.label_recipient')}</div>
<div className="col-span-4 flex flex-col">
{meta.destinationJar !== undefined ? (
<>
<span>
{meta.destinationJar.name}{' '}
<span className="text-muted-foreground text-xs">#{meta.destinationJar.jarIndex}</span>
</span>
<div>
<Badge variant={getJarBadgeVariant(meta.destinationJar.jarIndex)}>
{meta.destinationJar.name} <span>#{meta.destinationJar.jarIndex}</span>
</Badge>
</div>
<Address
className="text-muted-foreground text-xs"
value={values.destination?.address ?? ''}
Expand Down
5 changes: 3 additions & 2 deletions src/components/send/SendForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { DevBadge } from '../dev/DevBadge'
import { buildSweepPreconditionSummary } from '../sweep/preconditions'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../ui/accordion'
import { Badge } from '../ui/badge'
import { getJarBadgeVariant } from '../ui/badge-variants'
import { Button } from '../ui/button'
import { ButtonGroup } from '../ui/button-group'
import { Card, CardContent, CardHeader } from '../ui/card'
Expand Down Expand Up @@ -411,7 +412,7 @@ export function SendForm({
)}
>
<Address value={values.destination.address} copyable={true} className="text-xs sm:text-sm" />
<Badge className="shrink-0 text-sm" variant="default">
<Badge className="shrink-0 text-sm" variant={getJarBadgeVariant(destinationJar.jarIndex)}>
{destinationJar.name} <span className="text-xs">#{destinationJar.jarIndex}</span>
</Badge>
</div>
Expand Down Expand Up @@ -512,7 +513,7 @@ export function SendForm({
<Balance valueString={values.amount.sweepAmount.toFixed(0)} />
)}

<Badge className="shrink-0 text-sm" variant="default">
<Badge className="shrink-0 text-sm" variant={getJarBadgeVariant(sourceJar?.jarIndex)}>
{sourceJar?.name} <span className="text-xs">#{sourceJar?.jarIndex}</span>
</Badge>
</div>
Expand Down
22 changes: 22 additions & 0 deletions src/components/ui/badge-variants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from 'vitest'
import { getJarBadgeVariant } from './badge-variants'

describe('getJarBadgeVariant', () => {
it.each([
[0, 'jar0'],
[1, 'jar1'],
[2, 'jar2'],
[3, 'jar3'],
[4, 'jar4'],
])('returns the jar badge variant for jar index %i', (jarIndex, variant) => {
expect(getJarBadgeVariant(jarIndex)).toBe(variant)
})

it('returns undefined when no jar is available', () => {
expect(getJarBadgeVariant()).toBeUndefined()
})

it('returns the unknown jar badge variant for non-standard jar indexes', () => {
expect(getJarBadgeVariant(5)).toBe('jarUnknown')
})
})
23 changes: 21 additions & 2 deletions src/components/ui/badge-variants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority'

const badgeVariants = cva(
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
Expand All @@ -11,6 +11,12 @@ const badgeVariants = cva(
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
dev: 'bg-brand-warning text-brand-warning-foreground border-transparent',
jar0: 'border-transparent bg-jar0 text-primary-foreground [a&]:hover:bg-jar0/90',
jar1: 'border-transparent bg-jar1 text-primary-foreground [a&]:hover:bg-jar1/90',
jar2: 'border-transparent bg-jar2 text-primary-foreground [a&]:hover:bg-jar2/90',
jar3: 'border-transparent bg-jar3 text-primary-foreground [a&]:hover:bg-jar3/90',
jar4: 'border-transparent bg-jar4 text-primary-foreground [a&]:hover:bg-jar4/90',
jarUnknown: 'border-transparent bg-jar-unknown text-primary-foreground [a&]:hover:bg-jar-unknown/90',
},
},
defaultVariants: {
Expand All @@ -19,4 +25,17 @@ const badgeVariants = cva(
},
)

export { badgeVariants }
type BadgeVariant = NonNullable<VariantProps<typeof badgeVariants>['variant']>

const JAR_BADGE_VARIANTS: Record<number, BadgeVariant> = {
0: 'jar0',
1: 'jar1',
2: 'jar2',
3: 'jar3',
4: 'jar4',
}

const getJarBadgeVariant = (jarIndex?: number): BadgeVariant | undefined =>
jarIndex === undefined ? undefined : (JAR_BADGE_VARIANTS[jarIndex] ?? 'jarUnknown')

export { badgeVariants, getJarBadgeVariant }
6 changes: 6 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
--color-brand-cherry: var(--brand-cherry);
--color-brand-date: var(--brand-date);
--color-brand-elderberry: var(--brand-elderberry);
--color-jar0: var(--brand-apricot);
--color-jar1: var(--brand-blueberry);
--color-jar2: var(--brand-cherry);
--color-jar3: var(--brand-date);
--color-jar4: var(--brand-elderberry);
--color-jar-unknown: #808080;
--color-brand-shell: var(--brand-shell);
--color-brand-shell-foreground: var(--brand-shell-foreground);
--color-brand-nav: var(--brand-nav);
Expand Down