Merge pull request #5699 from nymtech/fix/sign-in-page-wallet
Allow copy and paste on logins fields for the wallet
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
"updater:allow-download",
|
"updater:allow-download",
|
||||||
"updater:allow-download-and-install",
|
"updater:allow-download-and-install",
|
||||||
"updater:allow-install",
|
"updater:allow-install",
|
||||||
"core:event:allow-listen"
|
"core:event:allow-listen",
|
||||||
|
"shell:allow-open"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
{"main-capability":{"identifier":"main-capability","description":"Default capability for Nym Wallet main window","local":true,"windows":["main","nymWalletApp","log"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","opener:allow-open-url","opener:allow-default-urls","core:window:allow-set-title","core:app:allow-version","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","updater:default","updater:allow-check","updater:allow-download","updater:allow-download-and-install","updater:allow-install","core:event:allow-listen"],"platforms":["linux","macOS","windows"]}}
|
{"main-capability":{"identifier":"main-capability","description":"Default capability for Nym Wallet main window","local":true,"windows":["main","nymWalletApp","log"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","opener:allow-open-url","opener:allow-default-urls","core:window:allow-set-title","core:app:allow-version","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","updater:default","updater:allow-check","updater:allow-download","updater:allow-download-and-install","updater:allow-install","core:event:allow-listen","shell:allow-open"],"platforms":["linux","macOS","windows"]}}
|
||||||
@@ -57,6 +57,7 @@ const FormContextProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const [signature, setSignature] = useState('');
|
const [signature, setSignature] = useState('');
|
||||||
|
|
||||||
const onError = (e: string) => {
|
const onError = (e: string) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(e);
|
console.error(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const SignMessageModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
const sig = await signMessage(message);
|
const sig = await signMessage(message);
|
||||||
setSignature(sig || '');
|
setSignature(sig || '');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error('Signing failed:', err);
|
console.error('Signing failed:', err);
|
||||||
setSignature('');
|
setSignature('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ export const CurrencyFormFieldWithPaste = ({
|
|||||||
processPastedText(clipboardText);
|
processPastedText(clipboardText);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error('Error accessing clipboard:', err);
|
console.error('Error accessing clipboard:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import React, { FC, useEffect, useRef, useState } from 'react';
|
import React, { FC, useEffect, useRef, useState } from 'react';
|
||||||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||||
import { listen } from '@tauri-apps/api/event';
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { Box, Paper, Chip, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
import {
|
||||||
|
Box,
|
||||||
|
Paper,
|
||||||
|
Chip,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
|
||||||
// see https://github.com/tauri-apps/tauri-plugin-log/blob/dev/webview-src/index.ts#L4
|
// see https://github.com/tauri-apps/tauri-plugin-log/blob/dev/webview-src/index.ts#L4
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
@@ -29,24 +40,75 @@ const getLogLevelName = (value: LogLevel) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getLogLevelColor = (level: LogLevel, theme: any) => {
|
||||||
|
switch (level) {
|
||||||
|
case LogLevel.Trace:
|
||||||
|
return {
|
||||||
|
bg: '#e8f4f8',
|
||||||
|
color: '#2c3e50',
|
||||||
|
chipBg: '#e8f4f8',
|
||||||
|
};
|
||||||
|
case LogLevel.Debug:
|
||||||
|
return {
|
||||||
|
bg: '#e8f0f8',
|
||||||
|
color: '#2c3e50',
|
||||||
|
chipBg: '#e8f0f8',
|
||||||
|
};
|
||||||
|
case LogLevel.Info:
|
||||||
|
return {
|
||||||
|
bg: '#e8f8e8',
|
||||||
|
color: '#2c3e50',
|
||||||
|
chipBg: '#e8f8e8',
|
||||||
|
};
|
||||||
|
case LogLevel.Warn:
|
||||||
|
return {
|
||||||
|
bg: '#fff8e0',
|
||||||
|
color: '#5d4037',
|
||||||
|
chipBg: '#fff8e0',
|
||||||
|
};
|
||||||
|
case LogLevel.Error:
|
||||||
|
return {
|
||||||
|
bg: '#ffe8e8',
|
||||||
|
color: '#c0392b',
|
||||||
|
chipBg: '#ffe8e8',
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
bg: theme.palette.mode === 'dark' ? '#333' : '#f0f0f0',
|
||||||
|
color: theme.palette.mode === 'dark' ? '#fff' : '#000',
|
||||||
|
chipBg: theme.palette.mode === 'dark' ? '#444' : '#e0e0e0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// see https://github.com/tauri-apps/tauri-plugin-log/blob/dev/webview-src/index.ts#L147
|
// see https://github.com/tauri-apps/tauri-plugin-log/blob/dev/webview-src/index.ts#L147
|
||||||
interface RecordPayload {
|
interface RecordPayload {
|
||||||
level: LogLevel;
|
level: LogLevel;
|
||||||
message: string;
|
message: string;
|
||||||
|
timestamp?: number; // Adding timestamp for unique key generation
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LogViewer: FC = () => {
|
export const LogViewer: FC = () => {
|
||||||
|
const theme = useTheme();
|
||||||
const unlisten = useRef<UnlistenFn>();
|
const unlisten = useRef<UnlistenFn>();
|
||||||
const messages = useRef<RecordPayload[]>([]);
|
const [messages, setMessages] = useState<RecordPayload[]>([]);
|
||||||
const [messageCount, setMessageCount] = useState(0);
|
const [messageCount, setMessageCount] = useState(0);
|
||||||
|
const tableRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listen('log://log', (event) => {
|
listen('log://log', (event) => {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(event.payload);
|
|
||||||
const payload = event.payload as RecordPayload;
|
const payload = event.payload as RecordPayload;
|
||||||
messages.current.unshift(payload);
|
const payloadWithTimestamp = {
|
||||||
|
...payload,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages((prev) => [payloadWithTimestamp, ...prev]);
|
||||||
setMessageCount((prev) => prev + 1);
|
setMessageCount((prev) => prev + 1);
|
||||||
|
|
||||||
|
if (tableRef.current) {
|
||||||
|
tableRef.current.scrollTop = 0;
|
||||||
|
}
|
||||||
}).then((fn) => {
|
}).then((fn) => {
|
||||||
unlisten.current = fn;
|
unlisten.current = fn;
|
||||||
});
|
});
|
||||||
@@ -59,25 +121,102 @@ export const LogViewer: FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ height: '100vh', width: '100vw', display: 'grid', gridTemplateRows: '1fr auto' }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: '100vh',
|
||||||
|
width: '100vw',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateRows: '1fr auto',
|
||||||
|
bgcolor: theme.palette.mode === 'dark' ? '#1e1e1e' : '#f5f5f5',
|
||||||
|
color: theme.palette.mode === 'dark' ? '#ffffff' : '#000000',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box sx={{ overflowY: 'hidden', p: 2 }}>
|
<Box sx={{ overflowY: 'hidden', p: 2 }}>
|
||||||
<TableContainer component={Paper} sx={{ maxHeight: '100%' }}>
|
<TableContainer
|
||||||
|
component={Paper}
|
||||||
|
sx={{
|
||||||
|
maxHeight: '100%',
|
||||||
|
bgcolor: '#ffffff',
|
||||||
|
boxShadow: theme.shadows[3],
|
||||||
|
borderRadius: '4px',
|
||||||
|
}}
|
||||||
|
ref={tableRef}
|
||||||
|
>
|
||||||
<Table size="small" stickyHeader>
|
<Table size="small" stickyHeader>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>Severity</TableCell>
|
<TableCell
|
||||||
<TableCell>Log message</TableCell>
|
sx={{
|
||||||
|
bgcolor: '#f0f0f0',
|
||||||
|
color: '#333333',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
width: '120px',
|
||||||
|
borderBottom: '1px solid #e0e0e0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Severity
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#f0f0f0',
|
||||||
|
color: '#333333',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderBottom: '1px solid #e0e0e0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Log message
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{messages.current.map((m) => (
|
{messages.map((m, index) => {
|
||||||
<TableRow sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
const levelColors = getLogLevelColor(m.level, theme);
|
||||||
<TableCell sx={{ padding: 1 }}>
|
return (
|
||||||
<Chip label={getLogLevelName(m.level)} variant="outlined" size="small" />
|
<TableRow
|
||||||
</TableCell>
|
key={`log-${m.timestamp || index}`}
|
||||||
<TableCell sx={{ padding: 1, fontFamily: 'Monospace' }}>{m.message}</TableCell>
|
sx={{
|
||||||
</TableRow>
|
bgcolor: levelColors.bg,
|
||||||
))}
|
'&:hover': {
|
||||||
|
filter: 'brightness(0.95)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
padding: 1,
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
width: '120px',
|
||||||
|
bgcolor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
label={getLogLevelName(m.level)}
|
||||||
|
variant="filled"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
bgcolor: levelColors.chipBg,
|
||||||
|
color: levelColors.color,
|
||||||
|
fontWeight: 'medium',
|
||||||
|
minWidth: '70px',
|
||||||
|
border: '1px solid rgba(0,0,0,0.1)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
padding: 1,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
color: levelColors.color,
|
||||||
|
bgcolor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{m.message}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
@@ -86,9 +225,10 @@ export const LogViewer: FC = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
p: 1,
|
p: 1,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
fontSize: 'small',
|
fontSize: '0.75rem',
|
||||||
borderTop: '2px solid',
|
borderTop: '1px solid #e0e0e0',
|
||||||
borderTopColor: (theme) => theme.palette.divider,
|
bgcolor: '#ffffff',
|
||||||
|
color: '#666666',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{messageCount} log entries since opening this window
|
{messageCount} log entries since opening this window
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { PasswordInput } from '@nymproject/react/textfields/Password';
|
||||||
|
import { readText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
|
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
|
||||||
|
interface PasteButtonProps {
|
||||||
|
onPaste: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PasteButton: React.FC<PasteButtonProps> = ({ onPaste }) => (
|
||||||
|
<Tooltip title="Paste from clipboard">
|
||||||
|
<IconButton size="small" onClick={onPaste} aria-label="paste from clipboard">
|
||||||
|
<ContentPasteIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface EnhancedPasswordInputProps {
|
||||||
|
password: string;
|
||||||
|
onUpdatePassword: (password: string) => void;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
error?: string;
|
||||||
|
autoFocus?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnhancedPasswordInput: React.FC<EnhancedPasswordInputProps> = ({
|
||||||
|
password,
|
||||||
|
onUpdatePassword,
|
||||||
|
...otherProps
|
||||||
|
}) => {
|
||||||
|
const inputRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const findInputElement = () => {
|
||||||
|
if (!inputRef.current) return undefined;
|
||||||
|
|
||||||
|
const input = inputRef.current.querySelector('input');
|
||||||
|
if (!input) return undefined;
|
||||||
|
|
||||||
|
const handleKeyDown = async (e: KeyboardEvent) => {
|
||||||
|
if (document.activeElement !== input) return;
|
||||||
|
|
||||||
|
if ((e.metaKey || e.ctrlKey) && e.key === 'a') {
|
||||||
|
e.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
(input as HTMLInputElement).select();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((e.metaKey || e.ctrlKey) && e.key === 'v') {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const clipboardText = await readText();
|
||||||
|
if (clipboardText) {
|
||||||
|
onUpdatePassword(clipboardText);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Failed to paste text:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
input.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
input.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = findInputElement();
|
||||||
|
const timeoutId = setTimeout(findInputElement, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (cleanup) cleanup();
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
}, [onUpdatePassword]);
|
||||||
|
|
||||||
|
const handlePaste = async () => {
|
||||||
|
try {
|
||||||
|
const clipboardText = await readText();
|
||||||
|
if (clipboardText) {
|
||||||
|
onUpdatePassword(clipboardText);
|
||||||
|
|
||||||
|
const input = inputRef.current?.querySelector('input');
|
||||||
|
if (input) {
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Failed to paste from clipboard:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box position="relative" ref={inputRef}>
|
||||||
|
<PasswordInput password={password} onUpdatePassword={onUpdatePassword} {...otherProps} />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '40px',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PasteButton onPaste={handlePaste} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { MnemonicInput as OriginalMnemonicInput } from '@nymproject/react/textfields/Mnemonic';
|
||||||
|
import { readText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
|
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
|
||||||
|
interface PasteButtonProps {
|
||||||
|
onPaste: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PasteButton: React.FC<PasteButtonProps> = ({ onPaste }) => (
|
||||||
|
<Tooltip title="Paste from clipboard">
|
||||||
|
<IconButton size="small" onClick={onPaste} aria-label="paste from clipboard">
|
||||||
|
<ContentPasteIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface EnhancedMnemonicInputProps {
|
||||||
|
mnemonic: string;
|
||||||
|
onUpdateMnemonic: (mnemonic: string) => void;
|
||||||
|
error?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { OriginalMnemonicInput as MnemonicInput };
|
||||||
|
|
||||||
|
export const EnhancedMnemonicInput: React.FC<EnhancedMnemonicInputProps> = ({
|
||||||
|
mnemonic,
|
||||||
|
onUpdateMnemonic,
|
||||||
|
...otherProps
|
||||||
|
}) => {
|
||||||
|
const inputRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const findInputElement = () => {
|
||||||
|
if (!inputRef.current) return undefined;
|
||||||
|
|
||||||
|
const textarea = inputRef.current.querySelector('textarea');
|
||||||
|
const input = textarea || inputRef.current.querySelector('input');
|
||||||
|
|
||||||
|
if (!input) return undefined;
|
||||||
|
|
||||||
|
// Fix the event type issue by casting Event to KeyboardEvent
|
||||||
|
const handleKeyDown = async (e: Event) => {
|
||||||
|
const keyEvent = e as KeyboardEvent;
|
||||||
|
if (document.activeElement !== input) return;
|
||||||
|
|
||||||
|
if ((keyEvent.metaKey || keyEvent.ctrlKey) && keyEvent.key === 'a') {
|
||||||
|
keyEvent.preventDefault();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (textarea) {
|
||||||
|
textarea.select();
|
||||||
|
} else {
|
||||||
|
(input as HTMLInputElement).select();
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((keyEvent.metaKey || keyEvent.ctrlKey) && keyEvent.key === 'v') {
|
||||||
|
keyEvent.preventDefault();
|
||||||
|
try {
|
||||||
|
const clipboardText = await readText();
|
||||||
|
if (clipboardText) {
|
||||||
|
onUpdateMnemonic(clipboardText.trim());
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Failed to paste text:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
input.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
input.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanup = findInputElement();
|
||||||
|
const timeoutId = setTimeout(findInputElement, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (cleanup) cleanup();
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
}, [onUpdateMnemonic]);
|
||||||
|
|
||||||
|
const handlePaste = async () => {
|
||||||
|
try {
|
||||||
|
const clipboardText = await readText();
|
||||||
|
if (clipboardText) {
|
||||||
|
onUpdateMnemonic(clipboardText.trim());
|
||||||
|
|
||||||
|
const textarea = inputRef.current?.querySelector('textarea');
|
||||||
|
const input = textarea || inputRef.current?.querySelector('input');
|
||||||
|
if (input) {
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Failed to paste from clipboard:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box position="relative" ref={inputRef}>
|
||||||
|
<OriginalMnemonicInput mnemonic={mnemonic} onUpdateMnemonic={onUpdateMnemonic} {...otherProps} />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '14px',
|
||||||
|
top: '16px',
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PasteButton onPaste={handlePaste} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -12,7 +12,6 @@ export const TauriLink: React.FC<LinkProps & any> = (props) => {
|
|||||||
|
|
||||||
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
|
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log('Opening link in browser:', href);
|
|
||||||
await openUrl(href);
|
await openUrl(href);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { Box, Button, FormControl, Stack } from '@mui/material';
|
import { Box, Button, FormControl, Stack } from '@mui/material';
|
||||||
import { AppContext } from 'src/context';
|
import { AppContext } from 'src/context';
|
||||||
import { isPasswordCreated } from 'src/requests';
|
import { isPasswordCreated } from 'src/requests';
|
||||||
import { MnemonicInput } from '@nymproject/react/textfields/Mnemonic';
|
import { EnhancedMnemonicInput } from 'src/components/Login/MnemonicLoginFormWrapper';
|
||||||
import { Subtitle } from '../components';
|
import { Subtitle } from '../components';
|
||||||
|
|
||||||
export const SignInMnemonic = () => {
|
export const SignInMnemonic = () => {
|
||||||
@@ -38,7 +38,11 @@ export const SignInMnemonic = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
|
<EnhancedMnemonicInput
|
||||||
|
mnemonic={mnemonic}
|
||||||
|
onUpdateMnemonic={(mnc: string) => setMnemonic(mnc)}
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
<Button variant="contained" size="large" fullWidth type="submit">
|
<Button variant="contained" size="large" fullWidth type="submit">
|
||||||
Sign in with mnemonic
|
Sign in with mnemonic
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Box, Button, FormControl, Stack } from '@mui/material';
|
import { Box, Button, FormControl, Stack } from '@mui/material';
|
||||||
import { PasswordInput } from '@nymproject/react/textfields/Password';
|
import { EnhancedPasswordInput } from 'src/components/Login/LoginPasswordFormWrapper';
|
||||||
import { Subtitle } from '../components';
|
import { Subtitle } from '../components';
|
||||||
import { AppContext } from '../../../context/main';
|
import { AppContext } from '../../../context/main';
|
||||||
|
|
||||||
@@ -21,10 +21,11 @@ export const SignInPassword = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<PasswordInput
|
{/* Use the password wrapper input instead */}
|
||||||
|
<EnhancedPasswordInput
|
||||||
label="Enter password"
|
label="Enter password"
|
||||||
password={password}
|
password={password}
|
||||||
onUpdatePassword={(pswd) => setPassword(pswd)}
|
onUpdatePassword={(pswd: any) => setPassword(pswd)}
|
||||||
error={error}
|
error={error}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const NodeTestPage = () => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError('Node test failed, please try again');
|
setError('Node test failed, please try again');
|
||||||
testStateRef.current = 'Stopped';
|
testStateRef.current = 'Stopped';
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(e);
|
console.log(e);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -67,6 +68,7 @@ export const NodeTestPage = () => {
|
|||||||
await client.tester.init(validator, nodeTesterId);
|
await client.tester.init(validator, nodeTesterId);
|
||||||
setNodeTestClient(client);
|
setNodeTestClient(client);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(e);
|
console.log(e);
|
||||||
setError('Failed to load node tester client, please try again');
|
setError('Failed to load node tester client, please try again');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user