diff --git a/nym-wallet/src-tauri/capabilities/main-capability.json b/nym-wallet/src-tauri/capabilities/main-capability.json index 9ca4de883f..60b91143bb 100644 --- a/nym-wallet/src-tauri/capabilities/main-capability.json +++ b/nym-wallet/src-tauri/capabilities/main-capability.json @@ -32,6 +32,7 @@ "updater:allow-download", "updater:allow-download-and-install", "updater:allow-install", - "core:event:allow-listen" + "core:event:allow-listen", + "shell:allow-open" ] } \ No newline at end of file diff --git a/nym-wallet/src-tauri/gen/schemas/capabilities.json b/nym-wallet/src-tauri/gen/schemas/capabilities.json index 533103f942..a0a9361c59 100644 --- a/nym-wallet/src-tauri/gen/schemas/capabilities.json +++ b/nym-wallet/src-tauri/gen/schemas/capabilities.json @@ -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"]}} \ No newline at end of file +{"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"]}} \ No newline at end of file diff --git a/nym-wallet/src/components/Login/LoginPasswordFormWrapper.tsx b/nym-wallet/src/components/Login/LoginPasswordFormWrapper.tsx new file mode 100644 index 0000000000..208df3f631 --- /dev/null +++ b/nym-wallet/src/components/Login/LoginPasswordFormWrapper.tsx @@ -0,0 +1,117 @@ +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 = ({ onPaste }) => ( + + + + + +); + +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 = ({ + password, + onUpdatePassword, + ...otherProps +}) => { + const inputRef = useRef(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) { + 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) { + console.error('Failed to paste from clipboard:', err); + } + }; + + return ( + + + + + + + ); +}; diff --git a/nym-wallet/src/components/Login/MnemonicLoginFormWrapper.tsx b/nym-wallet/src/components/Login/MnemonicLoginFormWrapper.tsx new file mode 100644 index 0000000000..30dcd105b4 --- /dev/null +++ b/nym-wallet/src/components/Login/MnemonicLoginFormWrapper.tsx @@ -0,0 +1,123 @@ +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 = ({ onPaste }) => ( + + + + + +); + +interface EnhancedMnemonicInputProps { + mnemonic: string; + onUpdateMnemonic: (mnemonic: string) => void; + error?: string; + [key: string]: any; +} + +export { OriginalMnemonicInput as MnemonicInput }; + +export const EnhancedMnemonicInput: React.FC = ({ + mnemonic, + onUpdateMnemonic, + ...otherProps +}) => { + const inputRef = useRef(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) { + 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) { + console.error('Failed to paste from clipboard:', err); + } + }; + + return ( + + + + + + + ); +}; diff --git a/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx b/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx index 55f34774b5..a5babd3a96 100644 --- a/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx +++ b/nym-wallet/src/pages/auth/pages/signin-mnemonic.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Box, Button, FormControl, Stack } from '@mui/material'; import { AppContext } from 'src/context'; import { isPasswordCreated } from 'src/requests'; -import { MnemonicInput } from '@nymproject/react/textfields/Mnemonic'; +import { EnhancedMnemonicInput } from 'src/components/Login/MnemonicLoginFormWrapper'; import { Subtitle } from '../components'; export const SignInMnemonic = () => { @@ -38,7 +38,11 @@ export const SignInMnemonic = () => { }} > - setMnemonic(mnc)} error={error} /> + setMnemonic(mnc)} + error={error} + /> diff --git a/nym-wallet/src/pages/auth/pages/signin-password.tsx b/nym-wallet/src/pages/auth/pages/signin-password.tsx index 0b62040ac6..45e9c4602b 100644 --- a/nym-wallet/src/pages/auth/pages/signin-password.tsx +++ b/nym-wallet/src/pages/auth/pages/signin-password.tsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from 'react'; import { useNavigate } from 'react-router-dom'; 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 { AppContext } from '../../../context/main'; @@ -21,10 +21,11 @@ export const SignInPassword = () => { }} > - setPassword(pswd)} + onUpdatePassword={(pswd: any) => setPassword(pswd)} error={error} autoFocus />