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
4 changes: 2 additions & 2 deletions e2e/bioforest-full-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ async function performTransfer(
await continueBtn.click()
await page.waitForTimeout(500)

// 点击确认转账
const confirmBtn = page.locator(`[data-testid="confirm-transfer-button"], button:has-text("${UI_TEXT.confirm.source}")`).first()
// 点击确认转账 (TransferPreviewJob)
const confirmBtn = page.locator(`[data-testid="confirm-preview-button"], button:has-text("${UI_TEXT.confirm.source}")`).first()
await expect(confirmBtn).toBeVisible({ timeout: 5000 })
await confirmBtn.click()
await page.waitForTimeout(500)
Expand Down
2 changes: 1 addition & 1 deletion e2e/bioforest-real-transfer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ async function doTransfer(page: Page, toAddress: string, amount: string, needPay
await expect(continueBtn).toBeEnabled({ timeout: 15000 })
await continueBtn.click()

await page.locator('[data-testid="confirm-transfer-button"]').click()
await page.locator('[data-testid="confirm-preview-button"]').click()

// 验证钱包锁
const patternInput = page.locator('[data-testid="wallet-pattern-input"]')
Expand Down
4 changes: 2 additions & 2 deletions e2e/bioforest-transfer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ describeOrSkip('BioForest 转账测试', () => {
await continueBtn.click()
console.log(' ✅ 继续到确认页')

// 7. 确认转账
// 7. 确认转账(TransferPreviewJob)
console.log('7. 确认转账...')
const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]')
const confirmBtn = page.locator('[data-testid="confirm-preview-button"]')
await expect(confirmBtn).toBeVisible({ timeout: 5000 })
await confirmBtn.click()
console.log(' ✅ 点击确认')
Expand Down
10 changes: 5 additions & 5 deletions e2e/send-transaction.mock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,14 +344,14 @@ test.describe('发送交易 - Job 弹窗流程', () => {
await page.waitForTimeout(500)

// 检查确认弹窗内容
const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]')
const cancelBtn = page.locator('[data-testid="cancel-transfer-button"]')
const confirmBtn = page.locator('[data-testid="confirm-preview-button"]')
const cancelBtn = page.locator('[data-testid="cancel-preview-button"]')

// 至少一个按钮应该可见(确认或取消)
const hasConfirmUI = await confirmBtn.isVisible() || await cancelBtn.isVisible()

if (hasConfirmUI) {
console.log('TransferConfirmJob opened successfully')
console.log('TransferPreviewJob opened successfully')

// 截图
await expect(page).toHaveScreenshot('send-confirm-job.png')
Expand All @@ -365,7 +365,7 @@ test.describe('发送交易 - Job 弹窗流程', () => {
await expect(page.locator('[data-testid="send-continue-button"]')).toBeVisible()
}
} else {
console.log('TransferConfirmJob may not have opened - check mock configuration')
console.log('TransferPreviewJob may not have opened - check mock configuration')
}
} else {
console.log('Continue button not enabled - mock service may not be configured correctly')
Expand All @@ -390,7 +390,7 @@ test.describe('发送交易 - Job 弹窗流程', () => {
await continueBtn.click()
await page.waitForTimeout(500)

const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]')
const confirmBtn = page.locator('[data-testid="confirm-preview-button"]')

if (await confirmBtn.isVisible()) {
await confirmBtn.click()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion miniapps/forge/e2e/helpers/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const UI_TEXT = {
subtitle: { source: '跨链桥接', pattern: /跨链桥接|Cross-chain Bridge/i },
},
mode: {
recharge: { source: '充值', pattern: /充值|Recharge/i },
recharge: { source: '锻造', pattern: /锻造|Forge/i },
redemption: { source: '赎回', pattern: /赎回|Redemption/i },
},
connect: {
Expand Down
2 changes: 1 addition & 1 deletion miniapps/forge/e2e/ui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ test.describe('BioBridge UI', () => {
await page.waitForLoadState('networkidle')

// Wait for config to load - mode tabs should be visible since redemption is enabled
await expect(page.locator('text=充值')).toBeVisible({ timeout: 10000 })
await expect(page.locator('text=锻造')).toBeVisible({ timeout: 10000 })
await expect(page.locator('text=赎回')).toBeVisible({ timeout: 5000 })

await expect(page).toHaveScreenshot('09-mode-tabs.png')
Expand Down
1 change: 1 addition & 0 deletions miniapps/forge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@biochain/key-ui": "workspace:*",
"@biochain/key-utils": "workspace:*",
"@biochain/keyapp-sdk": "workspace:*",
"@fontsource-variable/noto-sans-sc": "^5.2.10",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
Expand Down
16 changes: 15 additions & 1 deletion miniapps/forge/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ export type LanguageCode = keyof typeof languages

export const defaultLanguage: LanguageCode = 'zh-CN'

// Try to get language from local storage (aligns with shell/e2e)
let savedLanguage: LanguageCode = defaultLanguage
try {
const prefs = localStorage.getItem('bfm_preferences')
if (prefs) {
const parsed = JSON.parse(prefs)
if (parsed.language && Object.keys(languages).includes(parsed.language)) {
savedLanguage = parsed.language as LanguageCode
}
}
} catch (e) {
// Ignore error
}

export function getLanguageDirection(lang: LanguageCode): 'ltr' | 'rtl' {
return languages[lang]?.dir ?? 'ltr'
}
Expand All @@ -31,7 +45,7 @@ i18n.use(initReactI18next).init({
'zh-CN': { translation: zhCN },
'zh-TW': { translation: zhTW },
},
lng: defaultLanguage,
lng: savedLanguage,
fallbackLng: {
'zh-CN': ['zh'],
'zh-TW': ['zh'],
Expand Down
2 changes: 1 addition & 1 deletion miniapps/forge/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Bridge assets between external chains and Bio ecosystem"
},
"mode": {
"recharge": "Recharge",
"recharge": "Forge",
"redemption": "Redemption"
},
"connect": {
Expand Down
2 changes: 1 addition & 1 deletion miniapps/forge/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "在外链与 Bio 生态之间桥接资产"
},
"mode": {
"recharge": "充值",
"recharge": "锻造",
"redemption": "赎回"
},
"connect": {
Expand Down
2 changes: 1 addition & 1 deletion miniapps/forge/src/i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "在外鏈與 Bio 生態之間橋接資產"
},
"mode": {
"recharge": "充值",
"recharge": "鍛造",
"redemption": "贖回"
},
"connect": {
Expand Down
2 changes: 1 addition & 1 deletion miniapps/forge/src/i18n/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "在外链与 Bio 生态之间桥接资产"
},
"mode": {
"recharge": "充值",
"recharge": "锻造",
"redemption": "赎回"
},
"connect": {
Expand Down
1 change: 1 addition & 0 deletions miniapps/forge/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
}
body {
@apply bg-background text-foreground;
font-family: 'Noto Sans SC Variable', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
Expand Down
1 change: 1 addition & 0 deletions miniapps/forge/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './index.css'
import '@fontsource-variable/noto-sans-sc'
import './i18n'
import '@biochain/bio-sdk'
import { StrictMode } from 'react'
Expand Down
14 changes: 11 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 107 additions & 0 deletions src/components/wallet/wallet-address-portfolio-from-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useMemo } from 'react'
import { WalletAddressPortfolioView, type WalletAddressPortfolioViewProps } from './wallet-address-portfolio-view'
import type { ChainType } from '@/stores'
import { ChainProviderGate, useChainProvider } from '@/contexts'
import type { TokenInfo } from '@/components/token/token-item'
import { toTransactionInfoList } from '@/components/transaction/adapters'

export interface WalletAddressPortfolioFromProviderProps {
chainId: ChainType
address: string
chainName?: string
onTokenClick?: WalletAddressPortfolioViewProps['onTokenClick']
onTransactionClick?: WalletAddressPortfolioViewProps['onTransactionClick']
className?: string
testId?: string
}

/**
* 从 Provider 获取地址资产组合(内部实现)
*
* 使用 ChainProvider 响应式 API 获取数据,复用 WalletAddressPortfolioView 展示。
*/
function WalletAddressPortfolioFromProviderInner({
chainId,
address,
chainName,
onTokenClick,
onTransactionClick,
className,
testId,
}: WalletAddressPortfolioFromProviderProps) {
const chainProvider = useChainProvider()

// 使用新的响应式 API
const { data: tokens = [], isLoading: tokensLoading } = chainProvider.allBalances.useState(
{ address },
{ enabled: !!address }
)

const { data: txResult, isLoading: transactionsLoading } = chainProvider.transactionHistory.useState(
{ address, limit: 50 },
{ enabled: !!address }
)

// 转换为 TokenInfo 格式
const tokenInfoList: TokenInfo[] = useMemo(() => {
return tokens.map((token) => ({
symbol: token.symbol,
name: token.name,
chain: chainId,
balance: token.amount.toFormatted(),
decimals: token.decimals,
fiatValue: undefined,
change24h: 0,
icon: token.icon,
}))
}, [tokens, chainId])

// 转换交易历史格式
const transactions = useMemo(() => {
if (!txResult) return []
return toTransactionInfoList(txResult, chainId)
}, [txResult, chainId])

return (
<WalletAddressPortfolioView
chainId={chainId}
chainName={chainName}
tokens={tokenInfoList}
transactions={transactions}
tokensLoading={tokensLoading}
transactionsLoading={transactionsLoading}
tokensRefreshing={false}
tokensSupported={chainProvider.supportsTokenBalances || chainProvider.supportsNativeBalance}
tokensFallbackReason={undefined}
transactionsSupported={chainProvider.supportsTransactionHistory}
transactionsFallbackReason={undefined}
onTokenClick={onTokenClick}
onTransactionClick={onTransactionClick}
className={className}
testId={testId}
/>
)
}

/**
* 从 Provider 获取地址资产组合
*
* 使用 ChainProviderGate 确保 ChainProvider 可用,再使用响应式 API 获取数据。
* 适用于 Stories 测试和任意地址查询场景。
*/
export function WalletAddressPortfolioFromProvider(props: WalletAddressPortfolioFromProviderProps) {
return (
<ChainProviderGate
chainId={props.chainId}
fallback={
<div className="flex h-96 items-center justify-center">
<div className="text-muted-foreground text-center">
<p>Chain not supported: {props.chainId}</p>
</div>
</div>
}
>
<WalletAddressPortfolioFromProviderInner {...props} />
</ChainProviderGate>
)
}
3 changes: 2 additions & 1 deletion src/i18n/locales/en/transaction.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"twoStepSecretDescription": "This address has a security password set. Please enter your security password to confirm the transfer.",
"twoStepSecretPlaceholder": "Enter security password",
"twoStepSecretError": "Incorrect security password",
"fee": "Fee"
"fee": "Fee",
"previewTitle": "Transaction Preview"
},
"destroyPage": {
"title": "Destroy",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/zh-CN/transaction.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"twoStepSecretDescription": "该地址已设置安全密码,请输入安全密码确认转账。",
"twoStepSecretPlaceholder": "输入安全密码",
"twoStepSecretError": "安全密码错误",
"fee": "手续费"
"fee": "手续费",
"previewTitle": "交易预览"
},
"destroyPage": {
"title": "销毁",
Expand Down
12 changes: 8 additions & 4 deletions src/pages/send/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigation, useActivityParams, useFlow } from '@/stackflow';
import { setTransferConfirmCallback, setTransferWalletLockCallback, setScannerResultCallback } from '@/stackflow/activities/sheets';
import { setTransferPreviewCallback, setTransferWalletLockCallback, setScannerResultCallback } from '@/stackflow/activities/sheets';
import type { Contact, ContactAddress } from '@/stores';
import { addressBookStore, addressBookSelectors, preferencesActions } from '@/stores';
import { PageHeader } from '@/components/layout/page-header';
Expand Down Expand Up @@ -237,8 +237,8 @@ function SendPageContent() {

haptics.impact('light');

// Set up callback: TransferConfirm -> TransferWalletLock (合并的钱包锁+二次签名)
setTransferConfirmCallback(
// Set up callback: TransferPreview -> TransferWalletLock (合并的钱包锁+二次签名)
setTransferPreviewCallback(
async () => {
if (isWalletLockSheetOpen.current) return;
isWalletLockSheetOpen.current = true;
Expand Down Expand Up @@ -300,13 +300,17 @@ function SendPageContent() {
}
);

push('TransferConfirmJob', {
push('TransferPreviewJob', {
amount: state.amount?.toFormatted() ?? '0',
symbol,
decimals: String(state.asset?.decimals ?? chainConfig?.decimals ?? 8),
fromAddress: currentChainAddress?.address ?? '',
toAddress: state.toAddress,
feeAmount: state.feeAmount?.toFormatted() ?? '0',
feeSymbol: state.feeSymbol,
feeLoading: state.feeLoading ? 'true' : 'false',
chainId: selectedChain,
chainName: selectedChainName,
});
};

Expand Down
Loading