diff --git a/package.json b/package.json
index dfb395e..29a8728 100644
--- a/package.json
+++ b/package.json
@@ -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",
@@ -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",
diff --git a/src/form/formWrapper.ts b/src/form/formWrapper.ts
new file mode 100644
index 0000000..1e5b847
--- /dev/null
+++ b/src/form/formWrapper.ts
@@ -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 .
+ */
+
+ 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(
+ mapProps((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(
+ mapProps((outer) => ({
+ onSubmit: outer.handleSubmit,
+ children: get(outer, 'children')
+ }))
+ );
+
+ export const Form = FormWrapper(SemanticForm);
diff --git a/src/home/home.ts b/src/home/home.ts
index 0e7b4cc..b584135 100644
--- a/src/home/home.ts
+++ b/src/home/home.ts
@@ -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';
@@ -38,13 +38,10 @@ function isBlockIndex(q: string): boolean {
interface State {
redirect?: string;
- invalid?: boolean;
- q: string;
}
interface Handlers {
- handleSearch: () => void;
- handleSearchChange: (e: React.SyntheticEvent, data: InputOnChangeData) => void;
+ handleSearch: (values: object) => void;
}
export interface PropsInner extends State, Handlers {
@@ -52,18 +49,17 @@ export interface PropsInner extends State, Handlers {
export default compose(
withState('state', 'setState', {
- q: ''
}),
withHandlers, 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) {
@@ -77,7 +73,7 @@ export default compose(
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)) {
@@ -85,17 +81,14 @@ export default compose(
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, data: InputOnChangeData
- ) => {
- setState({...state, invalid: false, q: data.value !== undefined ? data.value : ''});
- }
}),
flattenProp('state')
)(View);
diff --git a/src/home/homeView.tsx b/src/home/homeView.tsx
index bc8bf3d..21eb8f5 100644
--- a/src/home/homeView.tsx
+++ b/src/home/homeView.tsx
@@ -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');
@@ -30,19 +32,20 @@ const Home: React.SFC = (props: Props) => (
-
+
+
Invalid account/block/transaction.
- Search
-
+
+
{props.redirect != null ? () : null}
diff --git a/src/ontIds/createClaim.ts b/src/ontIds/createClaim.ts
index b8e22a8..289d210 100644
--- a/src/ontIds/createClaim.ts
+++ b/src/ontIds/createClaim.ts
@@ -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 {
@@ -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, data: InputOnChangeData) => void;
- handleContextChange: (e: React.SyntheticEvent, data: InputOnChangeData) => void;
- handleContentChange: (e: React.SyntheticEvent, data: TextAreaProps) => void;
+ handleCreate: (values: object) => void;
+ handleValidateNotEmpty: (value: string) => boolean;
+ handleValidateJSon: (value: string) => boolean;
}
export interface PropsInner extends Handlers, State, PropsOwn, PropsOuter {
@@ -56,57 +51,20 @@ export default compose(
id: props.match.params.id
})),
withState, 'state', 'setState'>('state', 'setState', {
- passwordInput: '',
- contextInput: '',
- contentInput: '',
- registering: false,
- wrong: null
+ registering: false
}),
withHandlers & 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);
@@ -116,30 +74,24 @@ export default compose(
});
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, data: InputOnChangeData
- ) => {
- setState({...state, passwordInput: data.value !== undefined ? data.value : ''});
- },
- handleContextChange: ({state, setState}) => (
- e: React.SyntheticEvent, data: InputOnChangeData
- ) => {
- setState({...state, contextInput: data.value !== undefined ? data.value : ''});
- },
- handleContentChange: ({state, setState}) => (
- e: React.SyntheticEvent, 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'),
diff --git a/src/ontIds/createClaimView.tsx b/src/ontIds/createClaimView.tsx
index 1555330..31b617b 100644
--- a/src/ontIds/createClaimView.tsx
+++ b/src/ontIds/createClaimView.tsx
@@ -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) => (
@@ -46,26 +48,31 @@ const OntIdView: React.SFC = (props) => (
{props.registering ? (
Asserting Claim on blockchain ...
) : (
-
+
-
-
- Create
-
+
+
)}
diff --git a/src/shared/ontIdApi.ts b/src/shared/ontIdApi.ts
index 445cbb9..a775fa1 100644
--- a/src/shared/ontIdApi.ts
+++ b/src/shared/ontIdApi.ts
@@ -53,8 +53,7 @@ export async function getOntIdsByIds(
direction: Direction
): Promise> {
const client = getClient();
- console.log('ids', ids);
-
+
const params: SearchParams = {
index: Indices.OntId,
from,
diff --git a/src/shared/walletApi.ts b/src/shared/walletApi.ts
index b3ee204..38c1f78 100644
--- a/src/shared/walletApi.ts
+++ b/src/shared/walletApi.ts
@@ -10,8 +10,8 @@ export enum Errors {
WRONG_PASSWORD = 'Wrong password'
}
-export function registerIdentity(ontId: string, privKey: string): Promise {
- return new Promise((resolve, reject) => {
+export async function registerIdentity(ontId: string, privKey: string): Promise {
+ return new Promise((resolve, reject) => {
const tx = OntidContract.buildRegisterOntidTx(ontId, privKey);
const raw = builder.sendRawTransaction(tx.serialize());
diff --git a/src/wallet/createWallet.ts b/src/wallet/createWallet.ts
index a6c5e42..4e23ef3 100644
--- a/src/wallet/createWallet.ts
+++ b/src/wallet/createWallet.ts
@@ -17,9 +17,9 @@
*/
import { compose, withState, withHandlers, flattenProp } from 'recompose';
+import { get } from 'lodash';
import { RouterProps } from 'react-router';
import { withRouter } from 'react-router-dom';
-import { InputOnChangeData } from 'semantic-ui-react';
import { Wallet, scrypt } from 'ont-sdk-ts';
import { StateSetter } from '~/utils';
import { registerIdentity, saveWallet } from '~/shared/walletApi';
@@ -29,18 +29,16 @@ interface PropsOuter {
}
interface PropsOwn {
+ initialValues: object;
}
interface State {
- nameInput: string;
- passwordInput: string;
registering: boolean;
}
interface Handlers {
- handleCreate: () => void;
- handleNameChange: (e: React.SyntheticEvent, data: InputOnChangeData) => void;
- handlePasswordChange: (e: React.SyntheticEvent, data: InputOnChangeData) => void;
+ handleCreate: (values: object) => void;
+ handleValidateNotEmpty: (value: string) => boolean;
}
export interface PropsInner extends Handlers, State, PropsOwn, PropsOuter {
@@ -49,19 +47,17 @@ export interface PropsInner extends Handlers, State, PropsOwn, PropsOuter {
export default compose(
withRouter,
withState, 'state', 'setState'>('state', 'setState', {
- nameInput: '',
- passwordInput: '',
registering: false
}),
withHandlers & RouterProps, Handlers>({
- handleCreate: (props) => async () => {
+ handleCreate: (props) => async (values) => {
props.setState({
...props.state,
registering: true
});
- const name = props.state.nameInput;
- const password = props.state.passwordInput;
+ const name = get(values, 'name', '');
+ const password = get(values, 'password', '');
const wallet = Wallet.createIdentityWallet(password, name);
@@ -80,16 +76,7 @@ export default compose(
props.history.push('/wallet');
},
- handleNameChange: ({state, setState}) => (
- e: React.SyntheticEvent, data: InputOnChangeData
- ) => {
- setState({...state, nameInput: data.value !== undefined ? data.value : ''});
- },
- handlePasswordChange: ({state, setState}) => (
- e: React.SyntheticEvent, data: InputOnChangeData
- ) => {
- setState({...state, passwordInput: data.value !== undefined ? data.value : ''});
- }
+ handleValidateNotEmpty: (props) => (value) => (value === undefined || value.trim().length === 0)
}),
flattenProp('state'),
) (View);
diff --git a/src/wallet/createWalletView.tsx b/src/wallet/createWalletView.tsx
index 1547311..2e6783b 100644
--- a/src/wallet/createWalletView.tsx
+++ b/src/wallet/createWalletView.tsx
@@ -18,7 +18,9 @@
import * as React from 'react';
import { Link } from 'react-router-dom';
-import { Breadcrumb, Segment, Header, Form, Message, Loader } from 'semantic-ui-react';
+import { Form as FinalForm, Field } from 'react-final-form';
+import { Breadcrumb, Segment, Header, Message, Loader, Button } from 'semantic-ui-react';
+import { InputField, Form } from '~/form/formWrapper';
import { PropsInner as Props } from './createWallet';
const CreateWallet: React.SFC = (props) => (
@@ -42,20 +44,24 @@ const CreateWallet: React.SFC = (props) => (
{props.registering ? (
Registering ONT ID on blockchain ...
) : (
-
+
-
- Create
-
+
+
)}
diff --git a/yarn.lock b/yarn.lock
index 667f7f0..2bd1b2a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3124,6 +3124,10 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
+final-form@^4.5.2:
+ version "4.5.2"
+ resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.5.2.tgz#f8a9518784bdc2f72e71548a42b04c640963ca49"
+
finalhandler@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
@@ -6401,6 +6405,10 @@ react-file-reader-input@^1.1.4:
dependencies:
karma "^0.13.22"
+react-final-form@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-3.3.1.tgz#dbe40eec853a9c451e5a8deb3b02d948a2fb7e3c"
+
react-router-dom@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d"