Skip to content

Commit

Permalink
use final-form-react for form filling
Browse files Browse the repository at this point in the history
  • Loading branch information
backslash47 committed Apr 17, 2018
1 parent 6fc5a7a commit 49bbc3c
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 146 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dotenv": "^5.0.1",
"elasticsearch": "^14.2.1",
"file-saver": "^1.3.8",
"final-form": "^4.5.2",
"html5-websocket": "^2.0.2",
"http-aws-es": "^4.0.0",
"lodash": "^4.17.5",
Expand All @@ -22,6 +23,7 @@
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.2.0",
"react-file-reader-input": "^1.1.4",
"react-final-form": "^3.3.1",
"react-router-dom": "^4.2.2",
"react-scripts-ts": "2.14.0",
"recompose": "^0.26.0",
Expand Down
47 changes: 47 additions & 0 deletions src/form/formWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2018 Matus Zamborsky
* This file is part of The ONT Detective.
*
* The ONT Detective is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ONT Detective 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with The ONT Detective. If not, see <http://www.gnu.org/licenses/>.
*/

import { compose, mapProps } from 'recompose';
import { get } from 'lodash';
import { Form as SemanticForm, FormFieldProps, FormProps } from 'semantic-ui-react';
import { FieldRenderProps, FormRenderProps } from 'react-final-form';

export const FieldWrapper = compose<FormFieldProps, FieldRenderProps>(
mapProps<FormFieldProps, FieldRenderProps>((outer) => ({
...outer,
meta: undefined,
input: {
...outer.input,
value: outer.input.value
},
error: outer.meta.touched && outer.meta.invalid,
onChange: outer.input.onChange
}))
);

export const InputField = FieldWrapper(SemanticForm.Input);
export const TextareaField = FieldWrapper(SemanticForm.TextArea);

export const FormWrapper = compose<FormProps, FormRenderProps>(
mapProps<FormProps, FormRenderProps>((outer) => ({
onSubmit: outer.handleSubmit,
children: get(outer, 'children')
}))
);

export const Form = FormWrapper(SemanticForm);
27 changes: 10 additions & 17 deletions src/home/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import { compose, withHandlers, withState, flattenProp } from 'recompose';
import { InputOnChangeData } from 'semantic-ui-react';
import { get } from 'lodash';
import { getBlockByIndex, getBlock } from '~/shared/blocksApi';
import { getTransaction } from '~/shared/transactionsApi';
import { getAccount } from '~/shared/accountsApi';
Expand All @@ -38,32 +38,28 @@ function isBlockIndex(q: string): boolean {

interface State {
redirect?: string;
invalid?: boolean;
q: string;
}

interface Handlers {
handleSearch: () => void;
handleSearchChange: (e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData) => void;
handleSearch: (values: object) => void;
}

export interface PropsInner extends State, Handlers {
}

export default compose<PropsInner, {}>(
withState<null, State, 'state', 'setState'>('state', 'setState', {
q: ''
}),
withHandlers<StateSetter<State>, Handlers>({
handleSearch: ({state, setState}) => async () => {
let q = state.q.trim();
handleSearch: ({state, setState}) => async (values) => {
let q = get(values, 'q').trim();

if (isAddress(q)) {
try {
await getAccount(q);
setState({...state, redirect: `/accounts/${q}`});
} catch (e) {
setState({...state, invalid: true});
return Promise.resolve({q: 'Invalid address.'});
}
} else if (isBlockOrTransaction(q)) {
if (q.length === 66) {
Expand All @@ -77,25 +73,22 @@ export default compose<PropsInner, {}>(
await getTransaction(q);
setState({...state, redirect: `/transactions/${q}`});
} catch (e) {
setState({...state, invalid: true});
return Promise.resolve({q: 'Invalid block or transaction hash.'});
}
}
} else if (isBlockIndex(q)) {
try {
const block = await getBlockByIndex(Number(q));
setState({...state, redirect: `/blocks/${block.Hash}`});
} catch (e) {
setState({...state, invalid: true});
return Promise.resolve({q: 'Invalid block index.'});
}
} else {
setState({...state, invalid: true});
return Promise.resolve({q: 'Invalid search term.'});
}

return Promise.resolve({});
},
handleSearchChange: ({state, setState}) => (
e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData
) => {
setState({...state, invalid: false, q: data.value !== undefined ? data.value : ''});
}
}),
flattenProp('state')
)(View);
19 changes: 11 additions & 8 deletions src/home/homeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
*/

import * as React from 'react';
import { Grid, Header, Form, Message } from 'semantic-ui-react';
import { Redirect } from 'react-router-dom';
import './home.css';
import { Grid, Header, Button, Message } from 'semantic-ui-react';
import { Form as FinalForm, Field } from 'react-final-form';
import { InputField, Form } from '~/form/formWrapper';
import { PropsInner as Props } from './home';
import './home.css';

const logo = require('./detective.svg');

Expand All @@ -30,19 +32,20 @@ const Home: React.SFC<Props> = (props: Props) => (
<Grid.Column textAlign="center">
<Header as="h1">ONT Detective</Header>
<img className="logo" src={logo} />
<Form warning={props.invalid} onSubmit={props.handleSearch}>
<Form.Input
<FinalForm onSubmit={props.handleSearch} component={Form}>
<Field
name="q"
component={InputField}
fluid={true}
icon="search"
placeholder="Search by block/tx/address hash or block index"
input={{value: props.q}}
onChange={props.handleSearchChange}
/>

<Message warning={true}>
Invalid account/block/transaction.
</Message>
<Form.Button className="search">Search</Form.Button>
</Form>
<Button className="search">Search</Button>
</FinalForm>
{props.redirect != null ? (<Redirect to={props.redirect}/>) : null}
</Grid.Column>
</Grid.Row>
Expand Down
104 changes: 28 additions & 76 deletions src/ontIds/createClaim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
*/

import { compose, withState, withHandlers, flattenProp, withProps } from 'recompose';
import { get } from 'lodash';
import { RouterProps, match } from 'react-router';
import { withRouter } from 'react-router-dom';
import { InputOnChangeData, TextAreaProps } from 'semantic-ui-react';
import { StateSetter } from '~/utils';
import { registerSelfClaim, Errors } from '~/shared/walletApi';
import { registerSelfClaim } from '~/shared/walletApi';
import View from './createClaimView';

interface PropsOuter {
Expand All @@ -33,18 +33,13 @@ interface PropsOwn {
}

interface State {
passwordInput: string;
contextInput: string;
contentInput: string;
registering: boolean;
wrong: string | null;
}

interface Handlers {
handleCreate: () => void;
handlePasswordChange: (e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData) => void;
handleContextChange: (e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData) => void;
handleContentChange: (e: React.SyntheticEvent<HTMLTextAreaElement>, data: TextAreaProps) => void;
handleCreate: (values: object) => void;
handleValidateNotEmpty: (value: string) => boolean;
handleValidateJSon: (value: string) => boolean;
}

export interface PropsInner extends Handlers, State, PropsOwn, PropsOuter {
Expand All @@ -56,57 +51,20 @@ export default compose<PropsInner, PropsOuter>(
id: props.match.params.id
})),
withState<null, Partial<State>, 'state', 'setState'>('state', 'setState', {
passwordInput: '',
contextInput: '',
contentInput: '',
registering: false,
wrong: null
registering: false
}),
withHandlers<PropsOuter & PropsOwn & StateSetter<State> & RouterProps, Handlers>({
handleCreate: (props) => async () => {
handleCreate: (props) => async (values) => {
props.setState({
...props.state,
registering: true,
wrong: null
registering: true
});

const password = props.state.passwordInput;
const context = props.state.contextInput;
const content = props.state.contentInput;

if (password.trim().length === 0) {
props.setState({
...props.state,
registering: false,
wrong: 'Empty password.'
});

return;
}

if (context.trim().length === 0) {
props.setState({
...props.state,
registering: false,
wrong: 'Invalid context.'
});

return;
}

let contentJson;
try {
contentJson = JSON.parse(content);
} catch (e) {
props.setState({
...props.state,
registering: false,
wrong: 'Invalid JSON content.'
});

return;
}

const password = get(values, 'password');
const context = get(values, 'context');
const content = get(values, 'content');
const contentJson = JSON.parse(content);

try {
await registerSelfClaim(props.id, password, context, contentJson);

Expand All @@ -116,30 +74,24 @@ export default compose<PropsInner, PropsOuter>(
});

props.history.push(`/ont-ids/${props.id}`);
return Promise.resolve({});
} catch (e) {
if (e === Errors.WRONG_PASSWORD) {
props.setState({
...props.state,
registering: false,
wrong: 'Invalid password.'
});
}
props.setState({
...props.state,
registering: false,
});

return Promise.resolve({ password: 'Invalid password.'});
}
},
handlePasswordChange: ({state, setState}) => (
e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData
) => {
setState({...state, passwordInput: data.value !== undefined ? data.value : ''});
},
handleContextChange: ({state, setState}) => (
e: React.SyntheticEvent<HTMLInputElement>, data: InputOnChangeData
) => {
setState({...state, contextInput: data.value !== undefined ? data.value : ''});
},
handleContentChange: ({state, setState}) => (
e: React.SyntheticEvent<HTMLTextAreaElement>, data: TextAreaProps
) => {
setState({...state, contentInput: data.value !== undefined ? data.value.toString() : ''});
handleValidateNotEmpty: (props) => (value) => (value === undefined || value.trim().length === 0),
handleValidateJSon: (props) => (value) => {
try {
JSON.parse(value);
return false;
} catch (e) {
return true;
}
}
}),
flattenProp('state'),
Expand Down
29 changes: 18 additions & 11 deletions src/ontIds/createClaimView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

import * as React from 'react';
import { Link } from 'react-router-dom';
import { Breadcrumb, Segment, Header, Loader, Form, Message } from 'semantic-ui-react';
import { Form as FinalForm, Field } from 'react-final-form';
import { Breadcrumb, Segment, Header, Loader, Message, Button } from 'semantic-ui-react';
import { InputField, TextareaField, Form } from '~/form/formWrapper';
import { PropsInner as Props } from './createClaim';

const OntIdView: React.SFC<Props> = (props) => (
Expand Down Expand Up @@ -46,26 +48,31 @@ const OntIdView: React.SFC<Props> = (props) => (
{props.registering ? (
<Loader active={true} inline="centered">Asserting Claim on blockchain ...</Loader>
) : (
<Form warning={props.wrong !== null} onSubmit={props.handleCreate}>
<Message warning={true}>{props.wrong}</Message>
<Form.Input
<FinalForm onSubmit={props.handleCreate} component={Form}>
<Field
name="password"
component={InputField}
fluid={true}
label="Password"
type="password"
onChange={props.handlePasswordChange}
validate={props.handleValidateNotEmpty}
/>
<Form.Input
<Field
name="context"
component={InputField}
fluid={true}
label="Context"
onChange={props.handleContextChange}
placeholder="claim:standard0001"
validate={props.handleValidateNotEmpty}
/>
<Form.TextArea
<Field
name="content"
component={TextareaField}
label="Content"
onChange={props.handleContentChange}
validate={props.handleValidateJSon}
/>
<Form.Button>Create</Form.Button>
</Form>
<Button>Create</Button>
</FinalForm>
)}
</Segment>
</Segment.Group>
Expand Down
3 changes: 1 addition & 2 deletions src/shared/ontIdApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ export async function getOntIdsByIds(
direction: Direction
): Promise<Result<OntId>> {
const client = getClient();
console.log('ids', ids);


const params: SearchParams = {
index: Indices.OntId,
from,
Expand Down
Loading

0 comments on commit 49bbc3c

Please sign in to comment.