Forget confirmation & account name editing (#4)
* Forget confirmation & account name editing * Update ActionBar padding
This commit is contained in:
parent
7f35ea1e8e
commit
93b135d685
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
47
packages/extension-ui/src/Popup/Forget.tsx
Normal file
47
packages/extension-ui/src/Popup/Forget.tsx
Normal 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);
|
||||||
@ -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}
|
||||||
|
|||||||
@ -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} />
|
||||||
} />
|
} />
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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==
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user