rwadurian/frontend/admin-web/src/components/features/co-managed-wallet/CreateWalletModal.tsx

322 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect } from 'react';
import styles from './co-managed-wallet.module.scss';
import { cn } from '@/utils/helpers';
import ThresholdConfig from './ThresholdConfig';
import InviteQRCode from './InviteQRCode';
import ParticipantList from './ParticipantList';
import SessionProgress from './SessionProgress';
import WalletResult from './WalletResult';
interface Participant {
partyId: string;
name: string;
status: 'waiting' | 'ready' | 'processing' | 'completed' | 'failed';
joinedAt?: string;
}
interface SessionState {
sessionId: string;
inviteCode: string;
inviteUrl: string;
status: 'waiting' | 'ready' | 'processing' | 'completed' | 'failed';
participants: Participant[];
currentRound: number;
totalRounds: number;
publicKey?: string;
error?: string;
createdAt?: string;
}
interface CreateWalletModalProps {
isOpen: boolean;
onClose: () => void;
}
type Step = 'config' | 'invite' | 'progress' | 'result';
/**
* 创建共管钱包向导对话框
*/
export default function CreateWalletModal({ isOpen, onClose }: CreateWalletModalProps) {
const [step, setStep] = useState<Step>('config');
const [walletName, setWalletName] = useState('');
const [thresholdT, setThresholdT] = useState(2);
const [thresholdN, setThresholdN] = useState(3);
const [initiatorName, setInitiatorName] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [session, setSession] = useState<SessionState | null>(null);
// 重置状态
useEffect(() => {
if (!isOpen) {
setStep('config');
setWalletName('');
setThresholdT(2);
setThresholdN(3);
setInitiatorName('');
setIsLoading(false);
setError(null);
setSession(null);
}
}, [isOpen]);
// 创建会话
const handleCreateSession = async () => {
if (!walletName.trim()) {
setError('请输入钱包名称');
return;
}
if (!initiatorName.trim()) {
setError('请输入您的名称');
return;
}
if (thresholdT > thresholdN) {
setError('签名阈值不能大于参与方总数');
return;
}
setIsLoading(true);
setError(null);
try {
// TODO: 调用 API 创建会话
// const result = await coManagedWalletService.createSession({
// walletName: walletName.trim(),
// thresholdT,
// thresholdN,
// initiatorName: initiatorName.trim(),
// });
// 模拟创建成功
const mockSession: SessionState = {
sessionId: 'session-' + Date.now(),
inviteCode: 'ABCD-1234-EFGH',
inviteUrl: `https://app.rwadurian.com/join/ABCD-1234-EFGH`,
status: 'waiting',
participants: [
{
partyId: 'party-1',
name: initiatorName.trim(),
status: 'ready',
joinedAt: new Date().toISOString(),
},
],
currentRound: 0,
totalRounds: 3,
createdAt: new Date().toISOString(),
};
setSession(mockSession);
setStep('invite');
} catch (err) {
setError('创建会话失败,请重试');
} finally {
setIsLoading(false);
}
};
// 开始密钥生成
const handleStartKeygen = async () => {
if (!session) return;
setStep('progress');
setSession(prev => prev ? { ...prev, status: 'processing' } : null);
// TODO: 监听会话状态更新
// 模拟进度更新
for (let i = 1; i <= 3; i++) {
await new Promise(resolve => setTimeout(resolve, 2000));
setSession(prev => prev ? { ...prev, currentRound: i } : null);
}
// 模拟完成
setSession(prev => prev ? {
...prev,
status: 'completed',
publicKey: '0x1234567890abcdef1234567890abcdef12345678',
} : null);
setStep('result');
};
// 渲染步骤指示器
const renderStepIndicator = () => {
const steps = [
{ key: 'config', label: '配置' },
{ key: 'invite', label: '邀请' },
{ key: 'progress', label: '生成' },
{ key: 'result', label: '完成' },
];
const currentIndex = steps.findIndex(s => s.key === step);
return (
<div className={styles.createWalletModal__steps}>
{steps.map((s, index) => (
<div
key={s.key}
className={cn(
styles.createWalletModal__step,
index < currentIndex && styles['createWalletModal__step--completed'],
index === currentIndex && styles['createWalletModal__step--active']
)}
>
<div className={styles.createWalletModal__stepNumber}>{index + 1}</div>
<span className={styles.createWalletModal__stepLabel}>{s.label}</span>
</div>
))}
</div>
);
};
if (!isOpen) return null;
return (
<div className={styles.modalOverlay} onClick={onClose}>
<div className={styles.createWalletModal} onClick={e => e.stopPropagation()}>
<div className={styles.createWalletModal__header}>
<h2 className={styles.createWalletModal__title}></h2>
<button
type="button"
className={styles.createWalletModal__closeBtn}
onClick={onClose}
aria-label="关闭"
>
×
</button>
</div>
{renderStepIndicator()}
<div className={styles.createWalletModal__content}>
{step === 'config' && (
<div className={styles.createWalletModal__form}>
<div className={styles.createWalletModal__field}>
<label className={styles.createWalletModal__label}></label>
<input
type="text"
value={walletName}
onChange={e => setWalletName(e.target.value)}
placeholder="为您的共管钱包命名"
className={styles.createWalletModal__input}
disabled={isLoading}
/>
</div>
<div className={styles.createWalletModal__field}>
<label className={styles.createWalletModal__label}> (T-of-N)</label>
<ThresholdConfig
thresholdT={thresholdT}
thresholdN={thresholdN}
onThresholdTChange={setThresholdT}
onThresholdNChange={setThresholdN}
disabled={isLoading}
/>
<p className={styles.createWalletModal__hint}>
{thresholdT}
</p>
</div>
<div className={styles.createWalletModal__field}>
<label className={styles.createWalletModal__label}></label>
<input
type="text"
value={initiatorName}
onChange={e => setInitiatorName(e.target.value)}
placeholder="输入您的名称(其他参与者可见)"
className={styles.createWalletModal__input}
disabled={isLoading}
/>
</div>
{error && <p className={styles.createWalletModal__error}>{error}</p>}
<div className={styles.createWalletModal__actions}>
<button
type="button"
className={styles.createWalletModal__cancelBtn}
onClick={onClose}
disabled={isLoading}
>
</button>
<button
type="button"
className={styles.createWalletModal__submitBtn}
onClick={handleCreateSession}
disabled={isLoading}
>
{isLoading ? '创建中...' : '创建会话'}
</button>
</div>
</div>
)}
{step === 'invite' && session && (
<div className={styles.createWalletModal__invite}>
<InviteQRCode
inviteCode={session.inviteCode}
inviteUrl={session.inviteUrl}
/>
<ParticipantList
participants={session.participants}
totalRequired={thresholdN}
/>
<div className={styles.createWalletModal__actions}>
<button
type="button"
className={styles.createWalletModal__cancelBtn}
onClick={() => setStep('config')}
>
</button>
<button
type="button"
className={styles.createWalletModal__submitBtn}
onClick={handleStartKeygen}
disabled={session.participants.length < thresholdN}
>
{session.participants.length < thresholdN
? `等待参与方 (${session.participants.length}/${thresholdN})`
: '开始生成密钥'}
</button>
</div>
</div>
)}
{step === 'progress' && session && (
<div className={styles.createWalletModal__progress}>
<SessionProgress
status={session.status}
currentRound={session.currentRound}
totalRounds={session.totalRounds}
error={session.error}
/>
<ParticipantList
participants={session.participants}
totalRequired={thresholdN}
/>
</div>
)}
{step === 'result' && session && session.publicKey && (
<WalletResult
walletName={walletName}
publicKey={session.publicKey}
threshold={{ t: thresholdT, n: thresholdN }}
participants={session.participants}
createdAt={session.createdAt || new Date().toISOString()}
onClose={onClose}
/>
)}
</div>
</div>
</div>
);
}