diff --git a/e2e/bioforest-full-flow.spec.ts b/e2e/bioforest-full-flow.spec.ts index 05ce11b3a..4cf50f639 100644 --- a/e2e/bioforest-full-flow.spec.ts +++ b/e2e/bioforest-full-flow.spec.ts @@ -268,8 +268,8 @@ async function performTransfer( await continueBtn.click() await page.waitForTimeout(500) - // 点击确认转账 (TransferPreviewJob) - const confirmBtn = page.locator(`[data-testid="confirm-preview-button"], button:has-text("${UI_TEXT.confirm.source}")`).first() + // 点击确认转账 + const confirmBtn = page.locator(`[data-testid="confirm-transfer-button"], button:has-text("${UI_TEXT.confirm.source}")`).first() await expect(confirmBtn).toBeVisible({ timeout: 5000 }) await confirmBtn.click() await page.waitForTimeout(500) diff --git a/e2e/bioforest-real-transfer.spec.ts b/e2e/bioforest-real-transfer.spec.ts index 1a9b4c9e3..9752e413f 100644 --- a/e2e/bioforest-real-transfer.spec.ts +++ b/e2e/bioforest-real-transfer.spec.ts @@ -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-preview-button"]').click() + await page.locator('[data-testid="confirm-transfer-button"]').click() // 验证钱包锁 const patternInput = page.locator('[data-testid="wallet-pattern-input"]') diff --git a/e2e/bioforest-transfer.spec.ts b/e2e/bioforest-transfer.spec.ts index 8a4793e4a..b79f2961c 100644 --- a/e2e/bioforest-transfer.spec.ts +++ b/e2e/bioforest-transfer.spec.ts @@ -201,9 +201,9 @@ describeOrSkip('BioForest 转账测试', () => { await continueBtn.click() console.log(' ✅ 继续到确认页') - // 7. 确认转账(TransferPreviewJob) + // 7. 确认转账 console.log('7. 确认转账...') - const confirmBtn = page.locator('[data-testid="confirm-preview-button"]') + const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]') await expect(confirmBtn).toBeVisible({ timeout: 5000 }) await confirmBtn.click() console.log(' ✅ 点击确认') diff --git a/e2e/send-transaction.mock.spec.ts b/e2e/send-transaction.mock.spec.ts index 6ec52f51f..c56109e18 100644 --- a/e2e/send-transaction.mock.spec.ts +++ b/e2e/send-transaction.mock.spec.ts @@ -344,14 +344,14 @@ test.describe('发送交易 - Job 弹窗流程', () => { await page.waitForTimeout(500) // 检查确认弹窗内容 - const confirmBtn = page.locator('[data-testid="confirm-preview-button"]') - const cancelBtn = page.locator('[data-testid="cancel-preview-button"]') + const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]') + const cancelBtn = page.locator('[data-testid="cancel-transfer-button"]') // 至少一个按钮应该可见(确认或取消) const hasConfirmUI = await confirmBtn.isVisible() || await cancelBtn.isVisible() if (hasConfirmUI) { - console.log('TransferPreviewJob opened successfully') + console.log('TransferConfirmJob opened successfully') // 截图 await expect(page).toHaveScreenshot('send-confirm-job.png') @@ -365,7 +365,7 @@ test.describe('发送交易 - Job 弹窗流程', () => { await expect(page.locator('[data-testid="send-continue-button"]')).toBeVisible() } } else { - console.log('TransferPreviewJob may not have opened - check mock configuration') + console.log('TransferConfirmJob may not have opened - check mock configuration') } } else { console.log('Continue button not enabled - mock service may not be configured correctly') @@ -390,7 +390,7 @@ test.describe('发送交易 - Job 弹窗流程', () => { await continueBtn.click() await page.waitForTimeout(500) - const confirmBtn = page.locator('[data-testid="confirm-preview-button"]') + const confirmBtn = page.locator('[data-testid="confirm-transfer-button"]') if (await confirmBtn.isVisible()) { await confirmBtn.click() diff --git a/src/components/wallet/wallet-address-portfolio-from-provider.tsx b/src/components/wallet/wallet-address-portfolio-from-provider.tsx deleted file mode 100644 index 4fd8230eb..000000000 --- a/src/components/wallet/wallet-address-portfolio-from-provider.tsx +++ /dev/null @@ -1,107 +0,0 @@ -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 ( - - ) -} - -/** - * 从 Provider 获取地址资产组合 - * - * 使用 ChainProviderGate 确保 ChainProvider 可用,再使用响应式 API 获取数据。 - * 适用于 Stories 测试和任意地址查询场景。 - */ -export function WalletAddressPortfolioFromProvider(props: WalletAddressPortfolioFromProviderProps) { - return ( - -
-

Chain not supported: {props.chainId}

-
- - } - > - -
- ) -} diff --git a/src/i18n/locales/en/transaction.json b/src/i18n/locales/en/transaction.json index 8a476f426..226ddfd02 100644 --- a/src/i18n/locales/en/transaction.json +++ b/src/i18n/locales/en/transaction.json @@ -60,8 +60,7 @@ "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", - "previewTitle": "Transaction Preview" + "fee": "Fee" }, "destroyPage": { "title": "Destroy", diff --git a/src/i18n/locales/zh-CN/transaction.json b/src/i18n/locales/zh-CN/transaction.json index cb5017c59..b3c8b5a22 100644 --- a/src/i18n/locales/zh-CN/transaction.json +++ b/src/i18n/locales/zh-CN/transaction.json @@ -60,8 +60,7 @@ "twoStepSecretDescription": "该地址已设置安全密码,请输入安全密码确认转账。", "twoStepSecretPlaceholder": "输入安全密码", "twoStepSecretError": "安全密码错误", - "fee": "手续费", - "previewTitle": "交易预览" + "fee": "手续费" }, "destroyPage": { "title": "销毁", diff --git a/src/pages/send/index.tsx b/src/pages/send/index.tsx index bd2ec942e..af1aee480 100644 --- a/src/pages/send/index.tsx +++ b/src/pages/send/index.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigation, useActivityParams, useFlow } from '@/stackflow'; -import { setTransferPreviewCallback, setTransferWalletLockCallback, setScannerResultCallback } from '@/stackflow/activities/sheets'; +import { setTransferConfirmCallback, 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'; @@ -237,8 +237,8 @@ function SendPageContent() { haptics.impact('light'); - // Set up callback: TransferPreview -> TransferWalletLock (合并的钱包锁+二次签名) - setTransferPreviewCallback( + // Set up callback: TransferConfirm -> TransferWalletLock (合并的钱包锁+二次签名) + setTransferConfirmCallback( async () => { if (isWalletLockSheetOpen.current) return; isWalletLockSheetOpen.current = true; @@ -300,17 +300,13 @@ function SendPageContent() { } ); - push('TransferPreviewJob', { + push('TransferConfirmJob', { 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, }); }; diff --git a/src/stackflow/activities/sheets/TransferPreviewJob.tsx b/src/stackflow/activities/sheets/TransferPreviewJob.tsx deleted file mode 100644 index f7baa133f..000000000 --- a/src/stackflow/activities/sheets/TransferPreviewJob.tsx +++ /dev/null @@ -1,250 +0,0 @@ -/** - * 转账预览确认组件 - * - * 显示完整的交易详情预览,替代简单的 TransferConfirmJob - * 流程: Send页面(填写) → TransferPreviewJob(交易详情预览) → TransferWalletLockJob(签名+广播+状态) - */ -import { useState, useCallback, useRef, useEffect } from 'react'; -import type { ActivityComponentType } from '@stackflow/react'; -import { BottomSheet, SheetContent } from '@/components/layout/bottom-sheet'; -import { useTranslation } from 'react-i18next'; -import { cn } from '@/lib/utils'; -import { FeeDisplay } from '@/components/transaction/fee-display'; -import { AddressDisplay } from '@/components/wallet/address-display'; -import { ChainIcon } from '@/components/wallet/chain-icon'; -import { AmountDisplay } from '@/components/common'; -import type { ChainType } from '@/stores'; -import { - IconArrowUp as ArrowUp, - IconArrowDown as ArrowDown, -} from '@tabler/icons-react'; -import { useFlow } from '../../stackflow'; -import { ActivityParamsProvider, useActivityParams } from '../../hooks'; -import { setFeeEditCallback } from './FeeEditJob'; - -interface TransferPreviewConfig { - onConfirm: () => Promise; - minFee?: string | undefined; - onFeeChange?: ((newFee: string) => void) | undefined; -} - -let pendingConfig: TransferPreviewConfig | null = null; - -export function setTransferPreviewCallback( - onConfirm: () => Promise, - options?: { minFee?: string; onFeeChange?: (newFee: string) => void } -) { - pendingConfig = { - onConfirm, - minFee: options?.minFee, - onFeeChange: options?.onFeeChange, - }; -} - -function clearTransferPreviewCallback() { - pendingConfig = null; -} - -type TransferPreviewJobParams = { - /** 转账金额 */ - amount: string; - /** 资产符号 */ - symbol: string; - /** 资产小数位 */ - decimals?: string; - /** 法币价值 */ - fiatValue?: string; - /** 发送地址 */ - fromAddress: string; - /** 接收地址 */ - toAddress: string; - /** 手续费金额 */ - feeAmount: string; - /** 手续费符号 */ - feeSymbol: string; - /** 手续费法币价值 */ - feeFiatValue?: string; - /** 手续费加载中 */ - feeLoading?: string; - /** 链ID */ - chainId: string; - /** 链名称 */ - chainName?: string; -}; - -function TransferPreviewJobContent() { - const { t } = useTranslation(['transaction', 'common']); - const { pop, push } = useFlow(); - const params = useActivityParams(); - - const [isConfirming, setIsConfirming] = useState(false); - const [customFee, setCustomFee] = useState(null); - - // 捕获配置 - const configRef = useRef(pendingConfig); - const initialized = useRef(false); - - if (!initialized.current && pendingConfig) { - configRef.current = pendingConfig; - clearTransferPreviewCallback(); - initialized.current = true; - } - - useEffect(() => { - return () => { - clearTransferPreviewCallback(); - }; - }, []); - - const feeLoading = params.feeLoading === 'true'; - const displayFee = customFee ?? params.feeAmount; - const canEditFee = !!configRef.current?.onFeeChange; - const decimals = params.decimals ? parseInt(params.decimals, 10) : 8; - const amountNum = parseFloat(params.amount) || 0; - - const handleEditFee = useCallback(() => { - const config = configRef.current; - if (!config?.onFeeChange) return; - - setFeeEditCallback( - { - currentFee: displayFee, - minFee: config.minFee ?? params.feeAmount, - symbol: params.feeSymbol, - }, - (result) => { - setCustomFee(result.fee); - config.onFeeChange?.(result.fee); - } - ); - push('FeeEditJob', {}); - }, [displayFee, params.feeAmount, params.feeSymbol, push]); - - const handleConfirm = useCallback(async () => { - const config = configRef.current; - - if (!config?.onConfirm || isConfirming) return; - - setIsConfirming(true); - try { - pop(); - await config.onConfirm(); - } finally { - setIsConfirming(false); - } - }, [isConfirming, pop]); - - const handleClose = useCallback(() => { - pop(); - }, [pop]); - - return ( - - -
- {/* 金额头部 */} -
-
- -
- -
-

{t('type.send')}

- - {params.fiatValue && ( -

≈ ${params.fiatValue}

- )} -
-
- - {/* 交易详情 */} -
-

{t('detail.info')}

- - {/* 发送地址 */} -
- {t('detail.fromAddress')} - -
- -
- - {/* 接收地址 */} -
- {t('detail.toAddress')} - -
- -
- - {/* 网络 */} -
- {t('detail.network')} -
- - {params.chainName ?? params.chainId} -
-
- -
- - {/* 手续费 */} -
- {t('detail.fee')} - -
-
- - {/* 操作按钮 */} -
- - -
-
- - - ); -} - -export const TransferPreviewJob: ActivityComponentType = ({ params }) => { - return ( - - - - ); -}; diff --git a/src/stackflow/activities/sheets/TransferWalletLockJob.tsx b/src/stackflow/activities/sheets/TransferWalletLockJob.tsx index 4b5100b75..b313eaf17 100644 --- a/src/stackflow/activities/sheets/TransferWalletLockJob.tsx +++ b/src/stackflow/activities/sheets/TransferWalletLockJob.tsx @@ -55,7 +55,7 @@ type Step = 'wallet_lock' | 'two_step_secret'; function TransferWalletLockJobContent() { const { t } = useTranslation(["security", "transaction", "common"]); - const { pop, push, replace } = useFlow(); + const { pop, push } = useFlow(); const { title } = useActivityParams(); const clipboard = useClipboard(); const toast = useToast(); @@ -110,12 +110,7 @@ function TransferWalletLockJobContent() { return unsubscribe; }, [txHash, selectedChain, currentWallet?.address]); - // Navigate back to home (replace entire stack) - const goToHome = useCallback(() => { - replace('MainTabsActivity', {}, { animate: true }); - }, [replace]); - - // 上链成功后 5 秒倒计时自动关闭,返回首页 + // 上链成功后 5 秒倒计时自动关闭 useEffect(() => { if (txStatus !== 'confirmed') { setCountdown(null); @@ -127,7 +122,7 @@ function TransferWalletLockJobContent() { setCountdown((prev) => { if (prev === null || prev <= 1) { clearInterval(timer); - goToHome(); + pop(); return null; } return prev - 1; @@ -135,7 +130,7 @@ function TransferWalletLockJobContent() { }, 1000); return () => clearInterval(timer); - }, [txStatus, goToHome]); + }, [txStatus, pop]); // Get chain config for explorer URL const chainConfig = useMemo(() => { diff --git a/src/stackflow/activities/sheets/index.ts b/src/stackflow/activities/sheets/index.ts index 4466a8ce9..b7040f658 100644 --- a/src/stackflow/activities/sheets/index.ts +++ b/src/stackflow/activities/sheets/index.ts @@ -11,7 +11,6 @@ export { WalletAddJob } from "./WalletAddJob"; export { WalletListJob } from "./WalletListJob"; export { SecurityWarningJob, setSecurityWarningConfirmCallback } from "./SecurityWarningJob"; export { TransferConfirmJob, setTransferConfirmCallback } from "./TransferConfirmJob"; -export { TransferPreviewJob, setTransferPreviewCallback } from "./TransferPreviewJob"; export { TransferWalletLockJob, setTransferWalletLockCallback } from "./TransferWalletLockJob"; export { FeeEditJob, setFeeEditCallback, type FeeEditConfig, type FeeEditResult } from "./FeeEditJob"; export { ScannerJob, setScannerResultCallback, scanValidators, getValidatorForChain, type ScannerJobParams, type ScannerResultEvent, type ScanValidator } from "./ScannerJob"; diff --git a/src/stackflow/stackflow.ts b/src/stackflow/stackflow.ts index f81b1af91..9f41f8cd8 100644 --- a/src/stackflow/stackflow.ts +++ b/src/stackflow/stackflow.ts @@ -48,7 +48,6 @@ import { WalletListJob, SecurityWarningJob, TransferConfirmJob, - TransferPreviewJob, TransferWalletLockJob, FeeEditJob, ScannerJob, @@ -116,7 +115,6 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ WalletListJob: '/job/wallet-list', SecurityWarningJob: '/job/security-warning', TransferConfirmJob: '/job/transfer-confirm', - TransferPreviewJob: '/job/transfer-preview', TransferWalletLockJob: '/job/transfer-wallet-lock', FeeEditJob: '/job/fee-edit', ScannerJob: '/job/scanner', @@ -183,7 +181,6 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ WalletListJob, SecurityWarningJob, TransferConfirmJob, - TransferPreviewJob, TransferWalletLockJob, FeeEditJob, ScannerJob,