diff --git a/package.json b/package.json index feb600844c..bb17b024ef 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@types/react-native": "^0.61.23", "@types/react-native-share": "^3.1.0", "@types/react-native-snap-carousel": "^3.8.1", + "@types/react-navigation": "^3.4.0", "@types/react-redux": "^7.1.7", "@types/react-test-renderer": "^16.9.0", "@types/uuid": "^3.4.8", diff --git a/src/components/GenericInputItem.tsx b/src/components/GenericInputItem.tsx index c7cc72df5d..187b0df362 100644 --- a/src/components/GenericInputItem.tsx +++ b/src/components/GenericInputItem.tsx @@ -10,6 +10,7 @@ interface Props { title: string; value?: string; validate?: (value: string) => string | undefined; + validateOnSave?: (value: string) => void; onSave?: (value: string) => void; } @@ -27,6 +28,7 @@ export const GenericInputItem = (props: Props) => { label, value, validate: props.validate, + validateOnSave: props.validateOnSave, onSave: handleValueSave, }); }; diff --git a/src/consts/models.tsx b/src/consts/models.tsx index 5566100921..770b5c7e81 100644 --- a/src/consts/models.tsx +++ b/src/consts/models.tsx @@ -146,6 +146,7 @@ export type RootStackParams = { header?: React.ReactNode; value?: string; validate?: (value: string) => string | undefined; + validateOnSave?: (value: string) => void; keyboardType?: KeyboardType; }; [Route.Message]: { diff --git a/src/helpers/DataProcessing.ts b/src/helpers/DataProcessing.ts index fd25ebfb70..02b5192690 100644 --- a/src/helpers/DataProcessing.ts +++ b/src/helpers/DataProcessing.ts @@ -1,3 +1,5 @@ +const bitcoin = require('bitcoinjs-lib'); + export const processAddressData = (data: string, stateAmount?: any) => { const regex = /[?&]([^=#]+)=([^&#]*)/g; const solvedData = regex.exec(data); @@ -21,3 +23,5 @@ export const processAddressData = (data: string, stateAmount?: any) => { return newAddresses; }; + +export const checkAddress = (address: string) => bitcoin.address.toOutputScript(address); diff --git a/src/screens/ContactDetailsScreen.tsx b/src/screens/ContactDetailsScreen.tsx index 2b94033726..4ace88c1dc 100644 --- a/src/screens/ContactDetailsScreen.tsx +++ b/src/screens/ContactDetailsScreen.tsx @@ -15,6 +15,7 @@ import { } from 'app/components'; import { CopyButton } from 'app/components/CopyButton'; import { Contact, Route, MainCardStackNavigatorParams, RootStackParams } from 'app/consts'; +import { checkAddress } from 'app/helpers/DataProcessing'; import { UpdateContactAction, updateContact } from 'app/state/contacts/actions'; const i18n = require('../../loc'); @@ -53,6 +54,10 @@ export class ContactDetailsScreen extends React.PureComponent { this.saveChanges({ address }); }; + validateAddress = (address: string) => { + checkAddress(address); + }; + saveChanges = (changes: Partial) => { const { contact } = this.props.route.params; const updatedContact = { ...contact, ...changes }; @@ -114,6 +119,7 @@ export class ContactDetailsScreen extends React.PureComponent { title={i18n.contactDetails.editAddress} label={i18n.contactDetails.addressLabel} value={address} + validateOnSave={this.validateAddress} onSave={this.setAddress} /> diff --git a/src/screens/CreateContactScreen.tsx b/src/screens/CreateContactScreen.tsx index 151f890363..b8407a825c 100644 --- a/src/screens/CreateContactScreen.tsx +++ b/src/screens/CreateContactScreen.tsx @@ -8,6 +8,7 @@ import { v4 as uuidv4 } from 'uuid'; import { icons } from 'app/assets'; import { Button, Header, InputItem, ScreenTemplate, Text, Image } from 'app/components'; import { Contact, Route, MainTabNavigatorParams, MainCardStackNavigatorParams } from 'app/consts'; +import { checkAddress } from 'app/helpers/DataProcessing'; import { CreateMessage, MessageType } from 'app/helpers/MessageCreator'; import { createContact, CreateContactAction } from 'app/state/contacts/actions'; import { palette, typography } from 'app/styles'; @@ -25,12 +26,14 @@ interface Props { interface State { name: string; address: string; + error: string; } export class CreateContactScreen extends React.PureComponent { state: State = { name: '', address: '', + error: '', }; get canCreateContact(): boolean { @@ -39,23 +42,35 @@ export class CreateContactScreen extends React.PureComponent { setName = (name: string) => this.setState({ name }); - setAddress = (address: string) => this.setState({ address }); + setAddress = (address: string) => this.setState({ address, error: '' }); onBarCodeScan = (address: string) => { this.setAddress(address.split('?')[0].replace('bitcoin:', '')); }; createContact = () => { - this.props.createContact({ - id: uuidv4(), - name: this.state.name.trim(), - address: this.state.address.trim(), - }); - this.showSuccessImportMessageScreen(); - this.setState({ - name: '', - address: '', - }); + try { + this.validateAddress(); + if (this.state.error) return; + this.props.createContact({ + id: uuidv4(), + name: this.state.name.trim(), + address: this.state.address.trim(), + }); + this.showSuccessImportMessageScreen(); + this.setState({ + name: '', + address: '', + }); + } catch (_) { + this.setState({ + error: i18n.send.details.address_field_is_not_valid, + }); + } + }; + + validateAddress = () => { + checkAddress(this.state.address); }; onScanQrCodePress = () => { @@ -95,6 +110,7 @@ export class CreateContactScreen extends React.PureComponent { { const { label, header, onSave, title } = params; const keyboardType = params.keyboardType || 'default'; const validate = params.validate || null; + const validateOnSave = params.validateOnSave || null; const [value, setValue] = useState(params.value || ''); + const [error, setError] = useState(''); const handlePressOnSaveButton = () => { + if (validateOnSave) { + try { + validateOnSave(value); + } catch (err) { + setError(i18n.send.details.address_field_is_not_valid); + return; + } + } onSave(value); props.navigation.pop(); }; @@ -43,7 +53,7 @@ export const EditTextScreen = (props: Props) => { value={value} setValue={setValue} autoFocus={true} - error={!!validate && validate(value)} + error={error || (value && !!validate && validate(value)) || ''} keyboardType={keyboardType as KeyboardType} /> diff --git a/yarn.lock b/yarn.lock index 7472c294ab..5641f8603b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1265,6 +1265,16 @@ color "^3.1.2" react-native-iphone-x-helper "^1.2.1" +"@react-navigation/core@^3.7.6": + version "3.7.6" + resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-3.7.6.tgz#e0244fcdc22937825b252197f70308bbe5709c58" + integrity sha512-loYFIn0Boy7C+vYxwcqsBVRFRO1EizZJErdutE6/3Jw6dbzz3Bnzupbw5hckZNB16GckacMwGoepZNIK51IIcg== + dependencies: + hoist-non-react-statics "^3.3.2" + path-to-regexp "^1.8.0" + query-string "^6.11.1" + react-is "^16.13.0" + "@react-navigation/core@^5.11.1": version "5.11.1" resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-5.11.1.tgz#c4890910ba3d6332ee6873f0adb3d3d13cf4fa4c" @@ -1277,6 +1287,14 @@ react-is "^16.13.0" use-subscription "^1.4.0" +"@react-navigation/native@^3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-3.8.0.tgz#35882c3fc2f997ea1a43c12a15088cf4c818fd92" + integrity sha512-Uym5XdNOTpTR6XC4IVa/Shfn12DUF3DUIqch+cpiLNfvauQukVmWoz+Rfxd2faGOzxT12EhrWGFsMJ/8nuTfcw== + dependencies: + hoist-non-react-statics "^3.3.2" + react-native-safe-area-view "^0.14.9" + "@react-navigation/native@^5.6.1": version "5.6.1" resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-5.6.1.tgz#a603b921f39fe3fcfcc27232d71b24e80effc1f2" @@ -1583,6 +1601,13 @@ dependencies: "@types/react" "*" +"@types/react-navigation@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@types/react-navigation/-/react-navigation-3.4.0.tgz#d610d13c9162312079a8ca102660143f07432cbf" + integrity sha512-Y7F5zU8BTBK8tEOvUqgvwvPZ7+9vnc2UI1vHwJ/9ZJG98TntNv04GWa6lrn4MA4149pqw6cyNw/V49Yd2osAFQ== + dependencies: + react-navigation "*" + "@types/react-redux@^7.1.7": version "7.1.9" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3" @@ -4678,7 +4703,7 @@ hoist-non-react-statics@^2.3.1: resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -7287,6 +7312,13 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -7627,7 +7659,7 @@ query-string@5: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -query-string@^6.13.1: +query-string@^6.11.1, query-string@^6.13.1: version "6.13.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad" integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA== @@ -7923,6 +7955,13 @@ react-native-safe-area-context@^3.0.6: resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.0.6.tgz#ee180f53f9f40f8302923b9c09d821cf8ada01eb" integrity sha512-/McWHgRG3CjXo/1ctlxH3mjW2psjf/QYAt9kWUTEtHu4b6z1y4hfUIGuYEJ02asaS1ixPsYrkqVqwzTv4olUMQ== +react-native-safe-area-view@^0.14.9: + version "0.14.9" + resolved "https://registry.yarnpkg.com/react-native-safe-area-view/-/react-native-safe-area-view-0.14.9.tgz#90ee8383037010d9a5055a97cf97e4c1da1f0c3d" + integrity sha512-WII/ulhpVyL/qbYb7vydq7dJAfZRBcEhg4/UWt6F6nAKpLa3gAceMOxBxI914ppwSP/TdUsandFy6lkJQE0z4A== + dependencies: + hoist-non-react-statics "^2.3.1" + react-native-safe-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-native-safe-modules/-/react-native-safe-modules-1.0.0.tgz#10a918adf97da920adb1e33e0c852b1e80123b65" @@ -8034,6 +8073,14 @@ react-native@0.61.5: stacktrace-parser "^0.1.3" whatwg-fetch "^3.0.0" +react-navigation@*: + version "4.4.0" + resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-4.4.0.tgz#c5523669df642aab23ff9e2ceb2109d3492a9374" + integrity sha512-BtxqNNlEGm/ve1mGHxCvrtvDzZ+2OF/V9OJaDPz/Cdx2VvYiFGaq6mtlFQm5/2bLxiEVXhNFcYSTCP26YGiENA== + dependencies: + "@react-navigation/core" "^3.7.6" + "@react-navigation/native" "^3.8.0" + react-redux@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"