Hi, My name is Mutangana Edgar. I am writing an app but all the code seems correct to me but to when I am trying to pass the challenge it is telling me this message (“You are not correctly calculating the user’s total bill. See instructions and review your code”). So let me tell you the story well.
I want to get into andela’s program of becoming a software engineer and this is the link for more information (“https://andela.com/fellowship/apply/”). So before andela accepts you in their program the give you many challenges with deadlines to go through. And the first challenge which I am currently doing right now is divided into four challenges; each challenge has its own instructions and you can not proceed to the next challenge unless you pass the current one. Here are the instructions of the challenges so far. (I have finished challenge 1, challenge 2 but I am stuck on challenge 3 and I don’t know why). But when you first start with challenge one they give you the boilerplate to start with.
Challenge 1 instructions:
Build & Style The UI
Step 1
- Style the BODY element with a white background color.
- Within the BODY tag, create a DIV and give it a
data-cart-info
attribute. Inside this DIV, create a HEADING element withmdc-typography--headline4"
as its CSS class. - The HEADING element should have 2 SPAN children elements. The First SPAN element displays a shopping cart by setting it’s CSS class to
material-icons
and its content toshopping_cart
. The second SPAN element should have adata-bill
attribute and will be used to display the total figure the user is trying to pay on the app. - Create another DIV just after the
data-cart-info
DIV. Give this new DIV adata-credit-card
attribute and set its class tomdc-card
andmdc-card--outlined
. Within thedata-credit-card
DIV you just created, add a new DIV with a class ofmdc-card__primary-action
. These elements will be styled with the look and feel of an actual credit card. Brace yourself! - Within the
.mdc-card__primary-action
DIV, create an IMAGE with an attribute ofdata-card-type
and set its source to https://placehold.it/120x60.png?text=Card. This IMAGE will be used to display the credit card type (Visa or MasterCard), based on the series of numbers entered by the user. - Right after the IMAGE and still within the
mdc-card__primary-action
DIV, create a new DIV with an attribute ofdata-cc-digits
. It should contain four INPUT text fields, each with asize
of4
and a placeholder of----
(4 dashes). These fields will be used to collect the credit card numbers from the user. - Create a DIV with an attribute of
data-cc-info
as a sibling to thedata-cc-digits
DIV. This new DIV should have two INPUT text fields, one for entering the card holder’s name while the other will be for the card’s expiry date. The first field should have asize
of20
and aplaceholder
ofName Surname
. The expiry date field should have asize
of6
and aplaceholder
ofMM/YY
- indicating the expiry date format.
We are now done with the structure of the credit card component. Let’s make a button to allow the user make payment with the card details
- Create a BUTTON as a sibling to the
data-credit-card
DIV. Set the BUTTON’s class tomdc-button
and give it adata-pay-btn
attribute. It should havePay Now
as its display text. After the user enters details of the card and clicks on this button, the app will strike-though the card details that are in-valid, if any.
Step 2
While we might have the right structure in place, the app visually tells a different story. Time to make the app look good and usable.
- The SPAN elements within the
data-cart-info
DIV should be displayed as inline block elements and theirvertical-align
set tomiddle
. The.material-icons
SPAN should have a150
pixels size of font. - Give the
data-credit-card
DIV435px
width,240px
minimum height,10px
rounded borders, and a#5d6874
background colour. - Display the
data-card-type
IMAGE as a block element. Give it a120px
width and a60px
height. - The
data-cc-digits
DIV should have a2em
top margin. - INPUT elements in the
data-cc-digits
DIV should have a white color,2em
font size and line height, no border or background, and a right margin of0.5em
; - The
data-cc-info
DIV should have a1em
top margin. - INPUT elements in the
data-cc-info
DIV should have a white color,1.2em
font size, no border and no background. The second input should also have a right padding of10px
and be floated to the right of the viewport. - The
data-pay-btn
BUTTON should have a fixed position,90%
width, a solid border of1px
and positioned20px
from thebottom
of the viewport.
Your app should look better at this point. Ideally, the 4 input fields within the
data-cc-digits
DIV should all be on a single horzontal line.
challenge 2 instructions:
Get The Bill
Write all Javascript strictly in ES6 syntax. This means use arrow functions instead of the function keyword. Declare variables and functions with
const
orlet
. Useconst
by default, and only uselet
if you are sure you need to re-assign values to the said variable. Use the Selector API instead of the getElementBy… or getElementsBy… APIs.
Install the JSON Viewer Chrome extension, open a new tab and go to this API endpoint. You will be making HTTP requests to the API, so spend some time looking at the structure of the response data.
Step 1
- Within the SCRIPT element and just after the
billHype
function, create anappState
variable and assign it an empty Object literal. This variable will hold data for the app. - Create a
formatAsMoney
function. It should take in anamount
and abuyerCountry
parameter. It will use these parameters to format the user’s bill as a proper currency. - Create
detectCardType
,validateCardExpiryDate
, andvalidateCardHolderName
functions.detectCardType
should be declared to acceptfirst4Digits
as a parameter represneting the first four digits of the 16-digit long credit card number. As implied by their names, these functions will play major roles in validating various aspects of the card being used for payment - Create a
uiCanInteract
function. It will be called to setup the UI, including adding event handlers to enable interactions with the app. - Create a
displayCartTotal
function. It should expect a parameter and should use object de-structuring to obtain theresults
property of the given parameter. This function will be called with the data from an API call and it will display the total bill to be paid.
Step 2
- Update the
fetchBill
function. It should then use the browser’sfetch
function to make a HTTP request toapiEndpoint
. Using an arrow function in a.then
call to thefetch
function, return the response after converting it to JSON. Using another.then
call to the first one, pass the JSON data todisplayCartTotal
. Make sure to handle errors that may occur, e.g by showing a warning message in the console. - Call the
fetchBill
function from inside thestartApp
. This should be the only code or function call withinstartApp
.
Step 3
- In the body of the
displayCartTotal
function, de-structure the first item in theresults
array parameter into adata
variable. Next, use object de-structuring to obtain theitemsInCart
andbuyerCountry
properties fromdata
. You might want to install the JSONViewer Chrome extension, open a new tab and navigate tohttps://randomapi.com/api/006b08a801d82d0c9824dcfdfdfa3b3c
to see the shape of the JSON data we are dealing with. - Next,
displayCartTotal
should setappState.items
toitemsInCart
andappState.country
tobuyerCountry
. - Use the Array
.reduce
function to calculate the total bill fromitemsInCart
Note that each item has aqty
property indicating how many of that item the user bought. Assign the calculated bill toappState.bill
- Go back to the
formatAsMoney
function. Use the in-built javascript.toLocaleString
function on theamount
andbuyerCountry
parameters to formatamount
as a currency with the currency symbol ofbuyerCountry
. The countries and their currency symbols are in thecountries
array you got in your starter code. If thebuyerCountry
is not incountries
, then useUnited States
as the country and format the currency accordingly. - Continuing from where you left off in
displayCartTotal
, use theformatAsMoney
function to setappState.billFormatted
to the formatted total bill. The already assignedappState.bill
andappState.country
should be handy for this task! - Set the text content of the
data-bill
SPAN element to the formatted bill saved inappState.billFormatted
- Assign an array literal to
appState.cardDigits
- Finally, call
uiCanInteract
to wrap updisplayCartTotal
Run your app (click the play button) and see if the correct total bill is displayed in the UI with the right currency format. Next, we will allow input and interaction so that users can provide payment information.
challenge 3 instructions:
Handle Simple Validation
Step 1
- Create a
flagIfInvalid
function just afterformatAsMoney
function. This function is used to mark an input entry as invalid (strike-though) nor not. It should take afield
andisValid
parameters. IfisValid
is true , it should remove theis-invalid
class fromfield
, otherwise it should add it tofield
. - Just after
flagIfInvalid
function, create aexpiryDateFormatIsValid
function which takes afield
parameter representing the card’s expiry date field. It should return true if the field’s value complies with the MM/YY format, otherwise it should return false. - With the above utility functions out of the way, go back to the
validateCardExpiryDate
function. This function should return true if the value provided matches theMM/YY
format (hint: delegate to expiryDateFormatIsValid ) AND if the date is in the future. In either case, it should use theflagIfInvalid
function to mark the field as valid or not. It then has to return true or false depending on if the validation requirements are met or not. - Now to the
validateCardHolderName
function. Recall that its placeholder already suggests the required format, which is Name Surname (2 names separated by space). Each name should be at least 3 characters long. It should use theflagIfInvalid
function to mark the field as valid or not and then return true or false depending on if the validation requirements are met or not.
Step 2
- Create
validateCardNumber
andvalidatePayment
functions above theuiCanInteract
function. - In
validatePayment
, callvalidateCardNumber
,validateCardHolderName
, andvalidateCardExpiryDate
functions in that order. - After that, create a
smartInput
function withevent
andfieldIndex
as parameters. If you already created aacceptCardNumbers
function, rename it tosmartInput
. - In the
uiCanInteract
function, give input focus to the first credit card number input field, then setvalidatePayment
as the click event listener for thedata-pay-btn
BUTTON. Finally, end theuiCanInteract
function (for now) by calling thebillHype
function
Try filling in the card holer’s name and expiry date into the UI then click the Pay Now BUTTON to see if the details are correctly marked as invalid or not.
Try clicking on the shopping cart icon or the displayed total bill. Do you hear someone hyping how much you are about to pay?
so this is what my code looks like at the moment:
<!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;
}
/* new css */
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;
}
[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: none;
}
[data-cc-info] input + input {
padding-right: 10px;
float: right;
}
[data-pay-btn]{
position: fixed;
width: 90%;
border: 1px solid;
bottom: 20px;
}
/* end of new css*/
/* Add Your CSS From Here */
</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 type="text" size="4" placeholder="----">
<input type="text" size="4" placeholder="----">
<input type="text" size="4" placeholder="----">
<input type="text" size="4" placeholder="----">
</div>
<div data-cc-info="">
<input type="text" size="20" placeholder="Name Surname">
<input type="text" size="6" 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)
);
}
});
};
/* new js */
// step 1 point 1
const appState = {};
// step 1 point 2
const formatAsMoney = (amount, buyerCountry) => {
// step 3 point 4
let country = countries.find(({ country }) => country === buyerCountry) || {
code: 'US',
currency: 'USD'
}
return amount.toLocaleString(country.code, {
style: 'currency',
currency: country.currency
})
}
// challenge 3 step 1 point 1
const flagIfInvalid = (field, isValid) => {
// field has to be an id or a selector
if(isValid){
field.classList.remove('is-invalid');
}else{
field.classList.add('is-invalid');
};
}
// end challenge 3 step 1 point 1
// challenge 3 step 1 point 2
const expiryDateFormatIsValid = (field) => {
//field has to be a selector
return RegExp(/^(((0)[0-9]) |((1)[0-2]))(\/)\d{2}$/).test(field.value) || RegExp(/^\d\/\d{2}$/);
}
// end challenge 3 step 1 point 2
// step 1 point 3
// function 1
const detectCardType = (first4Digits) => {
}
// function 2
const validateCardExpiryDate = () => {
// challenge 3 step 1 point 3
const target = document.querySelector('[data-cc-info] input:nth-child(2)');
const isValid = expiryDateFormatIsValid(field);
if(isValid) {
const [MM, YY] = field.value.split('/');
const date = new Date();
const tM = date.getMonth() + 1;
const tY = date.getYear() - 100;
if(+YY >= tY) {
if(+YY === tY) {
if(+MM < tM) isValid = false;
}
}else {
isValid = false;
}
}
flagIfInvalid(field, isValid);
return isValid;
const currentDate = new Date().getTime();
const ds = target.value.split('/');
const userDate = new Date(`20${Number(ds[1])}`, (Number(ds[0]-1) + 2)).getTime();
const valid = (expiryDateFormatIsValid(target) && (userDate >= currentDate));
flagIfInvalid(target, valid);
return valid;
// end challenge 3 step 1 point 3
}
// function 3
const validateCardHolderName = () => {
// challenge 3 step 1 point 4
const field = document.querySelector('[data-cc-info]input:nth-child(1)');
const validate = /^([A-Za-z]{3,})\s([A-Za-z]{3,})$/;
let isValid = validate.test(field.value);
flagIfInvalid(field, isValid);
return isValid;
// end challenge 3 step 1 point 4
}
// challenge 3 step 2 point 1
const validateCardNumber = () => {
}
const validatePayment = () => {
// challenge 3 step 2 point 2
validateCardNumber();
validateCardHolderName();
validateCardExpiryDate();
// end challenge 3 step 2 point 2
}
// end challenge 3 step 2 point 1
// challenge 3 step 2 point 3
const smartInput = (event, fieldIndex) => {
}
// end challenge 3 step 2 point 3
// step 1 point 4
const uiCanInteract = () => {
// challenge 3 step 2 point 4
const firstInputDigit = document.querySelector('div[data-cc-digits]input:nth-child(1)');
const firstInputInfo = document.querySelector('div[data-cc-info]input:nth-child(1)');
const secondInputInfo = document.querySelector('div[data-cc-info]input:nth-child(2)');
//adding event listener
firstInputDigit.addEventListener('blur', detectCardType(target));
firstInputInfo.addEventListener('blur', validateCardHolderName(target));
secondInputInfo.addEventListener('blur', validateCardExpiryDate(target));
//select the btn
const dataPayBtn = document.querySelector('button[data-pay-btn]');
dataPayBtn.addEventListener('click', validatePayment);
//adding focus to the first input
firstInputDigit.focus();
//call billHype
billHype()
// document.querySelector('[data-cc-digits]input:nth-child(1)').focus();
// document.querySelector('[data-pay-btn]').addEventListener('click', validatePayment());
// billHype();
// end challenge 3 step 2 point 4
}
// step 1 point 5
const displayCartTotal = ({results}) => {
// step 3 point 1
const [data] = results;
const {itemsInCart, buyerCountry} = data;
// step 3 point 2
appState.items = itemsInCart; // problem
appState.country = buyerCountry;// problem
// step 3 point 3
appState.bill = itemsInCart.reduce((p, q) => {p["price"]*q["qty"]});
// step 3 point 5
appState.billFormatted = formatAsMoney(appState.bill, appState.country);
// step 3 point 6
const billSpan = document.querySelector("span[data-bill]");
billSpan.textContent = appState.billFormatted;
// step 3 point 7
appState.cardDigits = [];
// step 3 point 8
uiCanInteract()
}
/* end new js*/
const fetchBill = () => {
const apiHost = 'https://randomapi.com/api';
const apiKey = '006b08a801d82d0c9824dcfdfdfa3b3c';
const apiEndpoint = `${apiHost}/${apiKey}`;
// step 2 point 1
// json response
fetch(apiEndpoint)
.then((response) => {
const res = response.json();
return res})
.then(data => displayCartTotal(data))
.catch(error => console.log(error));
};
const startApp = () => {
// step 2 point 2
fetchBill();
};
startApp();
</script>
</body>
</html>
when you run this code in your browser, you see that the last two instructions of the third challenge are not working but I don’t know what the problem is. Please help me find it.
The deadline to submit the challenge is on 25th september 2019. Thank you for your time and heart.