mirror of
https://github.com/kccleoc/seedpgp-web.git
synced 2026-03-07 09:57:50 +08:00
fix(blender): display detailed validation errors
Improves the user experience by providing clear, inline error messages when mnemonic validation fails. - The validation logic now captures the error message from `mnemonicToEntropy`. - The UI displays this message directly below the invalid input field. - This addresses the ambiguity where an input was marked as invalid (red border) without explaining why, as seen in the user-provided screenshot.
This commit is contained in:
@@ -96,18 +96,23 @@ export function SeedBlender({ onDirtyStateChange, setMnemonicForBackup, requestT
|
|||||||
const validMnemonics = entries.map(e => e.decryptedMnemonic).filter((m): m is string => m !== null && m.length > 0);
|
const validMnemonics = entries.map(e => e.decryptedMnemonic).filter((m): m is string => m !== null && m.length > 0);
|
||||||
|
|
||||||
const validityPromises = entries.map(async (entry) => {
|
const validityPromises = entries.map(async (entry) => {
|
||||||
if (!entry.rawInput.trim()) return null;
|
if (!entry.rawInput.trim()) return { isValid: null, error: null };
|
||||||
if (entry.isEncrypted && !entry.decryptedMnemonic) return null; // Cannot validate until decrypted
|
if (entry.isEncrypted && !entry.decryptedMnemonic) return { isValid: null, error: null };
|
||||||
|
|
||||||
const textToValidate = entry.decryptedMnemonic || entry.rawInput;
|
const textToValidate = entry.decryptedMnemonic || entry.rawInput;
|
||||||
try {
|
try {
|
||||||
await mnemonicToEntropy(textToValidate.trim());
|
await mnemonicToEntropy(textToValidate.trim());
|
||||||
return true;
|
return { isValid: true, error: null };
|
||||||
} catch {
|
} catch (e: any) {
|
||||||
return false;
|
return { isValid: false, error: e.message || "Invalid mnemonic" };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const newValidity = await Promise.all(validityPromises);
|
const newValidationResults = await Promise.all(validityPromises);
|
||||||
setEntries(currentEntries => currentEntries.map((e, i) => ({ ...e, isValid: newValidity[i] })));
|
setEntries(currentEntries => currentEntries.map((e, i) => ({
|
||||||
|
...e,
|
||||||
|
isValid: newValidationResults[i]?.isValid ?? e.isValid,
|
||||||
|
error: newValidationResults[i]?.error ?? e.error,
|
||||||
|
})));
|
||||||
|
|
||||||
if (validMnemonics.length > 0) {
|
if (validMnemonics.length > 0) {
|
||||||
try {
|
try {
|
||||||
@@ -223,16 +228,19 @@ export function SeedBlender({ onDirtyStateChange, setMnemonicForBackup, requestT
|
|||||||
{entry.error && <p className="text-xs text-red-400">{entry.error}</p>}
|
{entry.error && <p className="text-xs text-red-400">{entry.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col sm:flex-row items-start gap-2">
|
<div className="space-y-1">
|
||||||
<div className="relative w-full">
|
<div className="flex flex-col sm:flex-row items-start gap-2">
|
||||||
<textarea value={entry.rawInput} onChange={(e) => updateEntry(index, { rawInput: e.target.value, decryptedMnemonic: e.target.value, isValid: null })} placeholder={`Mnemonic #${index + 1} (12 or 24 words)`} className={`w-full h-28 sm:h-24 p-3 pr-10 bg-slate-50 border-2 rounded-lg text-sm font-mono text-slate-900 placeholder:text-slate-400 ${getBorderColor(entry.isValid)}`} />
|
<div className="relative w-full">
|
||||||
{entry.isValid === true && <CheckCircle2 className="absolute top-3 right-3 text-green-500" />}
|
<textarea value={entry.rawInput} onChange={(e) => updateEntry(index, { rawInput: e.target.value, decryptedMnemonic: e.target.value, isValid: null, error: null })} placeholder={`Mnemonic #${index + 1} (12 or 24 words)`} className={`w-full h-28 sm:h-24 p-3 pr-10 bg-slate-50 border-2 rounded-lg text-sm font-mono text-slate-900 placeholder:text-slate-400 ${getBorderColor(entry.isValid)}`} />
|
||||||
{entry.isValid === false && <AlertTriangle className="absolute top-3 right-3 text-red-500" />}
|
{entry.isValid === true && <CheckCircle2 className="absolute top-3 right-3 text-green-500" />}
|
||||||
</div>
|
{entry.isValid === false && <AlertTriangle className="absolute top-3 right-3 text-red-500" />}
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
</div>
|
||||||
<button onClick={() => handleScan(index)} className="p-3 h-full bg-purple-600/20 text-purple-300 hover:bg-purple-600/50 hover:text-white rounded-md"><QrCode size={20} /></button>
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
<button onClick={() => handleRemoveEntry(entry.id)} className="p-3 h-full bg-red-600/20 text-red-400 hover:bg-red-600/50 hover:text-white rounded-md"><X size={20} /></button>
|
<button onClick={() => handleScan(index)} className="p-3 h-full bg-purple-600/20 text-purple-300 hover:bg-purple-600/50 hover:text-white rounded-md"><QrCode size={20} /></button>
|
||||||
|
<button onClick={() => handleRemoveEntry(entry.id)} className="p-3 h-full bg-red-600/20 text-red-400 hover:bg-red-600/50 hover:text-white rounded-md"><X size={20} /></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{entry.error && <p className="text-xs text-red-400 px-1">{entry.error}</p>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user