Last active
July 29, 2019 21:10
-
-
Save osvaldoM/2172eccc1807a648afa00cf1431ff622 to your computer and use it in GitHub Desktop.
Snippet for andela challenge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> | |
<title>Mini App</title> | |
<style> | |
body { | |
margin: 0; | |
padding: 1em; | |
background-color: white; | |
} | |
[data-cart-info], | |
[data-credit-card] { | |
transform: scale(0.78); | |
margin-left: -3.4em; | |
} | |
[data-cc-info] input:focus, | |
[data-cc-digits] input:focus { | |
outline: none; | |
} | |
.mdc-card__primary-action, | |
.mdc-card__primary-action:hover { | |
cursor: auto; | |
padding: 20px; | |
min-height: inherit; | |
} | |
[data-credit-card] [data-card-type] { | |
transition: width 1.5s; | |
margin-left: calc(100% - 130px); | |
} | |
[data-credit-card].is-visa { | |
background: linear-gradient(135deg, #622774 0%, #c53364 100%); | |
} | |
[data-credit-card].is-mastercard { | |
background: linear-gradient(135deg, #65799b 0%, #5e2563 100%); | |
} | |
.is-visa [data-card-type], | |
.is-mastercard [data-card-type] { | |
width: auto; | |
} | |
input.is-invalid, | |
.is-invalid input { | |
text-decoration: line-through; | |
} | |
::placeholder { | |
color: #fff; | |
} | |
[data-cart-info] span { | |
display: inline-block; | |
vertical-align: middle; | |
} | |
.material-icons { | |
font-size: 150px | |
} | |
[data-credit-card] { | |
width: 435px; | |
min-height: 240px; | |
background-color: #5d6874; | |
border-radius: 10px | |
} | |
[data-card-type] { | |
display: block; | |
width: 120px; | |
height: 60px; | |
} | |
[data-cc-digits] { | |
margin-top: 2em; | |
} | |
[data-cc-digits] input { | |
color: white; | |
font-size: 2em; | |
line-height: 2em; | |
border: none; | |
background: transparent; | |
margin-right: 0.5em; | |
} | |
[data-cc-info] { | |
margin-top: 1em; | |
} | |
[data-cc-info] input { | |
color: white; | |
font-size: 1.2em; | |
border: none; | |
background: transparent; | |
} | |
[data-cc-info] .second-input { | |
padding-right: 10px; | |
float: right; | |
} | |
[data-pay-btn] { | |
position: fixed; | |
width: 90%; | |
border-style:solid; | |
border-width: 1px; | |
bottom: 20px; | |
} | |
</style> | |
</head> | |
<body> | |
<div data-cart-info> | |
<h4 class="mdc-typography--headline4"> | |
<span class="material-icons"> shopping_cart</span> | |
<span data-bill></span> | |
</h4> | |
</div> | |
<div data-credit-card class="mdc-card mdc-card--outlined"> | |
<div class="mdc-card__primary-action"> | |
<img data-card-type src="https://placehold.it/120x60.png?text=Card" /> | |
<div data-cc-digits> | |
<input size="4" type="text" placeholder="----"> | |
<input size="4" type="text" placeholder="----"> | |
<input size="4" type="text" placeholder="----"> | |
<input size="4" type="text" placeholder="----"> | |
</div> | |
<div data-cc-info> | |
<input size="20" type="text" placeholder="Name Surname"> | |
<input size="6" class="second-input" type="text" placeholder="MM/YY"> | |
</div> | |
</div> | |
</div> | |
<button class="mdc-button" data-pay-btn> | |
Pay & Checkout Now | |
</button> | |
<script> | |
const supportedCards = { | |
visa, mastercard | |
}; | |
const countries = [ | |
{ | |
code: "US", | |
currency: "USD", | |
country: 'United States' | |
}, | |
{ | |
code: "NG", | |
currency: "NGN", | |
country: 'Nigeria' | |
}, | |
{ | |
code: 'KE', | |
currency: 'KES', | |
country: 'Kenya' | |
}, | |
{ | |
code: 'UG', | |
currency: 'UGX', | |
country: 'Uganda' | |
}, | |
{ | |
code: 'RW', | |
currency: 'RWF', | |
country: 'Rwanda' | |
}, | |
{ | |
code: 'TZ', | |
currency: 'TZS', | |
country: 'Tanzania' | |
}, | |
{ | |
code: 'ZA', | |
currency: 'ZAR', | |
country: 'South Africa' | |
}, | |
{ | |
code: 'CM', | |
currency: 'XAF', | |
country: 'Cameroon' | |
}, | |
{ | |
code: 'GH', | |
currency: 'GHS', | |
country: 'Ghana' | |
} | |
]; | |
const appState = {}; | |
const formatAsMoney = (amount, buyerCountry) => { | |
const countryDetails = countries.find(({country}) => { | |
return country === buyerCountry; | |
}) || countries[0] | |
return amount.toLocaleString((`en-${countryDetails.code}`), { | |
style: 'currency', | |
currency: countryDetails.currency | |
}); | |
}; | |
const flagIfInvalid = (field, isValid) => { | |
if(isValid === true) { | |
field.classList.remove('is-invalid'); | |
field.classList.add('is-valid'); | |
} else { | |
field.classList.add('is-invalid'); | |
field.classList.remove('is-valid'); | |
} | |
}; | |
const expiryDateFormatIsValid = (target) => { | |
return /\d\d\/\d\d/.test(target); | |
} | |
const detectCardType = ({target}) => { | |
const $cardContainer = document.querySelector('[data-credit-card]'); | |
const $cardType = document.querySelector('[data-card-type]'); | |
let cardType = ''; | |
if(target.value.startsWith('4')) { | |
$cardContainer.classList.add('is-visa'); | |
$cardContainer.classList.remove('is-mastercard'); | |
$cardType.src = supportedCards.visa; | |
cardType = 'is-visa'; | |
} else { | |
$cardContainer.classList.remove('is-visa'); | |
$cardContainer.classList.add('is-mastercard'); | |
$cardType.src = supportedCards.mastercard; | |
cardType = 'is-mastercard'; | |
} | |
return cardType; | |
}; | |
const validateCardExpiryDate = ({target}) => { | |
const hasCorrectDateFormat = expiryDateFormatIsValid(target.value); | |
const isFutureDate = isInTheFuture(target.value); | |
const isValid = hasCorrectDateFormat && isFutureDate; | |
flagIfInvalid(target, isValid); | |
return isValid; | |
}; | |
const isInTheFuture = (dateString) => { | |
const currentDate = new Date(); | |
const currentCentury = currentDate.getFullYear() / 100 | 0 + ''; | |
const dateDetails = dateString.split('/'); | |
const targetMonthIndex = dateDetails[0]; | |
const targetYear = currentCentury + dateDetails[1]; | |
return currentDate < new Date(targetYear, targetMonthIndex, 1); | |
}; | |
const validateCardHolderName = ({target}) => { | |
const isValid = /^([a-zA-Z]{3,})\s([a-zA-Z]{3,})$/.test(target.value); | |
flagIfInvalid(target, isValid); | |
return isValid | |
}; | |
const validateWithLuhn = (digits) => { | |
if(digits.length > 16){ | |
return false; | |
} | |
let isValid = true; | |
const reversedDigits = [...digits].reverse(); | |
const lastDigit = reversedDigits[0]; | |
const doubledDigits = reversedDigits.map((digit, index) => { | |
if(index % 2 !== 0) { | |
return digit *2 | |
} | |
return digit; | |
}); | |
// if a doubled # is > 9, replace it with 9 subtracted from it | |
const replacedDoubleDigits = doubledDigits.map((digit, index) => { | |
if((index % 2 !== 0) && digit > 9) { | |
return digit - 9; | |
} | |
return digit; | |
}) | |
const sumOfDigits = replacedDoubleDigits.reduce((accumulator, digit) => accumulator + digit, 0); | |
isValid = sumOfDigits % 10 === 0; | |
return isValid; | |
} | |
const validateCardNumber = () => { | |
const $cardInputs = document.querySelectorAll('[data-cc-digits] input'); | |
const cardDigits = Array.from($cardInputs).reduce((accumulator, $input) => { | |
return accumulator.concat($input.value.split('')); | |
}, []); | |
const cardDigitsAsNumbers = cardDigits.map((digit) => { | |
return parseInt(digit,10); | |
}); | |
const isValidNumber = validateWithLuhn(cardDigitsAsNumbers); | |
const $digitsContainer = document.querySelector('[data-cc-digits]'); | |
if(isValidNumber) { | |
$digitsContainer.classList.remove('is-invalid'); | |
} | |
else { | |
$digitsContainer.classList.add('is-invalid'); | |
} | |
return isValidNumber; | |
}; | |
const uiCanInteract = () => { | |
document.querySelector('[data-cc-digits] input').addEventListener('blur', detectCardType); | |
document.querySelector('[data-cc-info] input').addEventListener('blur', validateCardHolderName); | |
document.querySelector('[data-cc-info] .second-input').addEventListener('blur', validateCardExpiryDate); | |
document.querySelector('[data-pay-btn]').addEventListener('click', validateCardNumber); | |
document.querySelector('[data-cc-digits] input').focus(); | |
}; | |
const startApp = () => { | |
fetchBill(); | |
}; | |
const displayCartTotal = ({results}) => { | |
const [data] = results; | |
const {itemsInCart, buyerCountry} = data; | |
appState.items = itemsInCart; | |
appState.country = buyerCountry; | |
appState.bill = itemsInCart.reduce((accumulator, {qty, price}) => { | |
return accumulator + (qty * price); | |
}, 0); | |
appState.billFormatted = formatAsMoney(appState.bill, appState.country); | |
$bill = document.querySelector('[data-bill]'); | |
$bill.textContent = appState.billFormatted; | |
uiCanInteract(); | |
} | |
const fetchBill = () => { | |
const api = 'https://randomapi.com/api/006b08a801d82d0c9824dcfdfdfa3b3c'; | |
fetch(api).then( res => { | |
return res.json(); | |
}) | |
.then(displayCartTotal) | |
.catch(error => { | |
console.log(error.message); | |
}); | |
} | |
startApp(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment