AddressInput component & Donation address (#157)
* Add donation address constant. Add AddressInput component. Replace using those 2 where needed. * Fix invalid defaultValue logic
This commit is contained in:
parent
abc824b17e
commit
2895d1045a
|
@ -0,0 +1,35 @@
|
||||||
|
.AddressInput {
|
||||||
|
&-input {
|
||||||
|
&-identicon {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 100%;
|
||||||
|
left: -2px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper-lg & {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper-sm & {
|
||||||
|
left: -4px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ant overrides
|
||||||
|
&.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||||
|
padding-left: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-input-affix-wrapper-lg .ant-input:not(:first-child) {
|
||||||
|
padding-left: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-input-affix-wrapper-sm .ant-input:not(:first-child) {
|
||||||
|
padding-left: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { Form, Input } from 'antd';
|
||||||
|
import { InputProps } from 'antd/lib/input';
|
||||||
|
import { FormItemProps } from 'antd/lib/form';
|
||||||
|
import { isValidEthAddress } from 'utils/validators';
|
||||||
|
import Identicon from 'components/Identicon';
|
||||||
|
import { DONATION } from 'utils/constants';
|
||||||
|
import './AddressInput.less';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
value: string | undefined;
|
||||||
|
className?: string;
|
||||||
|
showIdenticon?: boolean;
|
||||||
|
inputProps?: InputProps;
|
||||||
|
formItemProps?: FormItemProps;
|
||||||
|
onChange(ev: React.ChangeEvent<HTMLInputElement>): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AddressInput extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const { value, onChange, className, showIdenticon } = this.props;
|
||||||
|
const passedFormItemProps = this.props.formItemProps || {};
|
||||||
|
const passedInputProps = this.props.inputProps || {};
|
||||||
|
const isInvalid = value && !isValidEthAddress(value);
|
||||||
|
|
||||||
|
const formItemProps = {
|
||||||
|
validateStatus: (isInvalid
|
||||||
|
? 'error'
|
||||||
|
: undefined) as FormItemProps['validateStatus'],
|
||||||
|
help: isInvalid ? 'Address is invalid' : undefined,
|
||||||
|
...passedFormItemProps,
|
||||||
|
className: classnames('AddressInput', className, passedFormItemProps.className),
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputProps = {
|
||||||
|
placeholder: DONATION.ETH,
|
||||||
|
prefix: value &&
|
||||||
|
showIdenticon && (
|
||||||
|
<Identicon className="AddressInput-input-identicon" address={value} />
|
||||||
|
),
|
||||||
|
...passedInputProps,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
className: classnames(
|
||||||
|
'AddressInput-input',
|
||||||
|
className && `${className}-input`,
|
||||||
|
passedInputProps.className,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item {...formItemProps}>
|
||||||
|
<Input {...inputProps} />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Form, Input, Button } from 'antd';
|
import { Form, Button } from 'antd';
|
||||||
import { isValidEthAddress } from 'utils/validators';
|
import { isValidEthAddress } from 'utils/validators';
|
||||||
|
import AddressInput from 'components/AddressInput';
|
||||||
import './Address.less';
|
import './Address.less';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -20,14 +21,12 @@ export default class AddressProvider extends React.Component<Props, State> {
|
||||||
const { address } = this.state;
|
const { address } = this.state;
|
||||||
return (
|
return (
|
||||||
<Form className="AddressProvider" onSubmit={this.handleSubmit}>
|
<Form className="AddressProvider" onSubmit={this.handleSubmit}>
|
||||||
<Form.Item className="AddressProvider-address">
|
<AddressInput
|
||||||
<Input
|
className="AddressProvider-address"
|
||||||
size="large"
|
value={address}
|
||||||
value={address}
|
onChange={this.handleChange}
|
||||||
onChange={this.handleChange}
|
inputProps={{ size: 'large' }}
|
||||||
placeholder="0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520"
|
/>
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
import { CreateFormState } from 'types';
|
import { CreateFormState } from 'types';
|
||||||
import { getCreateErrors } from 'modules/create/utils';
|
import { getCreateErrors } from 'modules/create/utils';
|
||||||
import { ONE_DAY } from 'utils/time';
|
import { ONE_DAY } from 'utils/time';
|
||||||
|
import { DONATION } from 'utils/constants';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
payOutAddress: string;
|
payOutAddress: string;
|
||||||
|
@ -43,7 +44,7 @@ export default class CreateFlowTeam extends React.Component<Props, State> {
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size="large"
|
||||||
name="payOutAddress"
|
name="payOutAddress"
|
||||||
placeholder="0xe12a34230e5e7fc73d094e52025135e4fbf24653"
|
placeholder={DONATION.ETH}
|
||||||
type="text"
|
type="text"
|
||||||
value={payOutAddress}
|
value={payOutAddress}
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
|
@ -192,7 +193,7 @@ const TrusteeFields = ({
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<Input
|
<Input
|
||||||
size="large"
|
size="large"
|
||||||
placeholder="0xe12a34230e5e7fc73d094e52025135e4fbf24653"
|
placeholder={DONATION.ETH}
|
||||||
type="text"
|
type="text"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={ev => onChange(index, ev.currentTarget.value)}
|
onChange={ev => onChange(index, ev.currentTarget.value)}
|
||||||
|
|
|
@ -1,6 +1,24 @@
|
||||||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||||
import { SOCIAL_TYPE, CreateFormState } from 'types';
|
import { SOCIAL_TYPE, CreateFormState } from 'types';
|
||||||
|
|
||||||
|
function generateRandomAddress() {
|
||||||
|
return (
|
||||||
|
'0x' +
|
||||||
|
Math.random()
|
||||||
|
.toString(16)
|
||||||
|
.substring(2, 12) +
|
||||||
|
Math.random()
|
||||||
|
.toString(16)
|
||||||
|
.substring(2, 12) +
|
||||||
|
Math.random()
|
||||||
|
.toString(16)
|
||||||
|
.substring(2, 12) +
|
||||||
|
Math.random()
|
||||||
|
.toString(16)
|
||||||
|
.substring(2, 12)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const createExampleProposal = (
|
const createExampleProposal = (
|
||||||
payOutAddress: string,
|
payOutAddress: string,
|
||||||
trustees: string[],
|
trustees: string[],
|
||||||
|
@ -29,7 +47,7 @@ const createExampleProposal = (
|
||||||
avatarUrl: `https://randomuser.me/api/portraits/women/${Math.floor(
|
avatarUrl: `https://randomuser.me/api/portraits/women/${Math.floor(
|
||||||
Math.random() * 80,
|
Math.random() * 80,
|
||||||
)}.jpg`,
|
)}.jpg`,
|
||||||
ethAddress: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
ethAddress: generateRandomAddress(),
|
||||||
emailAddress: 'designer@tshirt.com',
|
emailAddress: 'designer@tshirt.com',
|
||||||
socialAccounts: {
|
socialAccounts: {
|
||||||
[SOCIAL_TYPE.KEYBASE]: 'willo',
|
[SOCIAL_TYPE.KEYBASE]: 'willo',
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const DONATION = {
|
||||||
|
ETH: '0x8B0B72F8bDE212991135668922fD5acE557DE6aB',
|
||||||
|
};
|
|
@ -0,0 +1,91 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { DONATION } from 'utils/constants';
|
||||||
|
|
||||||
|
import 'components/AddressInput.less';
|
||||||
|
import AddressInput, { Props as AddressInputProps } from 'components/AddressInput';
|
||||||
|
|
||||||
|
const cases: ExampleProps[] = [
|
||||||
|
{
|
||||||
|
disp: 'Default Input',
|
||||||
|
props: {},
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disp: 'Input w/ Identicon',
|
||||||
|
props: {
|
||||||
|
showIdenticon: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disp: 'Input w/ Identicon (large)',
|
||||||
|
props: {
|
||||||
|
showIdenticon: true,
|
||||||
|
inputProps: {
|
||||||
|
size: 'large',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disp: 'Input w/ Identicon (small)',
|
||||||
|
props: {
|
||||||
|
showIdenticon: true,
|
||||||
|
inputProps: {
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disp: 'Invalid Input',
|
||||||
|
props: {},
|
||||||
|
defaultValue: '0x0what',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
storiesOf('AddressInput', module).add('all', () => (
|
||||||
|
<div style={{ padding: '2em' }}>
|
||||||
|
{cases.map(c => (
|
||||||
|
<Example key={c.disp} {...c} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
interface ExampleProps {
|
||||||
|
disp: string;
|
||||||
|
defaultValue?: string;
|
||||||
|
props: Partial<AddressInputProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExampleState {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Example extends React.Component<ExampleProps, ExampleState> {
|
||||||
|
constructor(props: ExampleProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
value: props.defaultValue !== undefined ? props.defaultValue : DONATION.ETH,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const props = {
|
||||||
|
...this.props.props,
|
||||||
|
value: this.state.value,
|
||||||
|
onChange: this.handleChange,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: '2rem', width: '280px' }}>
|
||||||
|
<div style={{ fontSize: '0.9em', paddingBottom: '0.5rem' }}>
|
||||||
|
{this.props.disp}
|
||||||
|
</div>
|
||||||
|
<AddressInput {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ value: ev.currentTarget.value });
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { storiesOf } from '@storybook/react';
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { DONATION } from 'utils/constants';
|
||||||
|
|
||||||
import 'components/UserRow/style.less';
|
import 'components/UserRow/style.less';
|
||||||
import UserRow from 'components/UserRow';
|
import UserRow from 'components/UserRow';
|
||||||
|
@ -9,7 +10,7 @@ const user = {
|
||||||
name: 'Dana Hayes',
|
name: 'Dana Hayes',
|
||||||
title: 'QA Engineer',
|
title: 'QA Engineer',
|
||||||
avatarUrl: 'https://randomuser.me/api/portraits/women/19.jpg',
|
avatarUrl: 'https://randomuser.me/api/portraits/women/19.jpg',
|
||||||
ethAddress: '0x4bbeEB066eD09B7AEd07bF39EEe0460DFa261520',
|
ethAddress: DONATION.ETH,
|
||||||
emailAddress: 'test@test.test',
|
emailAddress: 'test@test.test',
|
||||||
socialAccounts: {},
|
socialAccounts: {},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue