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
153 changes: 153 additions & 0 deletions WALLET_IMPROVEMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# 钱包连接改进说明

## 改进概览

本次改进针对钱包连接功能进行了全面的优化,提升了用户体验和系统稳定性。

## 主要改进点

### 1. ✅ 网络切换功能增强

**改进内容:**
- 添加了 `switchToCorrectNetwork()` 方法,允许用户一键切换到正确的 GenLayer 网络
- 在 `AccountPanel` 组件中添加了"切换到 GenLayer 网络"按钮
- 当用户在错误网络时,会显示明确的警告和操作按钮

**代码位置:**
- `frontend/lib/genlayer/WalletProvider.tsx` - 新增 `switchToCorrectNetwork` 方法
- `frontend/components/AccountPanel.tsx` - 添加网络切换按钮和 UI

**用户体验:**
- 用户不再需要手动在 MetaMask 中切换网络
- 提供清晰的操作指引和反馈

### 2. ✅ 账户切换后的数据自动刷新

**改进内容:**
- 当用户切换账户时,自动触发所有相关数据的刷新
- 使用自定义事件 `walletAccountChanged` 通知所有组件
- 所有使用钱包数据的 hooks 都会自动响应账户变化

**代码位置:**
- `frontend/lib/genlayer/WalletProvider.tsx` - 在 `switchWalletAccount` 中触发事件
- `frontend/lib/hooks/useFootballBets.ts` - 所有 hooks 监听账户变化事件

**影响的 Hooks:**
- `useBets()` - 自动刷新投注列表
- `usePlayerPoints()` - 自动刷新用户积分
- `useLeaderboard()` - 自动刷新排行榜

### 3. ✅ 连接超时处理

**改进内容:**
- 为 `connectMetaMask()` 函数添加了超时机制(默认 30 秒)
- 使用 `Promise.race()` 实现超时控制
- 超时时会抛出明确的错误信息

**代码位置:**
- `frontend/lib/genlayer/client.ts` - `connectMetaMask` 函数

**好处:**
- 防止连接过程无限等待
- 提供更好的错误反馈

### 4. ✅ 网络状态变化提示

**改进内容:**
- 当网络切换时,显示成功或警告提示
- 在 Navbar 顶部添加网络警告横幅(当用户在错误网络时)
- 网络切换成功时显示确认消息

**代码位置:**
- `frontend/lib/genlayer/WalletProvider.tsx` - `handleChainChanged` 事件处理
- `frontend/components/Navbar.tsx` - 网络警告横幅

**用户体验:**
- 实时反馈网络状态变化
- 顶部横幅提供持续可见的警告

### 5. ✅ 错误处理和用户反馈优化

**改进内容:**
- 改进了错误消息的显示方式
- 区分不同类型的错误(用户拒绝、超时、网络错误等)
- 账户切换成功时显示确认消息

**代码位置:**
- `frontend/lib/genlayer/WalletProvider.tsx` - 所有错误处理逻辑
- `frontend/components/AccountPanel.tsx` - UI 错误显示

## 技术细节

### 自定义事件系统

使用浏览器自定义事件来通知组件账户变化:

```typescript
window.dispatchEvent(new CustomEvent("walletAccountChanged", {
detail: { address: newAddress }
}));
```

组件监听事件并自动刷新数据:

```typescript
useEffect(() => {
const handleAccountChange = () => {
queryClient.invalidateQueries({ queryKey: ["bets"] });
};
window.addEventListener("walletAccountChanged", handleAccountChange);
return () => {
window.removeEventListener("walletAccountChanged", handleAccountChange);
};
}, [queryClient]);
```

### 超时机制实现

```typescript
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error("Connection timeout. Please try again."));
}, timeoutMs);
});

return Promise.race([connectPromise, timeoutPromise]);
```

## 使用示例

### 切换网络

```typescript
const { switchToCorrectNetwork } = useWallet();

// 在按钮点击时调用
await switchToCorrectNetwork();
```

### 切换账户

```typescript
const { switchWalletAccount } = useWallet();

// 切换账户,数据会自动刷新
await switchWalletAccount();
```

## 未来可能的改进

1. **重试机制**:连接失败时提供自动重试选项
2. **连接状态持久化**:记住用户偏好(是否自动连接)
3. **多钱包支持**:支持 WalletConnect 等其他钱包
4. **连接历史**:记录连接历史,方便快速切换
5. **网络状态监控**:定期检查网络状态,自动提示切换

## 测试建议

1. 测试在不同网络间切换
2. 测试账户切换后的数据刷新
3. 测试连接超时场景
4. 测试用户拒绝连接/切换的场景
5. 测试网络警告横幅的显示和隐藏

