unify warning and error and add capslock warning (#545)
* unify warning and error and add capslock warning * Update packages/extension-ui/src/Popup/Signing/Unlock.tsx Co-authored-by: Jaco Greeff <jacogr@gmail.com> * Update packages/extension-ui/src/Popup/Derive/SelectParent.tsx Co-authored-by: Jaco Greeff <jacogr@gmail.com> * check capslock using onKeyPress * lint * fix tests * add test for caps-lock Co-authored-by: Github Actions <action@github.com> Co-authored-by: Jaco Greeff <jacogr@gmail.com>
This commit is contained in:
parent
f60ab0fd47
commit
1cb36bb73c
@ -60,7 +60,9 @@ function Request ({ authId, className, isFirst, request: { origin }, url }: Prop
|
||||
</div>
|
||||
{isFirst && (
|
||||
<>
|
||||
<Warning className='warningMargin'>{t<string>('Only approve this request if you trust the application. Approving gives the application access to the addresses of your accounts.')}</Warning>
|
||||
<Warning className='warningMargin'>
|
||||
{t<string>('Only approve this request if you trust the application. Approving gives the application access to the addresses of your accounts.')}
|
||||
</Warning>
|
||||
<Button
|
||||
className='acceptButton'
|
||||
onClick={_onApprove}
|
||||
|
||||
@ -42,6 +42,12 @@ describe('Create Account', () => {
|
||||
wrapper.update();
|
||||
};
|
||||
|
||||
const capsLockOn = async (input: ReactWrapper): Promise<void> => {
|
||||
input.simulate('keyPress', { getModifierState: () => true });
|
||||
await act(flushAllPromises);
|
||||
wrapper.update();
|
||||
};
|
||||
|
||||
const enterName = (name: string): Promise<void> => type(wrapper.find('input').first(), name);
|
||||
const password = (password: string) => (): Promise<void> => type(wrapper.find('input[type="password"]').first(), password);
|
||||
const repeat = (password: string) => (): Promise<void> => type(wrapper.find('input[type="password"]').last(), password);
|
||||
@ -102,7 +108,7 @@ describe('Create Account', () => {
|
||||
it('after typing less than 3 characters into name input, password input is not visible', async () => {
|
||||
await enterName('ab');
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-name]').find(Input).prop('withError')).toBe(true);
|
||||
expect(wrapper.find('span.error').first().text()).toBe('Account name is too short');
|
||||
expect(wrapper.find('.warning-message').first().text()).toBe('Account name is too short');
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input)).toHaveLength(1);
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
|
||||
expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
|
||||
@ -122,10 +128,17 @@ describe('Create Account', () => {
|
||||
expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('password with caps lock should show a warning', async () => {
|
||||
await enterName('abc').then(password('abcde'));
|
||||
await capsLockOn(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input));
|
||||
|
||||
expect(wrapper.find('.warning-message').first().text()).toBe('Warning: Caps lock is on');
|
||||
});
|
||||
|
||||
it('password shorter than 6 characters should be not valid', async () => {
|
||||
await enterName('abc').then(password('abcde'));
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input).prop('withError')).toBe(true);
|
||||
expect(wrapper.find('span.error').text()).toBe('Password is too short');
|
||||
expect(wrapper.find('.warning-message').text()).toBe('Password is too short');
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-password]').find(Input)).toHaveLength(1);
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]')).toHaveLength(0);
|
||||
expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
|
||||
@ -133,14 +146,14 @@ describe('Create Account', () => {
|
||||
|
||||
it('submit button is not visible until both passwords are equal', async () => {
|
||||
await enterName('abc').then(password('abcdef')).then(repeat('abcdeg'));
|
||||
expect(wrapper.find('span.error').text()).toBe('Passwords do not match');
|
||||
expect(wrapper.find('.warning-message').text()).toBe('Passwords do not match');
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]').find(Input).prop('withError')).toBe(true);
|
||||
expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('submit button is visible when both passwords are equal', async () => {
|
||||
await enterName('abc').then(password('abcdef')).then(repeat('abcdef'));
|
||||
expect(wrapper.find('span.error')).toHaveLength(0);
|
||||
expect(wrapper.find('.warning-message')).toHaveLength(0);
|
||||
expect(wrapper.find(InputWithLabel).find('[data-input-repeat-password]').find(Input).prop('withError')).toBe(false);
|
||||
expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(false);
|
||||
});
|
||||
@ -171,7 +184,7 @@ describe('Create Account', () => {
|
||||
|
||||
it('first password changes - button is not disabled', async () => {
|
||||
await type(wrapper.find('input[type="password"]').first(), 'aaaaaa');
|
||||
expect(wrapper.find('span.error').text()).toBe('Passwords do not match');
|
||||
expect(wrapper.find('.warning-message').text()).toBe('Passwords do not match');
|
||||
expect(wrapper.find('[data-button-action="add new root"] button').prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@ -39,7 +39,9 @@ function Mnemonic ({ onNextStep, seed }: Props): React.ReactElement<Props> {
|
||||
onCopy={_onCopy}
|
||||
seed={seed}
|
||||
/>
|
||||
<Warning>{t<string>("Please write down your wallet's mnemonic seed and keep it in a safe place. The mnemonic can be used to restore your wallet. Keep it carefully to not lose your assets.")}</Warning>
|
||||
<Warning>
|
||||
{t<string>("Please write down your wallet's mnemonic seed and keep it in a safe place. The mnemonic can be used to restore your wallet. Keep it carefully to not lose your assets.")}
|
||||
</Warning>
|
||||
<VerticalSpace />
|
||||
<Checkbox
|
||||
checked={isMnemonicSaved}
|
||||
|
||||
@ -115,7 +115,7 @@ describe('Derive', () => {
|
||||
});
|
||||
|
||||
it('No error is visible when first loading the page', () => {
|
||||
expect(wrapper.find('.error')).toHaveLength(0);
|
||||
expect(wrapper.find('Warning')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('"Create derived account" is disabled when password is not set', () => {
|
||||
@ -133,7 +133,8 @@ describe('Derive', () => {
|
||||
const button = wrapper.find('[data-button-action="create derived account"] button');
|
||||
|
||||
expect(button.prop('disabled')).toBe(true);
|
||||
expect(wrapper.find('.error')).toHaveLength(1);
|
||||
expect(wrapper.find('.warning-message')).toHaveLength(1);
|
||||
expect(wrapper.find('.warning-message').first().text()).toEqual('Wrong password');
|
||||
});
|
||||
|
||||
it('The error disappears when typing a new password and "Create derived account" is enabled', async () => {
|
||||
@ -147,7 +148,7 @@ describe('Derive', () => {
|
||||
const button = wrapper.find('[data-button-action="create derived account"] button');
|
||||
|
||||
expect(button.prop('disabled')).toBe(false);
|
||||
expect(wrapper.find('.error')).toHaveLength(0);
|
||||
expect(wrapper.find('.warning-message')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('"Create derived account" is enabled when password is set', async () => {
|
||||
@ -156,7 +157,7 @@ describe('Derive', () => {
|
||||
const button = wrapper.find('[data-button-action="create derived account"] button');
|
||||
|
||||
expect(button.prop('disabled')).toBe(false);
|
||||
expect(wrapper.find('.error')).toHaveLength(0);
|
||||
expect(wrapper.find('.warning-message')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('An error is visible and "Create derived account" is disabled when suri is incorrect', async () => {
|
||||
@ -169,7 +170,8 @@ describe('Derive', () => {
|
||||
const button = wrapper.find('[data-button-action="create derived account"] button');
|
||||
|
||||
expect(button.prop('disabled')).toBe(true);
|
||||
expect(wrapper.find('.error')).toHaveLength(1);
|
||||
expect(wrapper.find('.warning-message')).toHaveLength(1);
|
||||
expect(wrapper.find('.warning-message').first().text()).toEqual('Incorrect derivation path');
|
||||
});
|
||||
|
||||
it('The error disappears and "Create derived account" is enabled when typing a new suri', async () => {
|
||||
@ -183,7 +185,7 @@ describe('Derive', () => {
|
||||
const button = wrapper.find('[data-button-action="create derived account"] button');
|
||||
|
||||
expect(button.prop('disabled')).toBe(false);
|
||||
expect(wrapper.find('.error')).toHaveLength(0);
|
||||
expect(wrapper.find('Warning')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('takes selected address from URL as parent account', () => {
|
||||
|
||||
@ -7,7 +7,7 @@ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState }
|
||||
import styled from 'styled-components';
|
||||
import { assert } from '@polkadot/util';
|
||||
|
||||
import { AccountContext, ActionContext, Address, ButtonArea, Checkbox, InputWithLabel, Label, NextStepButton, VerticalSpace } from '../../components';
|
||||
import { AccountContext, ActionContext, Address, ButtonArea, Checkbox, InputWithLabel, Label, NextStepButton, VerticalSpace, Warning } from '../../components';
|
||||
import useTranslation from '../../hooks/useTranslation';
|
||||
import { validateAccount, validateDerivationPath } from '../../messaging';
|
||||
import { nextDerivationPath } from '../../util/nextDerivationPath';
|
||||
@ -132,7 +132,14 @@ function SelectParent ({ className, isLocked, onDerivationConfirmed, parentAddre
|
||||
type='password'
|
||||
value={parentPassword}
|
||||
/>
|
||||
{!!parentPassword && !isProperParentPassword && <div className='error'>{t('Wrong password')}</div>}
|
||||
{!!parentPassword && !isProperParentPassword && (
|
||||
<Warning
|
||||
isBelowInput
|
||||
isDanger
|
||||
>
|
||||
{t('Wrong password')}
|
||||
</Warning>
|
||||
)}
|
||||
</div>
|
||||
{isProperParentPassword && (
|
||||
<>
|
||||
@ -143,7 +150,14 @@ function SelectParent ({ className, isLocked, onDerivationConfirmed, parentAddre
|
||||
parentAddress={parentAddress}
|
||||
parentPassword={parentPassword}
|
||||
/>
|
||||
{!suriPath && <div className='error'>{t('Incorrect derivation path')}</div>}
|
||||
{!suriPath && (
|
||||
<Warning
|
||||
isBelowInput
|
||||
isDanger
|
||||
>
|
||||
{t('Incorrect derivation path')}
|
||||
</Warning>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@ -175,8 +189,7 @@ function SelectParent ({ className, isLocked, onDerivationConfirmed, parentAddre
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(SelectParent)(({ theme }: ThemeProps) => `
|
||||
|
||||
export default styled(SelectParent)`
|
||||
.smallerMargin {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
@ -190,12 +203,4 @@ export default styled(SelectParent)(({ theme }: ThemeProps) => `
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
display: block;
|
||||
margin-top: -10px;
|
||||
font-size: ${theme.labelFontSize};
|
||||
line-height: ${theme.labelLineHeight};
|
||||
color: ${theme.errorColor};
|
||||
}
|
||||
`);
|
||||
`;
|
||||
|
||||
@ -64,10 +64,7 @@ function Export ({ className, match: { params: { address } } }: Props): React.Re
|
||||
/>
|
||||
<div className={className}>
|
||||
<Address address={address}>
|
||||
<Warning
|
||||
className='movedWarning'
|
||||
isDanger
|
||||
>
|
||||
<Warning className='movedWarning'>
|
||||
{t<string>("You are exporting your account. Keep it safe and don't share it with anyone.")}
|
||||
</Warning>
|
||||
<div className='actionArea'>
|
||||
|
||||
@ -40,10 +40,7 @@ function Forget ({ className, match: { params: { address } } }: Props): React.Re
|
||||
/>
|
||||
<div className={className}>
|
||||
<Address address={address}>
|
||||
<Warning
|
||||
className='movedWarning'
|
||||
isDanger
|
||||
>
|
||||
<Warning className='movedWarning'>
|
||||
{t<string>('You are about to remove the account. This means that you will not be able to access it via this extension anymore. If you wish to recover it, you would need to use the seed.')}
|
||||
</Warning>
|
||||
<div className='actionArea'>
|
||||
|
||||
@ -71,7 +71,9 @@ function Request ({ className, metaId, request, url }: Props): React.ReactElemen
|
||||
</tr>
|
||||
</Table>
|
||||
<div className='requestInfo'>
|
||||
<Warning className='requestWarning'>{t<string>('This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata.')}</Warning>
|
||||
<Warning className='requestWarning'>
|
||||
{t<string>('This approval will add the metadata to your extension instance, allowing future requests to be decoded using this metadata.')}
|
||||
</Warning>
|
||||
<Button
|
||||
className='btnAccept'
|
||||
onClick={_onApprove}
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { ThemeProps } from '@polkadot/extension-ui/types';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { InputWithLabel } from '../../components';
|
||||
import { InputWithLabel, Warning } from '../../components';
|
||||
import useTranslation from '../../hooks/useTranslation';
|
||||
|
||||
interface Props extends ThemeProps {
|
||||
interface Props {
|
||||
className?: string;
|
||||
error?: string | null;
|
||||
isBusy: boolean;
|
||||
@ -43,15 +40,16 @@ function Unlock ({ className, error, isBusy, onSign, password, setError, setPass
|
||||
value={password}
|
||||
withoutMargin={true}
|
||||
/>
|
||||
{error && <div className='error'>{error}</div>}
|
||||
{error && (
|
||||
<Warning
|
||||
isBelowInput
|
||||
isDanger
|
||||
>
|
||||
{error}
|
||||
</Warning>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Unlock)(({ theme }: ThemeProps) => `
|
||||
.error {
|
||||
font-size: ${theme.labelFontSize};
|
||||
line-height: ${theme.labelLineHeight};
|
||||
color: ${theme.errorColor};
|
||||
}
|
||||
`));
|
||||
export default React.memo(Unlock);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Label from './Label';
|
||||
import { Input } from './TextInputs';
|
||||
import Warning from './Warning';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -25,11 +26,21 @@ interface Props {
|
||||
}
|
||||
|
||||
function InputWithLabel ({ className, defaultValue, disabled, isError, isFocused, isReadOnly, label = '', onBlur, onChange, onEnter, placeholder, type = 'text', value, withoutMargin }: Props): React.ReactElement<Props> {
|
||||
const _checkEnter = useCallback(
|
||||
const [isCapsLock, setIsCapsLock] = useState(false);
|
||||
|
||||
const _checkKey = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLInputElement>): void => {
|
||||
onEnter && event.key === 'Enter' && onEnter();
|
||||
|
||||
if (type === 'password') {
|
||||
if (event.getModifierState('CapsLock')) {
|
||||
setIsCapsLock(true);
|
||||
} else {
|
||||
setIsCapsLock(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[onEnter]
|
||||
[onEnter, type]
|
||||
);
|
||||
|
||||
const _onChange = useCallback(
|
||||
@ -52,7 +63,7 @@ function InputWithLabel ({ className, defaultValue, disabled, isError, isFocused
|
||||
disabled={disabled}
|
||||
onBlur={onBlur}
|
||||
onChange={_onChange}
|
||||
onKeyPress={_checkEnter}
|
||||
onKeyPress={_checkKey}
|
||||
placeholder={placeholder}
|
||||
readOnly={isReadOnly}
|
||||
spellCheck={false}
|
||||
@ -60,6 +71,9 @@ function InputWithLabel ({ className, defaultValue, disabled, isError, isFocused
|
||||
value={value}
|
||||
withError={isError}
|
||||
/>
|
||||
{ isCapsLock && (
|
||||
<Warning isBelowInput >Warning: Caps lock is on</Warning>
|
||||
)}
|
||||
</Label>
|
||||
);
|
||||
}
|
||||
@ -69,5 +83,9 @@ export default styled(InputWithLabel)`
|
||||
|
||||
&.withoutMargin {
|
||||
margin-bottom: 0px;
|
||||
|
||||
+ .error {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -11,9 +11,9 @@ interface Props extends ThemeProps {
|
||||
|
||||
const TextInput = css(({ theme, withError }: Props) => `
|
||||
background: ${theme.inputBackground};
|
||||
border-color: ${withError ? theme.errorBorderColor : theme.inputBorderColor};
|
||||
border-radius: ${theme.borderRadius};
|
||||
border: 1px solid ${theme.inputBorderColor};
|
||||
border-color: ${withError ? theme.errorBorderColor : theme.inputBorderColor};
|
||||
box-sizing: border-box;
|
||||
color: ${withError ? theme.errorColor : theme.textColor};
|
||||
display: block;
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { ThemeProps } from '../types';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import useIsMounted from '../hooks/useIsMounted';
|
||||
import { Result, Validator } from '../util/validators';
|
||||
import Warning from './Warning';
|
||||
|
||||
interface BasicProps {
|
||||
isError?: boolean;
|
||||
@ -52,17 +50,16 @@ function ValidatedInput<T extends Record<string, unknown>> ({ className, compone
|
||||
onChange={setValue}
|
||||
value={value}
|
||||
/>
|
||||
{Result.isError(validationResult) && <span className='error'>{validationResult.error.errorDescription}</span>}
|
||||
{Result.isError(validationResult) && (
|
||||
<Warning
|
||||
isBelowInput
|
||||
isDanger
|
||||
>
|
||||
{validationResult.error.errorDescription}
|
||||
</Warning>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(ValidatedInput)(({ theme }: ThemeProps) => `
|
||||
.error {
|
||||
display: block;
|
||||
margin-top: -10px;
|
||||
font-size: ${theme.labelFontSize};
|
||||
line-height: ${theme.labelLineHeight};
|
||||
color: ${theme.errorColor};
|
||||
}
|
||||
`);
|
||||
export default ValidatedInput;
|
||||
|
||||
@ -12,30 +12,48 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
interface Props extends ThemeProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
isBelowInput?: boolean;
|
||||
isDanger?: boolean;
|
||||
}
|
||||
|
||||
function Warning ({ children, className }: Props): React.ReactElement<Props> {
|
||||
function Warning ({ children, className = '', isBelowInput, isDanger }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div>
|
||||
<FontAwesomeIcon
|
||||
className='warningImage'
|
||||
icon={faExclamationTriangle}
|
||||
/>
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
<div className={`${className} ${isDanger ? 'danger' : ''} ${isBelowInput ? 'belowInput' : ''}`}>
|
||||
<FontAwesomeIcon
|
||||
className='warningImage'
|
||||
icon={faExclamationTriangle}
|
||||
/>
|
||||
<div className='warning-message'>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Warning)(({ isDanger, theme }: Props) => `
|
||||
export default React.memo(styled(Warning)<Props>(({ isDanger, theme }: Props) => `
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: ${isDanger ? '18px' : ''};
|
||||
padding-left: 18px;
|
||||
color: ${theme.subTextColor};
|
||||
margin-right: 20px;
|
||||
border-left: ${isDanger ? `0.25rem solid ${theme.buttonBackgroundDanger}` : ''};
|
||||
margin-top: 6px;
|
||||
border-left: ${`0.25rem solid ${theme.iconWarningColor}`};
|
||||
|
||||
&.belowInput {
|
||||
font-size: ${theme.labelFontSize};
|
||||
line-height: ${theme.labelLineHeight};
|
||||
|
||||
&.danger {
|
||||
margin-top: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.danger {
|
||||
border-left-color: ${theme.buttonBackgroundDanger};
|
||||
}
|
||||
|
||||
.warning-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.warningImage {
|
||||
margin: 5px 10px 5px 0;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user