rwadurian/backend/mpc-system/services/service-party-app/src/pages/Create.tsx

269 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { QRCodeSVG } from 'qrcode.react';
import styles from './Create.module.css';
interface CreateSessionResult {
success: boolean;
sessionId?: string;
inviteCode?: string;
error?: string;
}
export default function Create() {
const navigate = useNavigate();
const [walletName, setWalletName] = useState('');
const [thresholdT, setThresholdT] = useState(3);
const [thresholdN, setThresholdN] = useState(5);
const [participantName, setParticipantName] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<CreateSessionResult | null>(null);
const [step, setStep] = useState<'config' | 'creating' | 'created'>('config');
const handleCreateSession = async () => {
if (!walletName.trim()) {
setError('请输入钱包名称');
return;
}
if (!participantName.trim()) {
setError('请输入您的名称');
return;
}
if (thresholdT > thresholdN) {
setError('签名阈值不能大于参与方总数');
return;
}
if (thresholdT < 1) {
setError('签名阈值至少为 1');
return;
}
if (thresholdN < 2) {
setError('参与方总数至少为 2');
return;
}
setStep('creating');
setIsLoading(true);
setError(null);
try {
const createResult = await window.electronAPI.grpc.createSession({
walletName: walletName.trim(),
thresholdT,
thresholdN,
initiatorName: participantName.trim(),
});
if (createResult.success) {
setResult(createResult);
setStep('created');
} else {
setError(createResult.error || '创建会话失败');
setStep('config');
}
} catch (err) {
setError('创建会话失败,请检查网络连接');
setStep('config');
} finally {
setIsLoading(false);
}
};
const handleCopyInviteCode = async () => {
if (result?.inviteCode) {
try {
await navigator.clipboard.writeText(result.inviteCode);
// 可以添加复制成功的提示
} catch (err) {
console.error('Failed to copy:', err);
}
}
};
const handleGoToSession = () => {
if (result?.sessionId) {
navigate(`/session/${result.sessionId}`);
}
};
return (
<div className={styles.container}>
<div className={styles.card}>
<h1 className={styles.title}></h1>
<p className={styles.subtitle}>3-of-5 - </p>
{step === 'config' && (
<div className={styles.form}>
{/* 混合托管说明 */}
<div className={styles.infoBox}>
<div className={styles.infoIcon}></div>
<div className={styles.infoContent}>
<strong></strong>
<ul className={styles.infoList}>
<li><strong>5 </strong>: 2 + 3 </li>
<li><strong></strong>: 3 </li>
<li><strong></strong>: 2 使</li>
<li><strong></strong>: </li>
</ul>
</div>
</div>
<div className={styles.inputGroup}>
<label className={styles.label}></label>
<input
type="text"
value={walletName}
onChange={(e) => setWalletName(e.target.value)}
placeholder="为您的共管钱包命名"
className={styles.input}
disabled={isLoading}
/>
</div>
<div className={styles.inputGroup}>
<label className={styles.label}> (T-of-N)</label>
<div className={styles.thresholdConfig}>
<div className={styles.thresholdItem}>
<span className={styles.thresholdLabel}> (T)</span>
<div className={styles.numberInput}>
<button
className={styles.numberButton}
onClick={() => setThresholdT(Math.max(1, thresholdT - 1))}
disabled={thresholdT <= 1}
>
-
</button>
<span className={styles.numberValue}>{thresholdT}</span>
<button
className={styles.numberButton}
onClick={() => setThresholdT(Math.min(thresholdN, thresholdT + 1))}
disabled={thresholdT >= thresholdN}
>
+
</button>
</div>
</div>
<div className={styles.thresholdDivider}>of</div>
<div className={styles.thresholdItem}>
<span className={styles.thresholdLabel}> (N)</span>
<div className={styles.numberInput}>
<button
className={styles.numberButton}
onClick={() => {
const newN = Math.max(2, thresholdN - 1);
setThresholdN(newN);
if (thresholdT > newN) setThresholdT(newN);
}}
disabled={thresholdN <= 2}
>
-
</button>
<span className={styles.numberValue}>{thresholdN}</span>
<button
className={styles.numberButton}
onClick={() => setThresholdN(thresholdN + 1)}
disabled={thresholdN >= 10}
>
+
</button>
</div>
</div>
</div>
<p className={styles.hint}>
{thresholdT} ( 2 )
</p>
</div>
<div className={styles.inputGroup}>
<label className={styles.label}></label>
<input
type="text"
value={participantName}
onChange={(e) => setParticipantName(e.target.value)}
placeholder="输入您的名称(其他参与者可见)"
className={styles.input}
disabled={isLoading}
/>
</div>
{error && <div className={styles.error}>{error}</div>}
<div className={styles.actions}>
<button
className={styles.secondaryButton}
onClick={() => navigate('/')}
disabled={isLoading}
>
</button>
<button
className={styles.primaryButton}
onClick={handleCreateSession}
disabled={isLoading}
>
</button>
</div>
</div>
)}
{step === 'creating' && (
<div className={styles.creating}>
<div className={styles.spinner}></div>
<p>...</p>
</div>
)}
{step === 'created' && result && (
<div className={styles.form}>
<div className={styles.successIcon}></div>
<h3 className={styles.successTitle}></h3>
{/* QR Code for mobile scanning */}
<div className={styles.qrSection}>
<div className={styles.qrCodeWrapper}>
<QRCodeSVG
value={result.inviteCode || ''}
size={180}
level="M"
includeMargin={true}
bgColor="#ffffff"
fgColor="#000000"
/>
</div>
<p className={styles.qrHint}>使 App </p>
</div>
<div className={styles.inviteSection}>
<label className={styles.label}></label>
<div className={styles.inviteCodeWrapper}>
<code className={styles.inviteCode}>{result.inviteCode}</code>
<button
className={styles.copyButton}
onClick={handleCopyInviteCode}
>
</button>
</div>
<p className={styles.hint}>
使
</p>
</div>
<div className={styles.actions}>
<button
className={styles.primaryButton}
onClick={handleGoToSession}
>
</button>
</div>
</div>
)}
</div>
</div>
);
}