From 3e2f4d5c3590c65a3bdf538636b5ce64f40c022e Mon Sep 17 00:00:00 2001 From: Tj Date: Wed, 14 Jun 2017 19:09:21 -0700 Subject: [PATCH] Changing connectToState function nonsense. --- index.js | 2 +- package.json | 52 ++++++- src/CCInput.js | 56 ++++---- src/CreditCardInput.js | 273 ++++++++++++++++--------------------- src/FullCreditCardInput.js | 178 ++++++++++++++++++++++++ src/LiteCreditCardInput.js | 53 ++++--- 6 files changed, 397 insertions(+), 217 deletions(-) create mode 100644 src/FullCreditCardInput.js diff --git a/index.js b/index.js index 2a99d193..cab6a553 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -export * from "./src/"; +export { CreditCardInput } from "./src/CreditCardInput.js"; diff --git a/package.json b/package.json index e2653cbd..0533445d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-native-credit-card-input", "version": "0.3.3", "description": "React native credit card input component", - "main": "index.js", + "main": "./src/CreditCardInput.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "lint": "eslint --ext .js ." @@ -47,5 +47,55 @@ "eslint": "~2.2.0", "eslint-config-airbnb": "6.0.2", "eslint-plugin-react": "^4.2.0" + }, + "react-native": { + "zlib": "browserify-zlib", + "console": "console-browserify", + "constants": "constants-browserify", + "crypto": "react-native-crypto", + "dns": "dns.js", + "net": "react-native-tcp", + "domain": "domain-browser", + "http": "react-native-http", + "https": "https-browserify", + "os": "react-native-os", + "path": "path-browserify", + "querystring": "querystring-es3", + "fs": "react-native-level-fs", + "_stream_transform": "readable-stream/transform", + "_stream_readable": "readable-stream/readable", + "_stream_writable": "readable-stream/writable", + "_stream_duplex": "readable-stream/duplex", + "_stream_passthrough": "readable-stream/passthrough", + "dgram": "react-native-udp", + "stream": "stream-browserify", + "timers": "timers-browserify", + "tty": "tty-browserify", + "vm": "vm-browserify" + }, + "browser": { + "zlib": "browserify-zlib", + "console": "console-browserify", + "constants": "constants-browserify", + "crypto": "react-native-crypto", + "dns": "dns.js", + "net": "react-native-tcp", + "domain": "domain-browser", + "http": "react-native-http", + "https": "https-browserify", + "os": "react-native-os", + "path": "path-browserify", + "querystring": "querystring-es3", + "fs": "react-native-level-fs", + "_stream_transform": "readable-stream/transform", + "_stream_readable": "readable-stream/readable", + "_stream_writable": "readable-stream/writable", + "_stream_duplex": "readable-stream/duplex", + "_stream_passthrough": "readable-stream/passthrough", + "dgram": "react-native-udp", + "stream": "stream-browserify", + "timers": "timers-browserify", + "tty": "tty-browserify", + "vm": "vm-browserify" } } diff --git a/src/CCInput.js b/src/CCInput.js index e67b83c2..1e421b34 100644 --- a/src/CCInput.js +++ b/src/CCInput.js @@ -69,7 +69,9 @@ export default class CCInput extends Component { } _onFocus() { - this.props.onFocus(this.props.field); + setTimeout(() => { + this.props.onFocus(this.props.field); + }, 50); } _onChange(value) { @@ -82,33 +84,31 @@ export default class CCInput extends Component { validColor, invalidColor, placeholderColor } = this.props; return ( - - - { !!label && {label}} - {this.input = ref}} - autoFocus={(field == "number")} - keyboardType={keyboardType} - returnKeyType={keyboardType === 'numbers-and-punctuation' ? "done" : "default"} - autoCapitalise="words" - autoCorrect={false} - style={[ - s.baseInputStyle, - inputStyle, - ((validColor && status === "valid") ? { color: validColor } : - (status !== "valid" && field === "number" && (value.length > 0 && value.match(/\d/g).length >= 16)) ? { color: invalidColor } : - (invalidColor && status === "invalid") ? { color: invalidColor } : - {}), - ]} - underlineColorAndroid={"transparent"} - placeholderTextColor={placeholderColor} - placeholder={placeholder} - value={value} - onFocus={this._onFocus.bind(this)} - onChangeText={this._onChange.bind(this)} - onSubmitEditing={(event) => {this.props._handleSubmit();}}/> - - + + { !!label && {label}} + {this.input = ref}} + autoFocus={(field == "number")} + keyboardType={keyboardType} + returnKeyType={keyboardType === 'numbers-and-punctuation' ? "done" : "default"} + autoCapitalise="words" + autoCorrect={false} + style={[ + s.baseInputStyle, + inputStyle, + ((validColor && status === "valid") ? { color: validColor } : + (status !== "valid" && field === "number" && (value.length > 0 && value.match(/\d/g).length >= 16)) ? { color: invalidColor } : + (invalidColor && status === "invalid") ? { color: invalidColor } : + {}), + ]} + underlineColorAndroid={"transparent"} + placeholderTextColor={placeholderColor} + placeholder={placeholder} + value={value} + onFocus={this._onFocus.bind(this)} + onChangeText={this._onChange.bind(this)} + onSubmitEditing={(event) => {this.props._handleSubmit();}}/> + ); } } diff --git a/src/CreditCardInput.js b/src/CreditCardInput.js index fac5b406..80108180 100644 --- a/src/CreditCardInput.js +++ b/src/CreditCardInput.js @@ -1,178 +1,137 @@ import React, { Component, PropTypes } from "react"; -import ReactNative, { - NativeModules, - View, - Text, - StyleSheet, - ScrollView, - Dimensions, -} from "react-native"; - -import CreditCard from "./CardView"; -import CCInput from "./CCInput"; -import { InjectedProps } from "./connectToState"; - -const s = StyleSheet.create({ - container: { - alignItems: "center", - }, - form: { - marginTop: 20, - }, - inputContainer: { - marginLeft: 20, - }, - inputLabel: { - fontWeight: "bold", - }, - input: { - height: 40, - }, -}); - -const CVC_INPUT_WIDTH = 70; -const EXPIRY_INPUT_WIDTH = CVC_INPUT_WIDTH; -const CARD_NUMBER_INPUT_WIDTH_OFFSET = 40; -const CARD_NUMBER_INPUT_WIDTH = Dimensions.get("window").width - EXPIRY_INPUT_WIDTH - CARD_NUMBER_INPUT_WIDTH_OFFSET; -const NAME_INPUT_WIDTH = CARD_NUMBER_INPUT_WIDTH; -const PREVIOUS_FIELD_OFFSET = 40; -const POSTAL_CODE_INPUT_WIDTH = 120; - -/* eslint react/prop-types: 0 */ // https://github.com/yannickcr/eslint-plugin-react/issues/106 +import CCFieldFormatter from "./CCFieldFormatter"; +import CCFieldValidator from "./CCFieldValidator"; +import compact from "lodash.compact"; + +import FullCreditCardInput from "./FullCreditCardInput"; +import LiteCreditCardInput from "./LiteCreditCardInput"; + +export const SharedProps = { + focused: PropTypes.string, + values: PropTypes.object.isRequired, + status: PropTypes.object.isRequired, + onFocus: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onBecomeEmpty: PropTypes.func.isRequired, + onBecomeValid: PropTypes.func.isRequired, + requiresName: PropTypes.bool, + requiresCVC: PropTypes.bool, + requiresPostalCode: PropTypes.bool, +}; + export default class CreditCardInput extends Component { static propTypes = { - ...InjectedProps, - labels: PropTypes.object, - placeholders: PropTypes.object, - - labelStyle: Text.propTypes.style, - inputStyle: Text.propTypes.style, - inputContainerStyle: View.propTypes.style, - - validColor: PropTypes.string, - invalidColor: PropTypes.string, - placeholderColor: PropTypes.string, - - cardImageFront: PropTypes.number, - cardImageBack: PropTypes.number, - cardScale: PropTypes.number, - cardFontFamily: PropTypes.string, - cardBrandIcons: PropTypes.object, - - allowScroll: PropTypes.bool, + autoFocus: PropTypes.bool, + onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func, + requiresName: PropTypes.bool, + requiresCVC: PropTypes.bool, + requiresPostalCode: PropTypes.bool, + validatePostalCode: PropTypes.func, + isLite: PropTypes.bool }; static defaultProps = { - cardViewSize: {}, - labels: { - name: "CARDHOLDER'S NAME", - number: "CARD NUMBER", - expiry: "EXPIRY", - cvc: "CVC/CCV", - postalCode: "POSTAL CODE", - }, - placeholders: { - name: "Full Name", - number: "1234 5678 1234 5678", - expiry: "MM/YY", - cvc: "CVC", - postalCode: "34567", + autoFocus: false, + isLite: true, + onChange: () => {}, + onFocus: () => {}, + requiresName: false, + requiresCVC: true, + requiresPostalCode: true, + validatePostalCode: (postalCode = "") => { + return postalCode.length > 0 ? "valid" : "invalid"; }, - inputContainerStyle: { - borderBottomWidth: 1, - borderBottomColor: "black", - }, - validColor: "", - invalidColor: "red", - placeholderColor: "gray", - allowScroll: false, }; - componentDidMount = () => this._focus(this.props.focused); + constructor() { + super(); + this.state = { + focused: "number", + values: {}, + status: {}, + }; + } + + setValues(values) { + const newValues = { ...this.state.values, ...values }; + const displayedFields = this._displayedFields(); + const formattedValues = (new CCFieldFormatter(displayedFields)).formatValues(newValues); + const validation = (new CCFieldValidator(displayedFields, this.props.validatePostalCode)).validateValues(formattedValues); + const newState = { values: formattedValues, ...validation }; - componentWillReceiveProps = newProps => { - if (this.props.focused !== newProps.focused) this._focus(newProps.focused); - }; + this.setState(newState); + this.props.onChange(newState); + } - _focus = field => { - if (!field) return; + focus(field) { + console.log('CCDEBUG focus: ' + field); + if (this.state.focused == field) { + console.log('CCDEBUG already focused return'); + return; + } - const scrollResponder = this.refs.Form.getScrollResponder(); - const nodeHandle = ReactNative.findNodeHandle(this.refs[field]); + if (!field) { + field = 'number'; + } - NativeModules.UIManager.measureLayoutRelativeToParent(nodeHandle, - e => { throw e; }, - x => { - scrollResponder.scrollTo({ x: Math.max(x - PREVIOUS_FIELD_OFFSET, 0), animated: true }); - this.refs[field].focus(); - }); + this.setState({ focused: field }); } - _inputProps = field => { - const { - inputStyle, labelStyle, validColor, invalidColor, placeholderColor, - placeholders, labels, values, status, - onFocus, onChange, onBecomeEmpty, onBecomeValid, - } = this.props; - - return { - inputStyle: [s.input, inputStyle], - labelStyle: [s.inputLabel, labelStyle], - validColor, invalidColor, placeholderColor, - ref: field, field, - - label: labels[field], - placeholder: placeholders[field], - value: values[field], - status: status[field], - - onFocus, onChange, onBecomeEmpty, onBecomeValid, - }; - }; + _displayedFields() { + const { requiresName, requiresCVC, requiresPostalCode } = this.props; + return compact([ + "number", + "expiry", + requiresCVC ? "cvc" : null, + requiresName ? "name" : null, + requiresPostalCode ? "postalCode" : null, + ]); + } - render() { - const { - cardImageFront, cardImageBack, inputContainerStyle, - values: { number, expiry, cvc, name, type }, focused, - allowScroll, requiresName, requiresCVC, requiresPostalCode, - cardScale, cardFontFamily, cardBrandIcons, - } = this.props; + _focusPreviousField(field) { + const displayedFields = this._displayedFields(); + const fieldIndex = displayedFields.indexOf(field); + const previousField = displayedFields[fieldIndex - 1]; + if (previousField) { + this.focus(previousField); + } + } + + _focusNextField(field) { + if (field === "name") { + return; + } + // Should not focus to the next field after name (e.g. when requiresName & requiresPostalCode are true + // because we can't determine if the user has completed their name or not) + + const displayedFields = this._displayedFields(); + const fieldIndex = displayedFields.indexOf(field); + const nextField = displayedFields[fieldIndex + 1]; + if (nextField) { + this.focus(nextField); + } + } + + _change(field, value) { + this.setValues({ [field]: value }); + } + _onFocus(field) { + this.focus(field); + this.props.onFocus(field); + } + + render() { + const CreditCardElement = this.props.isLite ? LiteCreditCardInput : FullCreditCardInput; return ( - - - - - - { requiresCVC && - } - { requiresName && - } - { requiresPostalCode && - } - - + ); } } diff --git a/src/FullCreditCardInput.js b/src/FullCreditCardInput.js new file mode 100644 index 00000000..e89cb01c --- /dev/null +++ b/src/FullCreditCardInput.js @@ -0,0 +1,178 @@ +import React, { Component, PropTypes } from "react"; +import ReactNative, { + NativeModules, + View, + Text, + StyleSheet, + ScrollView, + Dimensions, +} from "react-native"; + +import CreditCard from "./CardView"; +import CCInput from "./CCInput"; +import { SharedProps } from "./CreditCardInput"; + +const s = StyleSheet.create({ + container: { + alignItems: "center", + }, + form: { + marginTop: 20, + }, + inputContainer: { + marginLeft: 20, + }, + inputLabel: { + fontWeight: "bold", + }, + input: { + height: 40, + }, +}); + +const CVC_INPUT_WIDTH = 70; +const EXPIRY_INPUT_WIDTH = CVC_INPUT_WIDTH; +const CARD_NUMBER_INPUT_WIDTH_OFFSET = 40; +const CARD_NUMBER_INPUT_WIDTH = Dimensions.get("window").width - EXPIRY_INPUT_WIDTH - CARD_NUMBER_INPUT_WIDTH_OFFSET; +const NAME_INPUT_WIDTH = CARD_NUMBER_INPUT_WIDTH; +const PREVIOUS_FIELD_OFFSET = 40; +const POSTAL_CODE_INPUT_WIDTH = 120; + +/* eslint react/prop-types: 0 */ // https://github.com/yannickcr/eslint-plugin-react/issues/106 +export default class FullCreditCardInput extends Component { + static propTypes = { + ...SharedProps, + labels: PropTypes.object, + placeholders: PropTypes.object, + + labelStyle: Text.propTypes.style, + inputStyle: Text.propTypes.style, + inputContainerStyle: View.propTypes.style, + + validColor: PropTypes.string, + invalidColor: PropTypes.string, + placeholderColor: PropTypes.string, + + cardImageFront: PropTypes.number, + cardImageBack: PropTypes.number, + cardScale: PropTypes.number, + cardFontFamily: PropTypes.string, + cardBrandIcons: PropTypes.object, + + allowScroll: PropTypes.bool, + }; + + static defaultProps = { + cardViewSize: {}, + labels: { + name: "CARDHOLDER'S NAME", + number: "CARD NUMBER", + expiry: "EXPIRY", + cvc: "CVC/CCV", + postalCode: "POSTAL CODE", + }, + placeholders: { + name: "Full Name", + number: "1234 5678 1234 5678", + expiry: "MM/YY", + cvc: "CVC", + postalCode: "34567", + }, + inputContainerStyle: { + borderBottomWidth: 1, + borderBottomColor: "black", + }, + validColor: "", + invalidColor: "red", + placeholderColor: "gray", + allowScroll: false, + }; + + componentDidMount = () => this._focus(this.props.focused); + + componentWillReceiveProps = newProps => { + if (this.props.focused !== newProps.focused) this._focus(newProps.focused); + }; + + _focus = field => { + if (!field) return; + + const scrollResponder = this.refs.Form.getScrollResponder(); + const nodeHandle = ReactNative.findNodeHandle(this.refs[field]); + + NativeModules.UIManager.measureLayoutRelativeToParent(nodeHandle, + e => { throw e; }, + x => { + scrollResponder.scrollTo({ x: Math.max(x - PREVIOUS_FIELD_OFFSET, 0), animated: true }); + this.refs[field].focus(); + }); + } + + _inputProps = field => { + const { + inputStyle, labelStyle, validColor, invalidColor, placeholderColor, + placeholders, labels, values, status, + onFocus, onChange, onBecomeEmpty, onBecomeValid, + } = this.props; + + return { + inputStyle: [s.input, inputStyle], + labelStyle: [s.inputLabel, labelStyle], + validColor, invalidColor, placeholderColor, + ref: field, field, + + label: labels[field], + placeholder: placeholders[field], + value: values[field], + status: status[field], + + onFocus, onChange, onBecomeEmpty, onBecomeValid, + }; + }; + + render() { + const { + cardImageFront, cardImageBack, inputContainerStyle, + values: { number, expiry, cvc, name, type }, focused, + allowScroll, requiresName, requiresCVC, requiresPostalCode, + cardScale, cardFontFamily, cardBrandIcons, + } = this.props; + + return ( + + + + + + { requiresCVC && + } + { requiresName && + } + { requiresPostalCode && + } + + + ); + } +} diff --git a/src/LiteCreditCardInput.js b/src/LiteCreditCardInput.js index e4b56259..ea50a8e4 100644 --- a/src/LiteCreditCardInput.js +++ b/src/LiteCreditCardInput.js @@ -5,12 +5,13 @@ import { StyleSheet, Image, TouchableOpacity, + LayoutAnimation, findNodeHandle } from "react-native"; import Icons from "./Icons"; import CCInput from "./CCInput"; -import { InjectedProps } from "./connectToState"; +import { SharedProps } from "./CreditCardInput"; import _ from 'lodash'; const INFINITE_WIDTH = 1000; @@ -18,7 +19,7 @@ const INFINITE_WIDTH = 1000; /* eslint react/prop-types: 0 */ // https://github.com/yannickcr/eslint-plugin-react/issues/106 export default class LiteCreditCardInput extends Component { static propTypes = { - ...InjectedProps, + ...SharedProps, placeholders: PropTypes.object, inputStyle: Text.propTypes.style, @@ -46,48 +47,43 @@ export default class LiteCreditCardInput extends Component { result : result.concat(key); }, []); - console.error('CCDEBUG: props changed: ' + JSON.stringify(diffProps)); + console.log('CCDEBUG: props changed: ' + JSON.stringify(diffProps)); let diffState = _.reduce(nextState, (result, value, key) => { return _.isEqual(value, this.state[key]) ? result : result.concat(key); }, []); - console.error('CCDEBUG: state changed: ' + JSON.stringify(diffState)); + console.log('CCDEBUG: state changed: ' + JSON.stringify(diffState)); return diffProps.length > 0 || diffState.length > 0; } componentWillReceiveProps (newProps) { if (this.props.focused !== newProps.focused) { - console.error('CCDEBUG: props changed new focus, call focus'); + console.log('CCDEBUG: props changed new focus, call focus'); this._focus(newProps.focused); } - - let diff = _.reduce(newProps, (result, value, key) => { - return _.isEqual(value, this.props[key]) ? - result : result.concat(key); - }, []); - console.error('CCDEBUG: props changed: ' + JSON.stringify(diff)); } _focusNumber() { - console.error('CCDEBUG focus number'); + console.log('CCDEBUG focus number'); this._focus("number"); } _focusExpiry() { - console.error('CCDEBUG focus expiry'); + console.log('CCDEBUG focus expiry'); this._focus("expiry"); }; _focus(field) { - console.error('CCDEBUG lite input focus: ' + field); + console.log('CCDEBUG lite input focus: ' + field); if (!field || this.props.focused == field) { return; } - console.error('CCDEBUG lite input focus: ' + this[field]); + console.log('CCDEBUG lite input focus: ' + this[field]); this[field].focus(); + LayoutAnimation.easeInEaseOut(); } _handleSubmit() { @@ -133,9 +129,9 @@ export default class LiteCreditCardInput extends Component { render() { let { focused, values: { number }, inputStyle, status: { number: numberStatus } } = this.props; let last4Value = numberStatus == "valid" ? number.substr(number.length - 4, 4) : ""; - console.error('CCDEBUG: destructured focused: ' + focused); + console.log('CCDEBUG: destructured focused: ' + focused); - console.error('CCDEBUG: props focused: ' + this.props.focused); + console.log('CCDEBUG: props focused: ' + this.props.focused); let showRightPart = !focused || (focused != "number"); let creditNumberStyle = []; let infoStyle = []; @@ -145,34 +141,32 @@ export default class LiteCreditCardInput extends Component { creditNumberStyle = [s.hiddenViewStyle, s.leftPart]; infoStyle = [s.expandedViewStyle, s.rightPart]; onPressFunc = this._focusNumber.bind(this); - console.error('CCDEBUG: showRightPart true'); + console.log('CCDEBUG: showRightPart true'); } else { - console.error('CCDEBUG: showRightPart false'); - creditNumberStyle = [s.expandedViewStyle, s.leftPart]; + console.log('CCDEBUG: showRightPart false'); + creditNumberStyle = [s.expandedViewStyle, s.leftPart, {marginLeft: 20}]; infoStyle = [s.hiddenViewStyle, s.rightPart]; onPressFunc = this._focusExpiry.bind(this); } - console.error('CCDEBUG number style: ' + JSON.stringify(StyleSheet.flatten(creditNumberStyle))); - console.error('CCDEBUG info style: ' + JSON.stringify(StyleSheet.flatten(infoStyle))); + console.log('CCDEBUG number style: ' + JSON.stringify(StyleSheet.flatten(creditNumberStyle))); + console.log('CCDEBUG info style: ' + JSON.stringify(StyleSheet.flatten(infoStyle))); return ( - - - + - @@ -218,7 +212,6 @@ const s = StyleSheet.create({ }, numberInput: { width: INFINITE_WIDTH, - marginLeft: 20, }, expiryInput: { width: 60,