Extract i18n from shared UI components (#4834)

* Actionbar i18n

* ui Errors i18n

* Features i18n

* GapPriceSelector i18n

* WIP

* WIP #2

* Update methodDecoding

* ModalBox -> functional

* Signer pages i18n (missed previously)

* Update ModalBox tests

* Update variable
This commit is contained in:
Jaco Greeff 2017-03-10 12:04:40 +01:00 committed by GitHub
parent d98b7aab61
commit 4e5fd92e67
13 changed files with 452 additions and 199 deletions

View File

@ -15,11 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import FileDownloadIcon from 'material-ui/svg-icons/file/file-download';
import Button from '../../Button'; import Button from '../../Button';
import { FileDownloadIcon } from '../../Icons';
class ActionbarExport extends Component { class ActionbarExport extends Component {
static propTypes = { static propTypes = {
@ -38,7 +39,12 @@ class ActionbarExport extends Component {
<Button <Button
className={ className } className={ className }
icon={ <FileDownloadIcon /> } icon={ <FileDownloadIcon /> }
label='export' label={
<FormattedMessage
id='ui.actionbar.export.button.export'
defaultMessage='export'
/>
}
onClick={ this.handleExport } onClick={ this.handleExport }
/> />
); );
@ -46,9 +52,7 @@ class ActionbarExport extends Component {
handleExport = () => { handleExport = () => {
const { filename, content } = this.props; const { filename, content } = this.props;
const text = JSON.stringify(content, null, 4); const text = JSON.stringify(content, null, 4);
const blob = new Blob([ text ], { type: 'application/json' }); const blob = new Blob([ text ], { type: 'application/json' });
FileSaver.saveAs(blob, `${filename}.json`); FileSaver.saveAs(blob, `${filename}.json`);

View File

@ -15,10 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ActionSearch from 'material-ui/svg-icons/action/search'; import { FormattedMessage } from 'react-intl';
import Button from '../../Button'; import Button from '../../Button';
import InputChip from '../../Form/InputChip'; import InputChip from '../../Form/InputChip';
import { SearchIcon } from '../../Icons';
import styles from './search.css'; import styles from './search.css';
@ -74,7 +75,12 @@ export default class ActionbarSearch extends Component {
<InputChip <InputChip
addOnBlur addOnBlur
className={ styles.input } className={ styles.input }
hint='Enter search input...' hint={
<FormattedMessage
id='ui.actionbar.search.hint'
defaultMessage='Enter search input...'
/>
}
ref='inputChip' ref='inputChip'
tokens={ tokens } tokens={ tokens }
@ -86,7 +92,7 @@ export default class ActionbarSearch extends Component {
<Button <Button
className={ styles.searchButton } className={ styles.searchButton }
icon={ <ActionSearch /> } icon={ <SearchIcon /> }
label='' label=''
onClick={ this.handleSearchClick } onClick={ this.handleSearchClick }
/> />

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { Snackbar } from 'material-ui'; import { Snackbar } from 'material-ui';
@ -24,6 +25,19 @@ import { closeErrors } from './actions';
import styles from './errors.css'; import styles from './errors.css';
const ERROR_REGEX = /-(\d+): (.+)$/; const ERROR_REGEX = /-(\d+): (.+)$/;
const DURATION_OPEN = 60000;
const STYLE_BODY = {
height: 'auto',
whiteSpace: 'pre-line'
};
const STYLE_CONTENT = {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
lineHeight: '1.5em',
padding: '0.75em 0'
};
class Errors extends Component { class Errors extends Component {
static propTypes = { static propTypes = {
@ -46,23 +60,18 @@ class Errors extends Component {
<Snackbar <Snackbar
className={ styles.container } className={ styles.container }
open open
action='close' action={
autoHideDuration={ 60000 } <FormattedMessage
id='ui.errors.close'
defaultMessage='close'
/>
}
autoHideDuration={ DURATION_OPEN }
message={ text } message={ text }
onActionTouchTap={ onCloseErrors } onActionTouchTap={ onCloseErrors }
onRequestClose={ this.onRequestClose } onRequestClose={ this.onRequestClose }
bodyStyle={ { bodyStyle={ STYLE_BODY }
whiteSpace: 'pre-line', contentStyle={ STYLE_CONTENT }
height: 'auto'
} }
contentStyle={ {
display: 'flex',
flexDirection: 'row',
lineHeight: '1.5em',
padding: '0.75em 0',
alignItems: 'center',
justifyContent: 'space-between'
} }
/> />
); );
} }

View File

@ -14,6 +14,9 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import { FormattedMessage } from 'react-intl';
const MODES = { const MODES = {
DEVELOPMENT: 1000, // only in dev mode, disabled by default, can be toggled DEVELOPMENT: 1000, // only in dev mode, disabled by default, can be toggled
TESTING: 1011, // feature is available in dev mode TESTING: 1011, // feature is available in dev mode
@ -28,13 +31,33 @@ const FEATURES = {
const DEFAULTS = { const DEFAULTS = {
[FEATURES.LANGUAGE]: { [FEATURES.LANGUAGE]: {
mode: MODES.TESTING, mode: MODES.TESTING,
name: 'Language Selection', name: (
description: 'Allows changing the default interface language' <FormattedMessage
id='ui.features.defaults.i18n.name'
defaultMssage='Language Selection'
/>
),
description: (
<FormattedMessage
id='ui.features.defaults.i18n.desc'
defaultMssage='Allows changing the default interface language'
/>
)
}, },
[FEATURES.LOGLEVELS]: { [FEATURES.LOGLEVELS]: {
mode: MODES.TESTING, mode: MODES.TESTING,
name: 'Logging Level Selection', name: (
description: 'Allows changing of the log levels for various components' <FormattedMessage
id='ui.features.defaults.logging.name'
defaultMssage='Logging Level Selection'
/>
),
description: (
<FormattedMessage
id='ui.features.defaults.logging.desc'
defaultMssage='Allows changing of the log levels for various components'
/>
)
} }
}; };

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
export default class CustomTooltip extends Component { export default class CustomTooltip extends Component {
static propTypes = { static propTypes = {
@ -41,11 +42,15 @@ export default class CustomTooltip extends Component {
return ( return (
<div> <div>
<p className='label'> <p className='label'>
{ count.toNumber() } transactions <FormattedMessage
with gas price set from id='ui.gasPriceSelector.customTooltip.transactions'
<span> { minprice.toFormat(0) } </span> defaultMessage='{number} {number, plural, one {transaction} other {transactions}} with gas price set from {minPrice} to {maxPrice}'
to values={ {
<span> { maxprice.toFormat(0) } </span> number: count.toNumber(),
minPrice: <span>{ minprice.toFormat(0) }</span>,
maxPrice: <span>{ maxprice.toFormat(0) }</span>
} }
/>
</p> </p>
</div> </div>
); );

View File

@ -34,6 +34,7 @@ export DoneIcon from 'material-ui/svg-icons/action/done-all';
export DialIcon from 'material-ui/svg-icons/communication/dialpad'; export DialIcon from 'material-ui/svg-icons/communication/dialpad';
export EditIcon from 'material-ui/svg-icons/content/create'; export EditIcon from 'material-ui/svg-icons/content/create';
export ErrorIcon from 'material-ui/svg-icons/alert/error'; export ErrorIcon from 'material-ui/svg-icons/alert/error';
export FileDownloadIcon from 'material-ui/svg-icons/file/file-download';
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload'; export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
export FileIcon from 'material-ui/svg-icons/editor/insert-drive-file'; export FileIcon from 'material-ui/svg-icons/editor/insert-drive-file';
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
@ -55,6 +56,7 @@ export RefreshIcon from 'material-ui/svg-icons/action/autorenew';
export ReorderIcon from 'material-ui/svg-icons/action/reorder'; export ReorderIcon from 'material-ui/svg-icons/action/reorder';
export ReplayIcon from 'material-ui/svg-icons/av/replay'; export ReplayIcon from 'material-ui/svg-icons/av/replay';
export SaveIcon from 'material-ui/svg-icons/content/save'; export SaveIcon from 'material-ui/svg-icons/content/save';
export SearchIcon from 'material-ui/svg-icons/action/search';
export SendIcon from 'material-ui/svg-icons/content/send'; export SendIcon from 'material-ui/svg-icons/content/send';
export SettingsIcon from 'material-ui/svg-icons/action/settings'; export SettingsIcon from 'material-ui/svg-icons/action/settings';
export SnoozeIcon from 'material-ui/svg-icons/av/snooze'; export SnoozeIcon from 'material-ui/svg-icons/av/snooze';

View File

@ -17,6 +17,7 @@
import { CircularProgress } from 'material-ui'; import { CircularProgress } from 'material-ui';
import moment from 'moment'; import moment from 'moment';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { TypedInput, InputAddress } from '../Form'; import { TypedInput, InputAddress } from '../Form';
@ -112,28 +113,50 @@ class MethodDecoding extends Component {
return null; return null;
} }
const gasValue = gas.mul(gasPrice); const gasProvided = (
<span className={ styles.highlight }>
<FormattedMessage
id='ui.methodDecoding.gasValues'
defaultMessage='{gas} gas ({gasPrice}M/{tag})'
values={ {
gas: gas.toFormat(0),
gasPrice: gasPrice.div(1000000).toFormat(0),
tag: <small>ETH</small>
} }
/>
</span>
);
const gasProvidedEth = (
<span className={ styles.highlight }>
{ this.renderEtherValue(gas.mul(gasPrice)) }
</span>
);
const gasUsed = transaction.gasUsed
? (
<span className={ styles.highlight }>
<FormattedMessage
id='ui.methodDecoding.gasUsed'
defaultMessage=' ({gas} gas used)'
values={ {
gas: transaction.gasUsed.toFormat(0)
} }
/>
</span>
)
: '';
return ( return (
<div className={ styles.gasDetails }> <div className={ styles.gasDetails }>
<span>{ historic ? 'Provided' : 'Provides' } </span> <FormattedMessage
<span className={ styles.highlight }> id='ui.methodDecoding.txValues'
{ gas.toFormat(0) } gas ({ gasPrice.div(1000000).toFormat(0) }M/<small>ETH</small>) defaultMessage='{historic, select, true {Provided} false {Provides}} {gasProvided}{gasUsed} for a total transaction value of {gasProvidedEth}'
</span> values={ {
{ historic,
transaction.gasUsed gasProvided,
? ( gasProvidedEth,
<span> gasUsed
<span>used</span> } }
<span className={ styles.highlight }> />
{ transaction.gasUsed.toFormat(0) } gas
</span>
</span>
)
: null
}
<span> for a total transaction value of </span>
<span className={ styles.highlight }>{ this.renderEtherValue(gasValue) }</span>
{ this.renderMinBlock() } { this.renderMinBlock() }
</div> </div>
); );
@ -148,14 +171,40 @@ class MethodDecoding extends Component {
} }
if (condition.block && condition.block.gt(0)) { if (condition.block && condition.block.gt(0)) {
const blockNumber = (
<span className={ styles.highlight }>
#{ condition.block.toFormat(0) }
</span>
);
return ( return (
<span>, { historic ? 'Submitted' : 'Submission' } at block <span className={ styles.highlight }>#{ condition.block.toFormat(0) }</span></span> <FormattedMessage
id='ui.methodDecoding.condition.block'
defaultMessage=', {historic, select, true {Submitted} false {Submission}} at block {blockNumber}'
values={ {
historic,
blockNumber
} }
/>
); );
} }
if (condition.time) { if (condition.time) {
const timestamp = (
<span className={ styles.highlight }>
{ moment(condition.time).format('LLLL') }
</span>
);
return ( return (
<span>, { historic ? 'Submitted' : 'Submission' } at <span className={ styles.highlight }>{ moment(condition.time).format('LLLL') }</span></span> <FormattedMessage
id='ui.methodDecoding.condition.time'
defaultMessage=', {historic, select, true {Submitted} false {Submission}} at {timestamp}'
values={ {
historic,
timestamp
} }
/>
); );
} }
@ -190,9 +239,12 @@ class MethodDecoding extends Component {
getAscii () { getAscii () {
const { api } = this.context; const { api } = this.context;
const { transaction } = this.props; const { transaction } = this.props;
const ascii = api.util.hexToAscii(transaction.input || transaction.data); const value = api.util.hexToAscii(transaction.input || transaction.data);
return { value: ascii, valid: ASCII_INPUT.test(ascii) }; return {
value,
valid: ASCII_INPUT.test(value)
};
} }
renderInputValue () { renderInputValue () {
@ -218,24 +270,49 @@ class MethodDecoding extends Component {
? text ? text
: text.slice(0, 50) + '...'; : text.slice(0, 50) + '...';
const inputDesc = (
<span
onClick={ this.toggleInputType }
className={ [ styles.clickable, styles.noSelect ].join(' ') }
>
{
type === 'ascii'
? (
<FormattedMessage
id='ui.methodDecoding.input.input'
defaultMessage='input'
/>
)
: (
<FormattedMessage
id='ui.methodDecoding.input.data'
defaultMessage='data'
/>
)
}
</span>
);
const inputValue = (
<span
onClick={ this.toggleInputExpand }
className={ expandable ? styles.clickable : '' }
>
<code className={ styles.inputData }>
{ textToShow }
</code>
</span>
);
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<span>with the </span> <FormattedMessage
<span id='ui.methodDecoding.input.withInput'
onClick={ this.toggleInputType } defaultMessage='with the {inputDesc} {inputValue}'
className={ [ styles.clickable, styles.noSelect ].join(' ') } values={ {
> inputDesc,
{ type === 'ascii' ? 'input' : 'data' } inputValue
</span> } }
<span> &nbsp; </span> />
<span
onClick={ this.toggleInputExpand }
className={ expandable ? styles.clickable : '' }
>
<code className={ styles.inputData }>
{ textToShow }
</code>
</span>
</div> </div>
); );
} }
@ -251,15 +328,19 @@ class MethodDecoding extends Component {
default: default:
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<div> <FormattedMessage
<span>{ historic ? 'Transferred' : 'Will transfer' } </span> id='ui.methodDecoding.token.transfer'
<span className={ styles.highlight }> defaultMessage='{historic, select, true {Transferred} false {Will transfer}} {value} to {address}'
{ this.renderTokenValue(value.value) } values={ {
</span> historic,
<span> to </span> value: (
</div> <span className={ styles.highlight }>
{ this.renderTokenValue(value.value) }
{ this.renderAddressName(address) } </span>
),
address: this.renderAddressName(address)
} }
/>
</div> </div>
); );
} }
@ -272,7 +353,10 @@ class MethodDecoding extends Component {
if (!historic) { if (!historic) {
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
Will deploy a contract. <FormattedMessage
id='ui.methodDecoding.deploy.willDeploy'
defaultMessage='Will deploy a contract'
/>
</div> </div>
); );
} }
@ -280,15 +364,24 @@ class MethodDecoding extends Component {
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<div> <div>
<span>Deployed a contract at address </span> <FormattedMessage
id='ui.methodDecoding.deploy.address'
defaultMessage='Deployed a contract at address '
/>
</div> </div>
{ this.renderAddressName(transaction.creates, false) } { this.renderAddressName(transaction.creates, false) }
<div> <div>
{ methodInputs && methodInputs.length ? 'with the following parameters:' : ''} {
methodInputs && methodInputs.length
? (
<FormattedMessage
id='ui.methodDecoding.deploy.params'
defaultMessage='with the following parameters:'
/>
)
: ''
}
</div> </div>
<div className={ styles.inputs }> <div className={ styles.inputs }>
{ this.renderInputs() } { this.renderInputs() }
</div> </div>
@ -300,17 +393,32 @@ class MethodDecoding extends Component {
const { historic, transaction } = this.props; const { historic, transaction } = this.props;
const { isContract } = this.state; const { isContract } = this.state;
const valueEth = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
const aContract = isContract
? (
<FormattedMessage
id='ui.methodDecoding.receive.contract'
defaultMessage='the contract '
/>
)
: '';
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<div> <FormattedMessage
<span>{ historic ? 'Received' : 'Will receive' } </span> id='ui.methodDecoding.receive.info'
<span className={ styles.highlight }> defaultMessage='{historic, select, true {Received} false {Will receive}} {valueEth} from {aContract}{address}'
{ this.renderEtherValue(transaction.value) } values={ {
</span> historic,
<span> from { isContract ? 'the contract' : '' } </span> valueEth,
</div> aContract,
address: this.renderAddressName(transaction.from)
{ this.renderAddressName(transaction.from) } } }
/>
{ this.renderInputValue() } { this.renderInputValue() }
</div> </div>
); );
@ -320,17 +428,32 @@ class MethodDecoding extends Component {
const { historic, transaction } = this.props; const { historic, transaction } = this.props;
const { isContract } = this.state; const { isContract } = this.state;
const valueEth = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
const aContract = isContract
? (
<FormattedMessage
id='ui.methodDecoding.transfer.contract'
defaultMessage='the contract '
/>
)
: '';
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<div> <FormattedMessage
<span>{ historic ? 'Transferred' : 'Will transfer' } </span> id='ui.methodDecoding.transfer.info'
<span className={ styles.highlight }> defaultMessage='{historic, select, true {Transferred} false {Will transfer}} {valueEth} to {aContract}{address}'
{ this.renderEtherValue(transaction.value) } values={ {
</span> historic,
<span> to { isContract ? 'the contract' : '' } </span> valueEth,
</div> aContract,
address: this.renderAddressName(transaction.to)
{ this.renderAddressName(transaction.to) } } }
/>
{ this.renderInputValue() } { this.renderInputValue() }
</div> </div>
); );
@ -340,26 +463,31 @@ class MethodDecoding extends Component {
const { historic, transaction } = this.props; const { historic, transaction } = this.props;
const { methodName, methodInputs } = this.state; const { methodName, methodInputs } = this.state;
const method = (
<span className={ styles.name }>
{ methodName }
</span>
);
const ethValue = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<div className={ styles.description }> <div className={ styles.description }>
<div> <FormattedMessage
<span>{ historic ? 'Executed' : 'Will execute' } the </span> id='ui.methodDecoding.signature.info'
<span className={ styles.name }>{ methodName }</span> defaultMessage='{historic, select, true {Executed} false {Will execute}} the {method} function on the contract {address} trsansferring {ethValue}{inputLength, plural, zero {,} other {passing the following {inputLength, plural, one {parameter} other {parameters}}}}'
<span> function on the contract </span> values={ {
</div> historic,
method,
{ this.renderAddressName(transaction.to) } ethValue,
address: this.renderAddressName(transaction.to),
<div> inputLength: methodInputs.length
<span>transferring </span> } }
<span className={ styles.highlight }> />
{ this.renderEtherValue(transaction.value) }
</span>
<span>
{ methodInputs.length ? ', passing the following parameters:' : '.' }
</span>
</div>
</div> </div>
<div className={ styles.inputs }> <div className={ styles.inputs }>
{ this.renderInputs() } { this.renderInputs() }
@ -371,23 +499,29 @@ class MethodDecoding extends Component {
renderUnknownMethod () { renderUnknownMethod () {
const { historic, transaction } = this.props; const { historic, transaction } = this.props;
const method = (
<span className={ styles.name }>
an unknown/unregistered
</span>
);
const ethValue = (
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
);
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<div> <FormattedMessage
<span>{ historic ? 'Executed' : 'Will execute' } </span> id='ui.methodDecoding.unknown.info'
<span className={ styles.name }>an unknown/unregistered</span> defaultMessage='{historic, select, true {Executed} false {Will execute}} the {method} on the contract {address} transferring {ethValue}.'
<span> method on the contract </span> values={ {
</div> historic,
method,
{ this.renderAddressName(transaction.to) } ethValue,
address: this.renderAddressName(transaction.to)
<div> } }
<span>transferring </span> />
<span className={ styles.highlight }>
{ this.renderEtherValue(transaction.value) }
</span>
<span>.</span>
</div>
</div> </div>
); );
} }

View File

@ -0,0 +1,35 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes } from 'react';
import styles from './modalBox.css';
export default function Body ({ children }) {
if (!children) {
return null;
}
return (
<div className={ styles.body }>
{ children }
</div>
);
}
Body.propTypes = {
children: PropTypes.node
};

View File

@ -14,60 +14,30 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { nodeOrStringProptype } from '~/util/proptypes'; import { nodeOrStringProptype } from '~/util/proptypes';
import Body from './body';
import Summary from './summary';
import styles from './modalBox.css'; import styles from './modalBox.css';
export default class ModalBox extends Component { export default function ModalBox ({ children, icon, summary }) {
static propTypes = { return (
children: PropTypes.node, <div className={ styles.body }>
icon: PropTypes.node.isRequired, <div className={ styles.icon }>
summary: nodeOrStringProptype() { icon }
}
render () {
const { icon } = this.props;
return (
<div className={ styles.body }>
<div className={ styles.icon }>
{ icon }
</div>
<div className={ styles.content }>
{ this.renderSummary() }
{ this.renderBody() }
</div>
</div> </div>
); <div className={ styles.content }>
} <Summary summary={ summary } />
<Body children={ children } />
renderBody () {
const { children } = this.props;
if (!children) {
return null;
}
return (
<div className={ styles.body }>
{ children }
</div> </div>
); </div>
} );
renderSummary () {
const { summary } = this.props;
if (!summary) {
return null;
}
return (
<div className={ styles.summary }>
{ summary }
</div>
);
}
} }
ModalBox.propTypes = {
children: PropTypes.node,
icon: PropTypes.node.isRequired,
summary: nodeOrStringProptype()
};

View File

@ -21,12 +21,16 @@ import ModalBox from './';
let component; let component;
const CHILDREN = <div id='testChild'>testChild</div>;
const ICON = <div id='testIcon'>testIcon</div>;
const SUMMARY = <div id='testSummary'>testSummary</div>;
function render () { function render () {
component = shallow( component = shallow(
<ModalBox <ModalBox
children={ <div id='testChild'>testChild</div> } children={ CHILDREN }
icon={ <div id='testIcon'>testIcon</div> } icon={ ICON }
summary={ <div id='testSummary'>testSummary</div> } summary={ SUMMARY }
/> />
); );
@ -42,15 +46,17 @@ describe('ui/ModalBox', () => {
expect(component).to.be.ok; expect(component).to.be.ok;
}); });
it('adds the children as supplied', () => {
expect(component.find('#testChild').text()).to.equal('testChild');
});
it('adds the icon as supplied', () => { it('adds the icon as supplied', () => {
expect(component.find('#testIcon').text()).to.equal('testIcon'); expect(component.find('#testIcon').text()).to.equal('testIcon');
}); });
it('adds the summary as supplied', () => { describe('components', () => {
expect(component.find('#testSummary').text()).to.equal('testSummary'); it('adds the Body as supplied', () => {
expect(component.find('Body').props().children).to.deep.equal(CHILDREN);
});
it('adds the Summary as supplied', () => {
expect(component.find('Summary').props().summary).to.deep.equal(SUMMARY);
});
}); });
}); });

View File

@ -0,0 +1,37 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import { nodeOrStringProptype } from '~/util/proptypes';
import styles from './modalBox.css';
export default function Summary ({ summary }) {
if (!summary) {
return null;
}
return (
<div className={ styles.summary }>
{ summary }
</div>
);
}
Summary.propTypes = {
summary: nodeOrStringProptype()
};

View File

@ -16,6 +16,7 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
@ -65,7 +66,10 @@ class Embedded extends Component {
if (!pending.length) { if (!pending.length) {
return ( return (
<div className={ styles.none }> <div className={ styles.none }>
There are currently no pending requests awaiting your confirmation <FormattedMessage
id='signer.embedded.noPending'
defaultMessage='There are currently no pending requests awaiting your confirmation'
/>
</div> </div>
); );
} }

View File

@ -15,10 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Store from '../../store'; import Store from '../../store';
import * as RequestsActions from '~/redux/providers/signerActions'; import * as RequestsActions from '~/redux/providers/signerActions';
@ -74,7 +75,14 @@ class RequestsPage extends Component {
} }
return ( return (
<Container title='Local Transactions'> <Container
title={
<FormattedMessage
id='signer.requestsPage.queueTitle'
defaultMessage='Local Transactions'
/>
}
>
<TxList <TxList
address='' address=''
hashes={ localHashes } hashes={ localHashes }
@ -90,7 +98,10 @@ class RequestsPage extends Component {
return ( return (
<Container> <Container>
<div className={ styles.noRequestsMsg }> <div className={ styles.noRequestsMsg }>
There are no requests requiring your confirmation. <FormattedMessage
id='signer.requestsPage.noPending'
defaultMessage='There are no requests requiring your confirmation.'
/>
</div> </div>
</Container> </Container>
); );
@ -99,7 +110,14 @@ class RequestsPage extends Component {
const items = pending.sort(this._sortRequests).map(this.renderPending); const items = pending.sort(this._sortRequests).map(this.renderPending);
return ( return (
<Container title='Pending Requests'> <Container
title={
<FormattedMessage
id='signer.requestsPage.pendingTitle'
defaultMessage='Pending Requests'
/>
}
>
{ items } { items }
</Container> </Container>
); );