Skip to content
Merged
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
9 changes: 6 additions & 3 deletions src/components/onboarding/chain-address-preview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,19 @@ describe('ChainAddressPreview', () => {
vi.useRealTimers()
})

it('renders enabled bioforest chains after derivation', async () => {
it('renders single address after derivation (all bioforest chains share same address)', async () => {
vi.useFakeTimers()
renderWithProviders(<ChainAddressPreview secret="abc" enabledBioforestChainConfigs={mockConfigs} />)

await act(async () => {
await vi.advanceTimersByTimeAsync(150)
})

expect(screen.getByText('BFMeta')).toBeInTheDocument()
expect(screen.getByText('PMChain')).toBeInTheDocument()
// Should show single address, not chain names list
expect(screen.queryByTestId('address-preview-loading')).not.toBeInTheDocument()
// Address container should be present
const container = document.querySelector('.px-4.py-3')
expect(container).toBeInTheDocument()
vi.useRealTimers()
})

Expand Down
48 changes: 9 additions & 39 deletions src/components/onboarding/chain-address-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

import { AddressDisplay } from '@/components/wallet/address-display'
import { ChainIcon } from '@/components/wallet/chain-icon'
import { deriveBioforestAddresses } from '@/lib/crypto'
import type { ChainConfig } from '@/services/chain-config'
import { cn } from '@/lib/utils'
Expand All @@ -29,12 +28,6 @@ export function ChainAddressPreview({
const [isLoading, setIsLoading] = useState(false)
const [addresses, setAddresses] = useState<DerivedAddress[]>([])

const metaById = useMemo(() => {
const map = new Map<string, ChainConfig>()
for (const config of enabledBioforestChainConfigs ?? []) map.set(config.id, config)
return map
}, [enabledBioforestChainConfigs])

useEffect(() => {
const trimmed = secret.trim()
if (!trimmed) {
Expand Down Expand Up @@ -65,39 +58,16 @@ export function ChainAddressPreview({
<div className="h-px bg-border" />

{isLoading ? (
<div className="space-y-3 px-4 py-4" data-testid="address-preview-loading">
{Array.from({ length: 3 }).map((_, idx) => (
<div key={idx} className="flex items-center gap-3 animate-pulse">
<div className="size-8 rounded-full bg-muted" />
<div className="flex-1 space-y-2">
<div className="h-3 w-24 rounded bg-muted" />
<div className="h-3 w-40 rounded bg-muted" />
</div>
</div>
))}
<div className="px-4 py-3 animate-pulse" data-testid="address-preview-loading">
<div className="h-4 w-48 rounded bg-muted" />
</div>
) : addresses.length > 0 ? (
<div className="px-4 py-3">
<AddressDisplay address={addresses[0]!.address} />
</div>
) : (
<div className="divide-y divide-border">
{addresses.map((item) => {
const meta = metaById.get(item.chainId)
return (
<div key={item.chainId} className="flex items-center gap-3 px-4 py-3">
<ChainIcon chain={item.chainId} size="sm" />
<div className="min-w-0 flex-1">
<div className="text-sm font-medium">{meta?.name ?? item.chainId}</div>
<div className="mt-1">
<AddressDisplay address={item.address} />
</div>
</div>
</div>
)
})}

{addresses.length === 0 && (
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
—
</div>
)}
<div className="px-4 py-6 text-center text-sm text-muted-foreground">
—
</div>
)}
</div>
Expand Down
9 changes: 4 additions & 5 deletions src/components/onboarding/security-warning-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { cn } from '@/lib/utils';

interface SecurityWarningDialogProps {
Expand Down Expand Up @@ -62,12 +63,10 @@ export function SecurityWarningDialog({
</div>
</div>

<label className="mt-4 flex cursor-pointer items-start gap-2 text-sm">
<input
type="checkbox"
className="mt-1 size-4"
<label className="mt-4 flex cursor-pointer items-center gap-2 text-sm">
<Checkbox
checked={acknowledged}
onChange={(e) => setAcknowledged(e.target.checked)}
onCheckedChange={setAcknowledged}
/>
<span>{t('onboarding:securityWarning.acknowledge')}</span>
</label>
Expand Down
4 changes: 4 additions & 0 deletions src/components/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export interface WalletInfo {
fiatValue?: string | undefined
chainName?: string | undefined
isBackedUp?: boolean | undefined
/** 主题色 hue (0-360) */
themeHue?: number | undefined
/** 链图标 URL,用于彩虹水印 */
chainIconUrl?: string | undefined
}
export { AddressDisplay } from './address-display'
export { ChainIcon, ChainBadge, ChainIconProvider, type ChainType } from './chain-icon'
Expand Down
2 changes: 2 additions & 0 deletions src/components/wallet/refraction/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { HologramCanvas } from './hologram-canvas'
export type { HologramCanvasProps } from './hologram-canvas'
export type { Priority } from './types'
export { resolveBackgroundStops } from './renderer'
export type { BackgroundStops } from './renderer'
2 changes: 0 additions & 2 deletions src/components/wallet/wallet-card-carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ export function WalletCardCarousel({
priority={getPriority(index)}
address={walletAddress}
chainIconUrl={chainIconUrls[walletChain]}
watermarkLogoSize={40}
watermarkLogoActualSize={24}
themeHue={getWalletTheme(wallet.id)}
onCopyAddress={() => {
if (walletAddress) onCopyAddress?.(walletAddress);
Expand Down
13 changes: 13 additions & 0 deletions src/components/wallet/wallet-card.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { WalletCard } from './wallet-card'
import type { Wallet } from '@/stores'
import { fn } from '@storybook/test'

// 不同链的图标路径
const CHAIN_ICONS = {
ethereum: '/icons/ethereum/chain.svg',
bitcoin: '/icons/bitcoin/chain.svg',
tron: '/icons/tron/chain.svg',
binance: '/icons/binance/chain.svg',
bfmeta: '/icons/bfmeta/chain.svg',
}

const createMockWallet = (overrides: Partial<Wallet> = {}): Wallet => ({
id: 'wallet-1',
name: '我的钱包',
Expand Down Expand Up @@ -64,6 +73,7 @@ export const Default: Story = {
chainName: 'Ethereum',
address: '0x1234567890abcdef1234567890abcdef12345678',
themeHue: 323,
chainIconUrl: CHAIN_ICONS.ethereum,
onCopyAddress: fn(),
onOpenChainSelector: fn(),
onOpenSettings: fn(),
Expand Down Expand Up @@ -112,6 +122,7 @@ export const TronChain: Story = {
chainName: 'Tron',
address: 'TAbcd1234567890abcdef1234567890abcde',
themeHue: 0,
chainIconUrl: CHAIN_ICONS.tron,
onCopyAddress: fn(),
onOpenChainSelector: fn(),
onOpenSettings: fn(),
Expand All @@ -125,6 +136,7 @@ export const BitcoinChain: Story = {
chainName: 'Bitcoin',
address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh',
themeHue: 30,
chainIconUrl: CHAIN_ICONS.bitcoin,
onCopyAddress: fn(),
onOpenChainSelector: fn(),
onOpenSettings: fn(),
Expand Down Expand Up @@ -168,6 +180,7 @@ export const AllThemes: Story = {
chainName="ETH"
address="0x1234...5678"
themeHue={hue}
chainIconUrl={CHAIN_ICONS.ethereum}
/>
))}
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/components/wallet/wallet-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const WalletCard = forwardRef<HTMLDivElement, WalletCardProps>(function W
priority = 'high',
address,
chainIconUrl,
watermarkLogoSize = 40,
watermarkLogoSize = 60,
watermarkLogoActualSize = 24,
onCopyAddress,
onOpenChainSelector,
Expand Down Expand Up @@ -113,6 +113,7 @@ export const WalletCard = forwardRef<HTMLDivElement, WalletCardProps>(function W
size: Math.round(watermarkLogoActualSize * dpr),
invert: false, // 白色区域不透明
contrast: 1.8,
targetBrightness: 0.85,
});

const { pointerX, pointerY, isActive, bindElement } = useCardInteraction({
Expand Down
Loading