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

View File

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

View File

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

View File

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

View File

@ -10,32 +10,44 @@ import defaults from './defaults';
type Props = { type Props = {
className?: string, className?: string,
defaultValue?: string | null,
isError?: boolean, isError?: boolean,
isFocussed?: boolean, isFocussed?: boolean,
isReadOnly?: boolean, isReadOnly?: boolean,
label: string, label?: string | null,
onBlur?: () => void,
onChange?: (value: string) => void, onChange?: (value: string) => void,
type?: 'text' | 'password', type?: 'text' | 'password',
value?: string 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 => { const _onChange = ({ target: { value } }: React.ChangeEvent<HTMLInputElement>): void => {
onChange && onChange(value.trim()); 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 ( return (
<Label <Label
className={className} className={className}
label={label} label={label}
> >
<input {element}
autoFocus={isFocussed}
readOnly={isReadOnly}
onChange={_onChange}
type={type}
value={value}
/>
</Label> </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> { export async function forgetAccount (address: string): Promise<boolean> {
return sendMessage('accounts.forget', { address }); return sendMessage('accounts.forget', { address });
} }

View File

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

View File

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

View File

@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details. // of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringJson } from '@polkadot/ui-keyring/types'; 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 keyring from '@polkadot/ui-keyring';
import accountsObservable from '@polkadot/ui-keyring/observable/accounts'; import accountsObservable from '@polkadot/ui-keyring/observable/accounts';
@ -29,6 +29,16 @@ export default class Extension {
return true; 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 { private accountsForget ({ address }: MessageAccountForget): boolean {
keyring.forgetAccount(address); keyring.forgetAccount(address);
@ -113,6 +123,9 @@ export default class Extension {
case 'accounts.forget': case 'accounts.forget':
return this.accountsForget(request); return this.accountsForget(request);
case 'accounts.edit':
return this.accountsEdit(request);
case 'accounts.list': case 'accounts.list':
return this.accountsList(); return this.accountsList();

View File

@ -4,7 +4,7 @@
import { KeypairType } from '@polkadot/util-crypto/types'; 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 = { export type MessageRequest = {
id: number, id: number,
@ -25,6 +25,11 @@ export type MessageAccountCreate = {
type?: KeypairType type?: KeypairType
}; };
export type MessageAccountEdit= {
address: string,
name: string
};
export type MessageAccountForget = { export type MessageAccountForget = {
address: string address: string
}; };

View File

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