|
import React, { Component } from 'react' |
|
import { connect } from 'react-redux' |
|
|
|
import { updateNumber, updateCvc, updateExpMonth, updateExpYear } from 'actions/card' |
|
|
|
|
|
@connect(state => ({ |
|
number: state.card.number, |
|
cvc: state.card.cvc, |
|
expMonth: state.card.expMonth, |
|
expYear: state.card.expYear |
|
}) dispatch => ({ |
|
updateNumber: number => dispatch(updateNumber(number)), |
|
updateCvc: cvc => dispatch(updateCvc(cvc)), |
|
updateExpMonth: expMonth => dispatch(updateExpMonth(expMonth)), |
|
updateExpYear: expYear => dispatch(updateExpYear(expYear)) |
|
})) |
|
export default class ReactInterview extends Component { |
|
|
|
state = { |
|
number: this.props.number, |
|
cvc: this.props.cvc, |
|
expMonth: this.props.expMonth, |
|
expYear: this.props.expYear, |
|
|
|
isValidNumber: false, |
|
isValidCvc: false, |
|
isValidExpMonth: false, |
|
isValidExpYear: false, |
|
|
|
error: null |
|
} |
|
|
|
//Luhn algorithm identifier verification |
|
//MIT Licensed |
|
validateNumber () { |
|
let identifier = this.state.number |
|
let sum = 0 |
|
let alt = false |
|
let i = identifier.length-1 |
|
let num |
|
|
|
if (identifier.length < 13 || identifier.length > 19) return false |
|
|
|
while (i >= 0){ |
|
//get the next digit |
|
num = parseInt(identifier.charAt(i), 10); |
|
|
|
//if it's not a valid number, abort |
|
if (isNaN(num)) return false |
|
|
|
//if it's an alternate number... |
|
if (alt) { |
|
num *= 2; |
|
if (num > 9){ |
|
num = (num % 10) + 1; |
|
} |
|
} |
|
|
|
//flip the alternate bit |
|
alt = !alt; |
|
//add to the rest of the sum |
|
sum += num; |
|
//go to next digit |
|
i--; |
|
} |
|
//determine if it's valid |
|
let isValidNumber = (sum % 10 == 0) |
|
this.setState({ isValidNumber }) |
|
} |
|
|
|
validateCvc () { |
|
this.setState({ |
|
isValidCvc: this.state.cvc.length >= 3 |
|
}) |
|
} |
|
|
|
validateExpMonth () { |
|
const { expYear, expMonth } = this.state |
|
let isValidExpMonth |
|
if (expYear) { |
|
isValidExpMonth = !!expMonth && new Date().setFullYear(expYear, expMonth, 1) < new Date() |
|
} else { |
|
isValidExpMonth = !!expMonth |
|
} |
|
this.setState({ isValidExpMonth }) |
|
} |
|
|
|
validateExpYear () { |
|
this.setState({ |
|
isValidExpYear: !!this.state.expYear |
|
}) |
|
this.validateExpMonth() |
|
} |
|
|
|
validateAll () { |
|
this.validateNumber() |
|
this.validateCvc() |
|
this.validateExpMonth() |
|
this.validateExpYear() |
|
} |
|
|
|
updateCardNumber ({ target: { value: number } }) { |
|
this.setState({ number }) |
|
this.props.dispatch(updateNumber(number)) |
|
this.validateNumber() |
|
} |
|
|
|
updateCardCvc (e) { |
|
e.preventDefault() |
|
this.setState({ |
|
cvc: e.target.value |
|
}) |
|
this.props.dispatch(updateCvc(e.target.value)) |
|
this.validateCvc() |
|
} |
|
|
|
updateCardExpMonth ({ target: { value } }) { |
|
this.setState({ |
|
expMonth: value |
|
}) |
|
this.props.updateExpMonth(value) |
|
this.validateExpMonth() |
|
} |
|
|
|
updateCardExpYear ({ target: { value: expYear } }) { |
|
this.setState({ expYear }) |
|
this.props.updateExpYear(expYear) |
|
this.validateExpYear() |
|
} |
|
|
|
get cardIsValid () { |
|
const { isValidNumber, isValidCvc, isValidExpMonth, isValidExpYear } = this.state |
|
return isValidNumber && isValidCvc && isValidExpMonth && isValidExpYear |
|
} |
|
|
|
async submit () { |
|
const { number, cvc, expMonth, expYear } = this.state |
|
this.validateAll() |
|
if (this.cardIsValid) { |
|
// make api request to post the card details |
|
await ApiCalls.addCard(this.state) |
|
} else { |
|
this.setState({ |
|
error: 'Fix the fields highlighted in red.' |
|
}) |
|
} |
|
} |
|
|
|
render () { |
|
return ( |
|
<form> |
|
<div> |
|
<label for='cc-number'>Number</label> |
|
<input onChange={this.updateCardNumber.bind(this)} |
|
className={`input ${this.state.isValidNumber && s.validation}`} |
|
name='number' |
|
value={this.state.number} /> |
|
</div> |
|
<div> |
|
<label for='cc-exp-month'>Expiration Month</label> |
|
<select onFocus={::this.updateCardExpMonth} className={`input ${this.state.isValidExpMonth ? s.validation : ''}`}> |
|
<option value='Month'>Month</option> |
|
<option value='01'>1</option> |
|
<option value='02'>2</option> |
|
<option value='03'>3</option> |
|
<option value='04'>4</option> |
|
<option value='05'>5</option> |
|
<option value='06'>6</option> |
|
<option value='07'>7</option> |
|
<option value='08'>8</option> |
|
<option value='09'>9</option> |
|
<option value='10'>10</option> |
|
<option value='11'>11</option> |
|
<option value='12'>12</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label for='cc-exp-year'>Expiration Year</label> |
|
<select onBlur={() => this.updateCardExpYear} className={cx('input', this.state.isValidExpYear && s.validation)}> |
|
<option value='Year'>Year</option> |
|
<option value="16">2016</option> |
|
<option value="17">2017</option> |
|
<option value="18">2018</option> |
|
<option value="19">2019</option> |
|
<option value="20">2020</option> |
|
<option value="21">2021</option> |
|
<option value="22">2022</option> |
|
<option value="23">2023</option> |
|
<option value="24">2024</option> |
|
<option value="25">2025</option> |
|
<option value="26">2026</option> |
|
<option value="27">2027</option> |
|
<option value="28">2028</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label for='cc-cvc'>Cvc</label> |
|
<input onBlur={e => this.updateCardCvc(e)} |
|
value={this.state.cvc} |
|
id='cc-cvc' |
|
name='cvc' |
|
className={cx('input', { |
|
[s.validation]: this.state.isValidCvc |
|
})}/> |
|
</div> |
|
<button onClick={::this.submit}>Submit</button> |
|
</form> |
|
) |
|
} |
|
} |