56 changes: 47 additions & 9 deletions frontend/components/AccountPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useState } from "react";
import { User, LogOut, AlertCircle, ExternalLink } from "lucide-react";
import { User, LogOut, AlertCircle, ExternalLink, Loader2 } from "lucide-react";
import { useWallet } from "@/lib/genlayer/wallet";
import { usePlayerPoints } from "@/lib/hooks/useFootballBets";
import { success, error, userRejected } from "@/lib/utils/toast";
Expand Down Expand Up @@ -29,6 +29,7 @@ export function AccountPanel() {
connectWallet,
disconnectWallet,
switchWalletAccount,
switchToCorrectNetwork,
} = useWallet();

const { data: points = 0 } = usePlayerPoints(address);
Expand All @@ -37,6 +38,7 @@ export function AccountPanel() {
const [connectionError, setConnectionError] = useState("");
const [isConnecting, setIsConnecting] = useState(false);
const [isSwitching, setIsSwitching] = useState(false);
const [isSwitchingNetwork, setIsSwitchingNetwork] = useState(false);

const handleConnect = async () => {
if (!isMetaMaskInstalled) {
Expand Down Expand Up @@ -92,6 +94,23 @@ export function AccountPanel() {
}
};

const handleSwitchNetwork = async () => {
try {
setIsSwitchingNetwork(true);
setConnectionError("");
await switchToCorrectNetwork();
} catch (err: any) {
console.error("Failed to switch network:", err);

// Don't show error if user cancelled
if (!err.message?.includes("rejected")) {
setConnectionError(err.message || "Failed to switch network");
}
} finally {
setIsSwitchingNetwork(false);
}
};

// Not connected state
if (!isConnected) {
return (
Expand Down Expand Up @@ -241,14 +260,33 @@ export function AccountPanel() {
</div>

{!isOnCorrectNetwork && (
<Alert variant="default" className="bg-yellow-500/10 border-yellow-500/20">
<AlertCircle className="h-4 w-4 text-yellow-500" />
<AlertTitle>Network Warning</AlertTitle>
<AlertDescription>
You&apos;re not on the GenLayer network. Please switch networks in
MetaMask or try reconnecting.
</AlertDescription>
</Alert>
<div className="space-y-3">
<Alert variant="default" className="bg-yellow-500/10 border-yellow-500/20">
<AlertCircle className="h-4 w-4 text-yellow-500" />
<AlertTitle>Network Warning</AlertTitle>
<AlertDescription>
You&apos;re not on the GenLayer network. Please switch to continue using the app.
</AlertDescription>
</Alert>
<Button
onClick={handleSwitchNetwork}
variant="gradient"
className="w-full"
disabled={isSwitchingNetwork || isLoading}
>
{isSwitchingNetwork ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Switching Network...
</>
) : (
<>
<ExternalLink className="w-4 h-4 mr-2" />
Switch to GenLayer Network
</>
)}
</Button>
</div>
)}

{connectionError && (
Expand Down
29 changes: 25 additions & 4 deletions frontend/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { useState, useEffect } from "react";
import { AccountPanel } from "./AccountPanel";
import { CreateBetModal } from "./CreateBetModal";
import { useBets } from "@/lib/hooks/useFootballBets";
import { useWallet } from "@/lib/genlayer/wallet";
import { Logo, LogoMark } from "./Logo";
import { Alert, AlertDescription } from "./ui/alert";
import { AlertCircle } from "lucide-react";

export function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [scrollProgress, setScrollProgress] = useState(0);
const { data: bets } = useBets();
const { isConnected, isOnCorrectNetwork } = useWallet();

useEffect(() => {
const handleScroll = () => {
Expand Down Expand Up @@ -44,10 +48,27 @@ export function Navbar() {
const resolvedBets = bets?.filter(bet => bet.has_resolved).length || 0;

return (
<header
className="fixed top-0 left-0 right-0 z-50 transition-all duration-500 ease-out"
style={{ paddingTop: `${paddingTop}px` }}
>
<>
{/* Network Warning Banner */}
{isConnected && !isOnCorrectNetwork && (
<div className="fixed top-0 left-0 right-0 z-[60] bg-yellow-500/10 border-b border-yellow-500/20 backdrop-blur-sm">
<div className="max-w-7xl mx-auto px-4 py-2">
<Alert variant="default" className="bg-transparent border-0 p-0">
<AlertCircle className="h-4 w-4 text-yellow-500" />
<AlertDescription className="text-sm text-yellow-400">
You&apos;re not on the GenLayer network. Please switch networks to continue.
</AlertDescription>
</Alert>
</div>
</div>
)}
<header
className="fixed top-0 left-0 right-0 z-50 transition-all duration-500 ease-out"
style={{
paddingTop: `${paddingTop}px`,
marginTop: isConnected && !isOnCorrectNetwork ? '48px' : '0'
}}
>
<div
className="transition-all duration-500 ease-out"
style={{
Expand Down
Loading