Before getting into Andela there are Challenges(asking help): Javascript

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 with mdc-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 to shopping_cart . The second SPAN element should have a data-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 a data-credit-card attribute and set its class to mdc-card and mdc-card--outlined . Within the data-credit-card DIV you just created, add a new DIV with a class of mdc-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 of data-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 of data-cc-digits . It should contain four INPUT text fields, each with a size of 4 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 the data-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 a size of 20 and a placeholder of Name Surname . The expiry date field should have a size of 6 and a placeholder of MM/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 to mdc-button and give it a data-pay-btn attribute. It should have Pay 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 their vertical-align set to middle . The .material-icons SPAN should have a 150 pixels size of font.
  • Give the data-credit-card DIV 435px width, 240px minimum height, 10px rounded borders, and a #5d6874 background colour.
  • Display the data-card-type IMAGE as a block element. Give it a 120px width and a 60px height.
  • The data-cc-digits DIV should have a 2em 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 of 0.5em ;
  • The data-cc-info DIV should have a 1em 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 of 10px and be floated to the right of the viewport.
  • The data-pay-btn BUTTON should have a fixed position, 90% width, a solid border of 1px and positioned 20px from the bottom 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 or let . Use const by default, and only use let 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 an appState variable and assign it an empty Object literal. This variable will hold data for the app.
  • Create a formatAsMoney function. It should take in an amount and a buyerCountry parameter. It will use these parameters to format the user’s bill as a proper currency.
  • Create detectCardType , validateCardExpiryDate , and validateCardHolderName functions. detectCardType should be declared to accept first4Digits 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 the results 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’s fetch function to make a HTTP request to apiEndpoint . Using an arrow function in a .then call to the fetch function, return the response after converting it to JSON. Using another .then call to the first one, pass the JSON data to displayCartTotal . Make sure to handle errors that may occur, e.g by showing a warning message in the console.
  • Call the fetchBill function from inside the startApp . This should be the only code or function call within startApp .

Step 3

  • In the body of the displayCartTotal function, de-structure the first item in the results array parameter into a data variable. Next, use object de-structuring to obtain the itemsInCart and buyerCountry properties from data . You might want to install the JSONViewer Chrome extension, open a new tab and navigate to https://randomapi.com/api/006b08a801d82d0c9824dcfdfdfa3b3c to see the shape of the JSON data we are dealing with.
  • Next, displayCartTotal should set appState.items to itemsInCart and appState.country to buyerCountry .
  • Use the Array .reduce function to calculate the total bill from itemsInCart Note that each item has a qty property indicating how many of that item the user bought. Assign the calculated bill to appState.bill
  • Go back to the formatAsMoney function. Use the in-built javascript .toLocaleString function on the amount and buyerCountry parameters to format amount as a currency with the currency symbol of buyerCountry . The countries and their currency symbols are in the countries array you got in your starter code. If the buyerCountry is not in countries , then use United States as the country and format the currency accordingly.
  • Continuing from where you left off in displayCartTotal , use the formatAsMoney function to set appState.billFormatted to the formatted total bill. The already assigned appState.bill and appState.country should be handy for this task!
  • Set the text content of the data-bill SPAN element to the formatted bill saved in appState.billFormatted
  • Assign an array literal to appState.cardDigits
  • Finally, call uiCanInteract to wrap up displayCartTotal

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 after formatAsMoney function. This function is used to mark an input entry as invalid (strike-though) nor not. It should take a field and isValid parameters. If isValid is true , it should remove the is-invalid class from field , otherwise it should add it to field .
  • Just after flagIfInvalid function, create a expiryDateFormatIsValid function which takes a field 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 the MM/YY format (hint: delegate to expiryDateFormatIsValid ) AND if the date is in the future. In either case, it should use the flagIfInvalid 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 the flagIfInvalid 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 and validatePayment functions above the uiCanInteract function.
  • In validatePayment , call validateCardNumber , validateCardHolderName , and validateCardExpiryDate functions in that order.
  • After that, create a smartInput function with event and fieldIndex as parameters. If you already created a acceptCardNumbers function, rename it to smartInput .
  • In the uiCanInteract function, give input focus to the first credit card number input field, then set validatePayment as the click event listener for the data-pay-btn BUTTON. Finally, end the uiCanInteract function (for now) by calling the billHype 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.

I would start by reviewing how you have structured this:

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)
            );
          }
        });

Whilst, in a logically what you have written makes sense, I do not think JavaScript allows multiple conditions to be written in that way. That is, it should be written explicitly like this:

billDisplay.addEventListener('click', () => {
          const billSpan = document.querySelector("[data-bill]");
          if (billSpan === billSpan.textContent &&
            appState.bill === billSpan.textContent &&
            appState.billFormatted === billSpan.textContent &&
            appState.billFormatted === billSpan.textContent) {
            window.speechSynthesis.speak(
              new SpeechSynthesisUtterance(appState.billFormatted)
            );
          }
        });

Hope this helps at all. If you do know otherwise, then forget what I have said.

thank you: but I didn’t write that function, because as I said above in the description, they have provided for me the boilerplate before I even started coding and that function was written before, and the error message they are showing me is that I did a bad calculation(not calculating) and in this function there is no such thing that does calculations.

Again, I am not sure if it is your code or part of the boilerplate, but the .then() syntax - does this not fall under ES5 and previous, and, as such, should it not be replaced with async and await?

the instructions that they gave us in the guidelines are okay with es5. I have even passed the challenge that demands this function.
Here is instructions that says that I should use es5:

Update the fetchBill function. It should then use the browser’s fetch function to make a HTTP request to apiEndpoint . Using an arrow function in a .then call to the fetch function, return the response after converting it to JSON. Using another .then call to the first one, pass the JSON data to displayCartTotal . Make sure to handle errors that may occur, e.g by showing a warning message in the console.

1 Like

It might not be relevant to the calculation of the bill, but the end part of the method validateCardExpiryDate will never run because there is a return statement before it.
Also the names of the variables are a bit confusing. In that same method there are both “isValid” and “valid”.

Also, idk if it will impact later on or if they are meant to be like this, but the methods “detectCardType” and “validateCardNumber” are empty. For the first one, you can get a look here on how to find the type.

thank you, returning two values was a mistake, I am fixing it. And also I am going to find a way to use one variable of find a different name. The emptiness of those functions is that they will be filled in the next challenge

FreeCodeCamp will not offer support on Andela’s or any other employer’s pre-interview questions or projects. The purpose of these projects is to display your own knowledge to the employer, not ours. I am locking this topic to further comments.