Created
August 8, 2019 01:20
-
-
Save osvaldoM/9a8e907c7ffe83355aa35d8991559f5b to your computer and use it in GitHub Desktop.
latest 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; | |
} | |
/* Add Your CSS From Here */ | |
[data-cart-info] span { | |
display: inline-block; | |
vertical-align: middle; | |
} | |
.material-icons { | |
font-size: 150px; | |
} | |
[data-credit-card] { | |
width: 435px; | |
min-height: 240px; | |
border-radius: 10px; | |
background-color: #5d6874; | |
} | |
[data-card-type] { | |
display: block; | |
width: 120px; | |
height: 60px; | |
} | |
[data-cc-digits] { | |
margin-top: 2em; | |
display: flex; | |
} | |
[data-cc-digits] input { | |
color: white; | |
font-size: 2em; | |
line-height: 2em; | |
border: none; | |
background: none; | |
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] input:nth-child(2) { | |
padding-right: 10px; | |
float: right; | |
} | |
[data-pay-btn] { | |
position: fixed; | |
width: 90%; | |
border: 1px solid; | |
bottom: 20px; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- your HTML goes here --> | |
<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" type="text" placeholder="MM/YY"> | |
</div> | |
</div> | |
</div> | |
<button class="mdc-button" data-pay-btn> Pay Now </button> | |
<script> | |
const supportedCards = { | |
visa, mastercard | |
}; | |
const countries = [ | |
{ | |
code: "US", | |
currency: "USD", | |
currencyName: '', | |
country: 'United States' | |
}, | |
{ | |
code: "NG", | |
currency: "NGN", | |
currencyName: '', | |
country: 'Nigeria' | |
}, | |
{ | |
code: 'KE', | |
currency: 'KES', | |
currencyName: '', | |
country: 'Kenya' | |
}, | |
{ | |
code: 'UG', | |
currency: 'UGX', | |
currencyName: '', | |
country: 'Uganda' | |
}, | |
{ | |
code: 'RW', | |
currency: 'RWF', | |
currencyName: '', | |
country: 'Rwanda' | |
}, | |
{ | |
code: 'TZ', | |
currency: 'TZS', | |
currencyName: '', | |
country: 'Tanzania' | |
}, | |
{ | |
code: 'ZA', | |
currency: 'ZAR', | |
currencyName: '', | |
country: 'South Africa' | |
}, | |
{ | |
code: 'CM', | |
currency: 'XAF', | |
currencyName: '', | |
country: 'Cameroon' | |
}, | |
{ | |
code: 'GH', | |
currency: 'GHS', | |
currencyName: '', | |
country: 'Ghana' | |
} | |
]; | |
const billHype = () => { | |
const billDisplay = document.querySelector('.mdc-typography--headline4'); | |
if (!billDisplay) return; | |
billDisplay.addEventListener('click', () => { | |
const billSpan = document.querySelector("[data-bill]"); | |
if (billSpan && | |
appState.bill && | |
appState.billFormatted && | |
appState.billFormatted === billSpan.textContent) { | |
window.speechSynthesis.speak( | |
new SpeechSynthesisUtterance(appState.billFormatted) | |
); | |
} | |
}); | |
}; | |
const appState = {}; | |
const formatAsMoney = (amount, buyerCountry) => { | |
const countryDetails = countries.find(({country}) => country == buyerCountry) || country[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 = field => /\d?\d\/\d\d/.test(field); | |
const detectCardType = (first4Digits) => { | |
const $cardContainer = document.querySelector('[data-credit-card]') | |
const $cardType = document.querySelector('[data-card-type]'); | |
let cardType = ''; | |
if(first4Digits[0] == '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'; | |
//debugger; | |
} | |
return cardType; | |
}; | |
const isInTheFuture = datestring => { | |
const currentDate = new Date(); | |
const currentCentury = parseInt(currentDate.getFullYear() / 100).toString(); | |
const dateParts = datestring.split('/'); | |
const targetMonthIndex = dateParts[0]; | |
const targetYear = currentCentury + dateParts[1]; | |
return currentDate < new Date(targetYear, targetMonthIndex, 1); | |
} | |
const validateCardExpiryDate = () => { | |
const expiryDateInput = document.querySelector('[data-cc-info] input:last-child'); | |
const hasValidDateFormat = expiryDateFormatIsValid(expiryDateInput.value); | |
const isFutureDate = isInTheFuture(expiryDateInput.value); | |
const isValidDate = hasValidDateFormat && isFutureDate; | |
flagIfInvalid(expiryDateInput, isValidDate); | |
return isValidDate; | |
} | |
const validateCardHolderName = () => { | |
const nameInput = document.querySelector('[data-cc-info] input'); | |
const isValidName = /^([a-zA-Z]{3,})\s([a-zA-Z]{3,})$/.test(nameInput.value); | |
flagIfInvalid(nameInput, isValidName); | |
return isValidName; | |
}; | |
const smartInput = (event, fieldIndex, fields) => { | |
const controlKeys = [ | |
'Shift', | |
'Tab', | |
'Backspace', | |
'Delete', | |
'ArrowLeft', | |
'ArrowRight', | |
'ArrowUp', | |
'ArrowDown' | |
] | |
const isControlKey = controlKeys.includes(event.key); | |
if(!isControlKey) { | |
if(fieldIndex <=3 && /^\d{0,4}$/.test(event.key)) { | |
if(appState.cardDigits[fieldIndex] === undefined) { | |
appState.cardDigits[fieldIndex] = []; | |
} | |
let {value, selectionStart} = event.target; | |
if(appState.cardDigits[fieldIndex].length <= 4) { | |
const $field = fields[fieldIndex]; | |
appState.cardDigits[fieldIndex][selectionStart] = (parseInt(event.key, 10)); | |
setTimeout(() => { | |
$field.value = '#'.repeat(appState.cardDigits[fieldIndex].length); | |
if(fieldIndex === 0) { | |
// detectCardType(appState.cardDigits[fieldIndex]); | |
} | |
}, 500); | |
if(fieldIndex === 0) { | |
//debugger; | |
detectCardType(appState.cardDigits[fieldIndex]); | |
} | |
smartCursor(event, fieldIndex, fields); | |
} else { | |
event.preventDefault(); | |
return; | |
} | |
//smartCursor(event, fieldIndex, fields); | |
} else { | |
event.preventDefault(); | |
return; | |
} | |
} | |
}; | |
const enableSmartTyping = () => { | |
const $inputs = document.querySelectorAll('input'); | |
Array.from($inputs).forEach((field, index, fields) => { | |
field.addEventListener('keydown', event => smartInput(event, index, fields)); | |
}); | |
}; | |
const validateCardNumber = () => { | |
}; | |
const validatePayment = (event) => { | |
validateCardNumber(event); | |
validateCardHolderName(event); | |
validateCardExpiryDate(event); | |
}; | |
const smartCursor = (event, fieldIndex, fields) => { | |
field = fields[fieldIndex]; | |
fieldSize = parseInt(field.getAttribute('size')); | |
if((field.value.length >= fieldSize) && fieldIndex+1 <= fields.length){ | |
fields[fieldIndex].focus(); | |
} | |
}; | |
const acceptCardNumbers = (event, fieldIndex) => { | |
}; | |
const uiCanInteract = () => { | |
document.querySelector('[data-pay-btn]').addEventListener('click', validatePayment); | |
document.querySelector('[data-cc-digits] input').focus(); | |
billHype(); | |
enableSmartTyping(); | |
}; | |
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); | |
const $bill = document.querySelector('[data-bill]'); | |
$bill.textContent = appState.billFormatted; | |
appState.cardDigits = []; | |
uiCanInteract(); | |
}; | |
const fetchBill = () => { | |
const apiHost = 'https://randomapi.com/api'; | |
const apiKey = '006b08a801d82d0c9824dcfdfdfa3b3c'; | |
const apiEndpoint = `${apiHost}/${apiKey}`; | |
fetch(apiEndpoint).then( res => res.json()) | |
.then(displayCartTotal) | |
.catch( error => console.error(error.message)); | |
}; | |
const startApp = () => { | |
fetchBill(); | |
}; | |
startApp(); | |
const last = a => a[a.length -1]; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment