- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
/**
* This component holds a full Credit Card form.
* When submitted, it will validate its input fields.
* If they all validate, then it calls a function passe in as a prop,
* `submitCallback` passed in by parent component.
* There is an optional `cancel` button - if you wish to use it, just pass
* `cancelButtonText`and `cancelButtonCallback` props and the form knows there's a cancel button
* to show. If you omit those props, no cancel button will appear.
*/
import styled from "@emotion/styled";
import Lion from "@rosetta/react-lion";
import PropTypes from "prop-types";
import React from "react";
//import { usStates } from "../common/util/us-states";
import { connect } from "react-redux";
import { errorsMap } from "./errors";
import { actions } from "./my-account-reducer";
const usStates = [
{ name: 'ALABAMA', abbreviation: 'AL'},
{ name: 'ALASKA', abbreviation: 'AK'},
{ name: 'AMERICAN SAMOA', abbreviation: 'AS'},
{ name: 'ARIZONA', abbreviation: 'AZ'},
{ name: 'ARKANSAS', abbreviation: 'AR'},
{ name: 'CALIFORNIA', abbreviation: 'CA'},
{ name: 'COLORADO', abbreviation: 'CO'},
{ name: 'CONNECTICUT', abbreviation: 'CT'},
{ name: 'DELAWARE', abbreviation: 'DE'},
{ name: 'DISTRICT OF COLUMBIA', abbreviation: 'DC'},
{ name: 'FEDERATED STATES OF MICRONESIA', abbreviation: 'FM'},
{ name: 'FLORIDA', abbreviation: 'FL'},
{ name: 'GEORGIA', abbreviation: 'GA'},
{ name: 'GUAM', abbreviation: 'GU'},
{ name: 'HAWAII', abbreviation: 'HI'},
{ name: 'IDAHO', abbreviation: 'ID'},
{ name: 'ILLINOIS', abbreviation: 'IL'},
{ name: 'INDIANA', abbreviation: 'IN'},
{ name: 'IOWA', abbreviation: 'IA'},
{ name: 'KANSAS', abbreviation: 'KS'},
{ name: 'KENTUCKY', abbreviation: 'KY'},
{ name: 'LOUISIANA', abbreviation: 'LA'},
{ name: 'MAINE', abbreviation: 'ME'},
{ name: 'MARSHALL ISLANDS', abbreviation: 'MH'},
{ name: 'MARYLAND', abbreviation: 'MD'},
{ name: 'MASSACHUSETTS', abbreviation: 'MA'},
{ name: 'MICHIGAN', abbreviation: 'MI'},
{ name: 'MINNESOTA', abbreviation: 'MN'},
{ name: 'MISSISSIPPI', abbreviation: 'MS'},
{ name: 'MISSOURI', abbreviation: 'MO'},
{ name: 'MONTANA', abbreviation: 'MT'},
{ name: 'NEBRASKA', abbreviation: 'NE'},
{ name: 'NEVADA', abbreviation: 'NV'},
{ name: 'NEW HAMPSHIRE', abbreviation: 'NH'},
{ name: 'NEW JERSEY', abbreviation: 'NJ'},
{ name: 'NEW MEXICO', abbreviation: 'NM'},
{ name: 'NEW YORK', abbreviation: 'NY'},
{ name: 'NORTH CAROLINA', abbreviation: 'NC'},
{ name: 'NORTH DAKOTA', abbreviation: 'ND'},
{ name: 'NORTHERN MARIANA ISLANDS', abbreviation: 'MP'},
{ name: 'OHIO', abbreviation: 'OH'},
{ name: 'OKLAHOMA', abbreviation: 'OK'},
{ name: 'OREGON', abbreviation: 'OR'},
{ name: 'PALAU', abbreviation: 'PW'},
{ name: 'PENNSYLVANIA', abbreviation: 'PA'},
{ name: 'PUERTO RICO', abbreviation: 'PR'},
{ name: 'RHODE ISLAND', abbreviation: 'RI'},
{ name: 'SOUTH CAROLINA', abbreviation: 'SC'},
{ name: 'SOUTH DAKOTA', abbreviation: 'SD'},
{ name: 'TENNESSEE', abbreviation: 'TN'},
{ name: 'TEXAS', abbreviation: 'TX'},
{ name: 'UTAH', abbreviation: 'UT'},
{ name: 'VERMONT', abbreviation: 'VT'},
{ name: 'VIRGIN ISLANDS', abbreviation: 'VI'},
{ name: 'VIRGINIA', abbreviation: 'VA'},
{ name: 'WASHINGTON', abbreviation: 'WA'},
{ name: 'WEST VIRGINIA', abbreviation: 'WV'},
{ name: 'WISCONSIN', abbreviation: 'WI'},
{ name: 'WYOMING', abbreviation: 'WY' }
];
const Form = styled.form({
"& > div": {
margin: "20px 0",
fontFamily: "helvetica",
color: "#262626",
fontSize: "20px",
textAlign: "left",
lineHeight: "23px",
"&::last-of-type": {
marginBottom: 0,
}
},
label: {
width: "100%",
},
"label > div": {
display: "block",
fontWeight: 700,
marginTop: "4px",
marginBottom: "9px",
},
"input, select": {
display: "block",
width: "100%",
padding: "13px 6px",
borderRadius: "3px",
border: "1px solid #999",
boxSizing: "border-box",
fontSize: "20px;"
},
select: {
//padding: "12px 5px",
//textAlign: "center"
},
".wrap-row": {
margin: "15px 0",
display: "flex",
"& > label": {
marginRight: "21px",
"&:last-of-type": {
marginRight: 0,
}
}
},
".expiration > div": {
display: "flex",
select: {
width: "100px"
}
},
".slash-divider": {
display: "flex",
alignItems: "center",
padding: "0 12px",
fontWeight: 100,
fontSize: "18px"
},
"input[type='submit'], input[type='button']": {
background: "#02B1FF",
color: "#fff",
border: "1px solid transparent",
borderRadius: "10px",
padding: "15px 0",
fontSize: "17px",
textTransform: "uppercase",
"&:disabled": {
background: '#ccc'
},
"&.cancel": {
background: "#999",
color: "#fff",
border: "1px solid #999",
marginLeft: "10px"
}
},
".support-info": {
fontSize: "16px",
lineHeight: "24px",
textAlign: "center",
color: "#262626",
fontWeight: 400,
maxWidth: "450px",
borderTop: "1px solid #e1e1e1",
paddingTop: "21px",
marginTop: "24px"
},
".form-error": {
background: "#ffbeca",
border: "3px solid #e25454",
padding: "5px",
fontSize: "16px",
lineHeight: "23px",
},
".cvv-container": {
display: "flex",
alignItems: "center",
"svg": {
width: "39px",
height: "39px",
marginLeft: "8px"
}
}
})
// Make sure mo/yr is not in past
function validateExpirationDate(dropdownMonth, dropdownYear) {
let ret = true
if (!dropdownMonth && !dropdownYear) {
ret = {msg: "Expiration month and year cannot be blank."}
} else if(!dropdownMonth) {
ret = {msg: "Expiration month cannot be blank."}
} else if (!dropdownYear) {
ret = {msg: "Expiration year cannot be blank."}
}
const currentMonth = new Date().getMonth() + 1
const currentYear = parseInt(new Date().getFullYear().toString().slice(-2))
if (dropdownMonth < currentMonth && dropdownYear === currentYear) {
ret = {msg: "Expiration date cannot be in the past."}
}
return ret
}
// For Credit Card expiration year dropdown
const expirationYears = [""]
const currentYear = new Date().getFullYear()
for (let year = currentYear; year < currentYear + 15; year++) {
expirationYears.push({fullYear: year, shortYear: year.toString().slice(2)})
}
export class CreditCardForm extends React.Component {
constructor(props) {
super(props);
this.props = props
this.state = {
maskedCreditCardNumber: null,
cardName: this.props.cardName || "",
cardBillingAddress: "",
cardBillingAddress2: "",
cardNumber: "",
cardState: "",
cardZip: "",
cardCity: "",
cardExpirationMonth: "",
cardExpirationYear: "",
cardSecurityCode: "",
country: "US",
isFetching: false,
errors: [],
submitButtonText: "SUBMIT",
cancelButtonText: "CANCEL",
submitCallback: null,
}
}
// If an `error` prop is passed, add it to `errors` state
componentDidUpdate(prevProps) {
// Check for prevProps to avoid maximum update depth error
if (this.props.error && (this.props.error !== prevProps.error)) {
this.setState({errors: [this.props.error]})
}
}
handleOnBlur(e) {
if (e.target.value === "" && this.state.maskedCreditCard) {
this.setState({ cardNumber: this.state.maskedCreditCard});
}
}
handleOnFocus(e) {
if (e.target.value.indexOf('*') !== -1) {
// Store this value. If user focuses out without changes, we
// restore this value in `handleOnBlur`
this.setState({
maskedCreditCardNumber: e.target.value,
cardNumber: "",
});
}
}
handleOnSubmit = (e) => {
e.preventDefault()
console.log(this.state)
// Number validation
if (
!this.state.cardName
|| this.state.cardName.trim() > 100
|| this.state.cardName.trim() < 2
|| !/^([a-z]+(-|\.| )*)+$/i.test(this.state.cardName))
{
this.state.errors.push("Name is invalid.")
}
// Card number validation
if (
!(this.state.cardNumber.length >= 15
&& this.state.cardNumber.length <= 16)
|| /\*/.test(this.state.cardNumber))
{
this.state.errors.push(errorsMap["InvalidCCNumber"])
}
// Zip code validation
if (!this.state.cardZip || this.state.cardZip.length !== 5 ) {
this.state.errors.push("Zip code is invalid.")
}
// Expiration date can't be in past
const isExpDateValid = validateExpirationDate(
parseInt(this.state.cardExpirationMonth),
parseInt(this.state.cardExpirationYear)
)
if (isExpDateValid !== true) {
this.state.errors.push(isExpDateValid.msg)
}
// Security code validation - 3 or 4 digits
if (
!this.state.cardSecurityCode
|| !(this.state.cardSecurityCode.length >= 3
&& this.state.cardSecurityCode.length <= 4))
{
this.state.errors.push("Security code is invalid.")
}
// Validation passed. Make API calls on parent component
if (this.state.errors.length) {
return
}
// No errors. Call parent's handle function
this.setState({errors: []})
this.props.submitCallback(this.state)
}
render() {
console.log('****')
console.log(this.state)
const creditCard = this.props.creditCard
const dispatch = this.props.dispatch
return(
<Form onSubmit={this.handleOnSubmit}>
{/* Full Name */}
<div>
<label>
<Lion.div lionkey="full_name" />
<input
type="text"
minLength="2"
maxLength="100"
defaultValue={this.props.cardName}
onChange={e => dispatch(actions.updateCreditCard({
...creditCard,
name: e.target.value
}))}
/>
</label>
</div>
<div className="wrap-row">
<input type="submit" value={this.props.submitButtonText} disabled={this.state.isFetching} />
<input type="button" className="cancel" value="cancel" onClick={this.props.cancelButtonCallback} />
</div>
{/*
Possible anti pattern here, but not only will we display local/validation errors,
we'll also display any XHR errors in the redux store - this is because the parent component
is making API calls, and those calls will store errors in the redux store.
*/}
{ this.state.errors.length > 0 &&
<div className="form-error">
{ this.state.errors.map((e, i) =>
<Lion.div key={i} lionkey={e} />
)}
</div>
}
</Form>
)
};
}
CreditCardForm.propTypes = {
/* the text string value of the submit button */
submitButtonText: PropTypes.string,
/* text string for cancel button. NOTE: if not passed, button will not show */
cancelButtonText: PropTypes.string,
/* Function defined in parent component, called if input validations pass. */
submitCallback: PropTypes.func,
/* Function defined in parent component, called when cancel button is clicked. */
cancelButtonCallback: PropTypes.func,
};
const mapStateToProps = (state) => {
return {
account: state.myAccount.account,
creditCard: state.myAccount.creditCard,
};
};
export default connect(mapStateToProps)(CreditCardForm);