Forget confirmation & account name editing (#4)

* Forget confirmation & account name editing

* Update ActionBar padding
This commit is contained in:
Jaco Greeff 2019-05-20 15:56:11 +02:00 committed by GitHub
parent 7f35ea1e8e
commit 93b135d685
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 158 additions and 42 deletions

View File

@ -6,7 +6,8 @@
"license": "Apache-2",
"dependencies": {
"@polkadot/ui-identicon": "^0.39.0-beta.5",
"@types/react-router-dom": "^4.3.2",
"@types/react-router": "^5.0.0",
"@types/react-router-dom": "^4.3.3",
"react-router": "^5.0.0",
"react-router-dom": "^5.0.0"
}

View File

@ -2,31 +2,58 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { ActionBar, Address } from '../../components';
import { forgetAccount } from '../../messaging';
import { editAccount } from '../../messaging';
import { Name } from '../../partials';
type Props = {
className?: string,
address: string,
name?: string,
name?: string | null,
onAction: () => void
};
export default function Account ({ address, name, onAction }: Props) {
const _onForget = (): void => {
forgetAccount(address)
.then(onAction)
.catch(console.error);
export default function Account ({ address, className, name, onAction }: Props) {
const [isEditing, setEditing] = useState(false);
const [editedname, setName] = useState(name);
const _toggleEdit = (): void => {
setEditing(!isEditing);
};
const _saveChanges = (): void => {
if (editedname && editedname !== name) {
editAccount(address, editedname)
.then(onAction)
.catch(console.error);
}
_toggleEdit();
};
return (
<Address
address={address}
name={name}
className={className}
name={
isEditing
? (
<Name
defaultValue={name}
isFocussed
label={null}
onBlur={_saveChanges}
onChange={setName}
/>
)
: name
}
>
<ActionBar>
<a onClick={_onForget}>Forget</a>
<a onClick={_toggleEdit}>Edit</a>
<Link to={`/account/forget/${address}`}>Forget</Link>
</ActionBar>
</Address>
);

View File

@ -0,0 +1,47 @@
// Copyright 2019 @polkadot/extension-ui authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringJson } from '@polkadot/ui-keyring/types';
import React from 'react';
import { RouteComponentProps, withRouter } from 'react-router';
import { Address, Button, Header, Tip } from '../components';
import { forgetAccount } from '../messaging';
import { Back } from '../partials';
type Props = RouteComponentProps<{ address: string }> & {
accounts: Array<KeyringJson>,
onAction: () => void
};
function Forget (props: Props) {
const { accounts, match: { params: { address } }, onAction } = props;
const account = accounts.find((account) => address === account.address);
const _onClick = (): void => {
forgetAccount(address)
.then(onAction)
.catch(console.error);
};
return (
<div>
<Header label='forget account' />
<Back />
<Tip header='danger' type='error'>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.</Tip>
<Address
address={address}
name={account && account.meta.name}
/>
<Button
isFull
label='I want to forget this account'
onClick={_onClick}
/>
</div>
);
}
export default withRouter(Forget);

View File

@ -34,7 +34,7 @@ export default function Request ({ accounts, isFirst, onAction, request: { addre
return (
<Address
address={address}
name={account ? account.meta.name : '<invalid account>'}
name={account && account.meta.name}
>
<Details
method={method}

View File

@ -12,6 +12,7 @@ import { Loading } from '../components';
import { getAccounts, getRequests } from '../messaging';
import Accounts from './Accounts';
import Create from './Create';
import Forget from './Forget';
import Import from './Import';
import Signing from './Signing';
@ -43,6 +44,9 @@ export default function Popup (props: Props) {
<Route path='/account/create' component={() =>
<Create onAction={onAction} />
} />
<Route path='/account/forget/:address' component={() =>
<Forget accounts={accounts} onAction={onAction} />
} />
<Route path='/account/import' component={() =>
<Import onAction={onAction} />
} />

View File

@ -5,8 +5,6 @@
import React from 'react';
import styled from 'styled-components';
import defaults from './defaults';
type Props = {
children: React.ReactNode;
className?: string;
@ -21,7 +19,7 @@ function ActionBar ({ children, className }: Props) {
}
export default styled(ActionBar)`
padding: ${defaults.boxPadding};
padding: 0.25rem;
text-align: right;
a {

View File

@ -14,7 +14,7 @@ type Props = {
children?: React.ReactNode;
className?: string,
isHidden?: boolean,
name?: string | null,
name?: React.ReactNode | null,
theme?: 'polkadot' | 'substrate'
};

View File

@ -10,32 +10,44 @@ import defaults from './defaults';
type Props = {
className?: string,
defaultValue?: string | null,
isError?: boolean,
isFocussed?: boolean,
isReadOnly?: boolean,
label: string,
label?: string | null,
onBlur?: () => void,
onChange?: (value: string) => void,
type?: 'text' | 'password',
value?: string
};
function Input ({ className, label, isFocussed, isReadOnly, onChange, type = 'text', value }: Props) {
function Input ({ className, defaultValue, label, isFocussed, isReadOnly, onBlur, onChange, type = 'text', value }: Props) {
const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>): void => {
onChange && onChange(value.trim());
};
const element = (
<input
autoFocus={isFocussed}
defaultValue={defaultValue || undefined}
readOnly={isReadOnly}
onBlur={onBlur}
onChange={_onChange}
type={type}
value={value}
/>
);
if (!label) {
return <div className={className}>{element}</div>;
}
return (
<Label
className={className}
label={label}
>
<input
autoFocus={isFocussed}
readOnly={isReadOnly}
onChange={_onChange}
type={type}
value={value}
/>
{element}
</Label>
);
}

View File

@ -22,6 +22,10 @@ function sendMessage (message: MessageTypes, request: any = {}): Promise<any> {
});
}
export async function editAccount (address: string, name: string): Promise<boolean> {
return sendMessage('accounts.edit', { address, name });
}
export async function forgetAccount (address: string): Promise<boolean> {
return sendMessage('accounts.forget', { address });
}

View File

@ -16,7 +16,7 @@ type Props = {
function Back ({ className, to = '/' }: Props) {
return (
<div className={className}>
<Link to='/'>{unicode.BACK} Back</Link>
<Link to={to}>{unicode.BACK} Back</Link>
</div>
);
}

View File

@ -7,14 +7,18 @@ import React, { useEffect, useState } from 'react';
import { Input } from '../components';
type Props = {
defaultValue?: string | null,
isFocussed?: boolean,
onChange: (name: string | null) => void
label?: string | null,
onBlur?: () => void,
onChange: (name: string | null) => void,
value?: string
};
const MIN_LENGTH = 3;
export default function Name ({ isFocussed, onChange }: Props) {
const [name, setName] = useState('');
export default function Name ({ defaultValue, isFocussed, label = 'a descriptive name for this account', onBlur, onChange, value }: Props) {
const [name, setName] = useState(defaultValue || '');
useEffect(() => {
onChange(
@ -25,14 +29,15 @@ export default function Name ({ isFocussed, onChange }: Props) {
}, [name]);
return (
<>
<Input
isError={name.length < MIN_LENGTH}
isFocussed={isFocussed}
label='a descriptive name for this account'
onChange={setName}
type='text'
/>
</>
<Input
defaultValue={defaultValue}
isError={name.length < MIN_LENGTH}
isFocussed={isFocussed}
label={label}
onBlur={onBlur}
onChange={setName}
type='text'
value={value}
/>
);
}

View File

@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringJson } from '@polkadot/ui-keyring/types';
import { MessageTypes, MessageAccountCreate, MessageExtrinsicSign, MessageExtrinsicSignApprove, MessageExtrinsicSignCancel, MessageSeedCreate, MessageSeedCreate$Response, MessageSeedValidate, MessageSeedValidate$Response, MessageAccountForget } from '../types';
import { MessageTypes, MessageAccountCreate, MessageAccountEdit, MessageExtrinsicSign, MessageExtrinsicSignApprove, MessageExtrinsicSignCancel, MessageSeedCreate, MessageSeedCreate$Response, MessageSeedValidate, MessageSeedValidate$Response, MessageAccountForget } from '../types';
import keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
@ -29,6 +29,16 @@ export default class Extension {
return true;
}
private accountsEdit ({ address, name }: MessageAccountEdit): boolean {
const pair = keyring.getPair(address);
assert(pair, 'Unable to find pair');
keyring.saveAccountMeta(pair, { ...pair.getMeta(), name });
return true;
}
private accountsForget ({ address }: MessageAccountForget): boolean {
keyring.forgetAccount(address);
@ -113,6 +123,9 @@ export default class Extension {
case 'accounts.forget':
return this.accountsForget(request);
case 'accounts.edit':
return this.accountsEdit(request);
case 'accounts.list':
return this.accountsList();

View File

@ -4,7 +4,7 @@
import { KeypairType } from '@polkadot/util-crypto/types';
export type MessageTypes = 'accounts.create' | 'accounts.forget' | 'accounts.list' | 'extrinsic.sign' | 'seed.create' | 'seed.validate' | 'signing.approve' | 'signing.cancel' | 'signing.requests';
export type MessageTypes = 'accounts.create' | 'accounts.edit' | 'accounts.forget' | 'accounts.list' | 'extrinsic.sign' | 'seed.create' | 'seed.validate' | 'signing.approve' | 'signing.cancel' | 'signing.requests';
export type MessageRequest = {
id: number,
@ -25,6 +25,11 @@ export type MessageAccountCreate = {
type?: KeypairType
};
export type MessageAccountEdit= {
address: string,
name: string
};
export type MessageAccountForget = {
address: string
};

View File

@ -2591,7 +2591,7 @@
dependencies:
"@types/react" "*"
"@types/react-router-dom@^4.3.2":
"@types/react-router-dom@^4.3.3":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.3.tgz#7837e3e9fefbc84a8f6c8a51dca004f4e83e94e3"
integrity sha512-xj0DmFjgvAqRfh/kJPO7apD5G30yPQe+8slu/dugioQOkdKpyzc4Fgk4hoTelm6CSHz7pI2PPsW5+Y6GRBF2zw==
@ -2600,7 +2600,7 @@
"@types/react" "*"
"@types/react-router" "*"
"@types/react-router@*":
"@types/react-router@*", "@types/react-router@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.0.0.tgz#22ae8f55d8af770ea1f755218936f01bfe1bfe27"
integrity sha512-0JjtJMxkQSyWUHTHaD3GhKf6rcZSUFmcQob8OlPTsbnxnIg2Nh3btkss4uke5CKVRtbCMipGU7My5jtfZQC+jw==