Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found
Not Found

All comments

  • BBualdo 540

    @BBualdo

    Submitted

    FIXED AND DONE

    Hi everyone!

    I spent so many hours on this and I can't progress any further. Probably it's just a lack of knowledge or I don't understand some JS behaviours 😢

    I designed it pretty fast, I have no problems with HTML and CSS, it's like breathing. Even if I don't know something I can google it easily. I made up my own idea of segregating and styling dark/light theme. Check it out and let me know if it is a proper way.

    Then I programmed appearing text from Input on the list. Easy.

    But when I tried to code delete buttons, my nightmare has started. I red, watched and tried 'normal' methods, array methods... Finally after 3hours I just coppied code from my previous 'Todo', which I was doing with one of the courses I watched. I tried to understand what I've just coppied, but Objects and Arrays are some black magic to me. I understand how to write them and which one is which, but I just can't use them by myself to code anything. I imagine that coding Completed, Active and All sections requires using 3 arrays, if I'm right... but how?

    I wish I could learn this OOP in JS. But I can't find tutorials or courses that could explain to me in a way I can understand and remember...

    This is my first solution in Intermediate level, but I can see now that it was too early for me 😢

    If you have any sources of knowledge or some lessons I will appreciate your effort!

    Thank you! ❤

    EDIT #1: I fixed markAsCompleted function and now it adds class to each checked todo. I also implemented Clear Completed button logic, where it checks if todo-list has class named 'completed' and if it has, it should splice that todo from an array and render list without that todos. But... Check it out what is happening. I have no clue. I'm not even sure if renderTodoList() is appropiate function.

    @JAleXDesigN

    Posted

    Hi @BBualdo, this is one way you can achieve the functionality of the todo list:

    • In the text input section add an id to the checkbox and to the input:
    <section class="todo-input-container">
      <div class="todo-input">
        <input
          id="add-task"
          class="checkbox"
          type="checkbox"
        />
        <input
          id="input-add-task"
          class="todo"
          placeholder="Create a new todo..."
        />
      </div>
    </section>
    
    • The "todo-list-container" section can be left empty, since the tasks will be added with js:
    <section class="todo-list-container">
      <!--Tasks will be added with js-->
    </section>
    
    • In the "nav-bar-container" section you can do the following:
    1. Add an id to the p element inside the "items-left", to be able to dynamically change its text according to the number of tasks.
    2. Add the class "is-toggled" all button "All", since it will be selected by default.
    <section class="nav-bar-container">
      <div class="items-left">
        <p id="items-count"><!--items left--></p>
      </div>
      <div class="categories">
        <button class="category-button is-toggled">All</button>
        <button class="category-button">Active</button>
        <button class="category-button">Completed</button>
      </div>
      <div class="clear-completed">
        <button class="clear-completed-button">Clear Completed</button>
      </div>
    </section>
    

    Now we go with the Javascript code:

    // Elements
    const todoListContainer = document.querySelector(".todo-list-container");
    const checkToAddTask = document.getElementById("add-task");
    const inputAddTask = document.getElementById("input-add-task");
    const categoryButtons = document.querySelectorAll(".category-button");
    const buttonClear = document.querySelector(".clear-completed-button");
    const itemsCountElement = document.getElementById("items-count");
    
    • The task array can be updated as follows by adding the id and complete properties which will be false by default:
    let todoList = [
      { id: 1, name: "Complete online JavaScript course", completed: false },
      { id: 2, name: "Jog around the park 3x", completed: false },
      { id: 3, name: "10 minutes meditation", completed: false },
      { id: 4, name: "Read for 1 hour", completed: false },
      { id: 5, name: "Pick up groceries", completed: false },
      { id: 6, name: "Complete Todo App on Frontend Mentor", completed: false },
    ];
    
    • Create a variable to store the selected filter by default it will be "all": let currentFilter = "all";

    • Add a function to render the list in the html:

    1. This function receives a parameter called list that if passed this is used, otherwise the list todoList will be used.
    2. In the variable html add the class "completed" to the container if task.completed is true using a ternary task.completed ? "completed" : "", also add a data-task-id="${task.id}" attribute, this will be used later for the update and delete actions and finally to the input the property checked is added if task.completed is true ${task.completed ? "checked" : ""}.
    function renderTodoList(list) {
      const filteredList = list || todoList; // 1
      todoListContainer.innerHTML = "";
    
      filteredList.forEach((task) => {
        const html = `
          <div class="todo-list ${
            task.completed ? "completed" : ""
          }" data-task-id="${task.id}">
            <input name="completed" class="checkbox" type="checkbox" ${
              task.completed ? "checked" : ""
            }>
            <div class="todo-content">
              <p>${task.name}</p>
              <button class="delete-button">Delete</button>
            </div>
          </div>
        `; //2
    
        todoListContainer.innerHTML += html; //Insert the html
      });
    
      updateItemsLeftCounter(filteredList); // Update counter
    }
    
    • Now the functions to add, update, delete and filter:
    1. Add task: Create an object with the data of the new task, as id in this case numbers are being used in order and to continue we use the todoList.length to obtain the number of elements of the tasks array and we add 1: id: todoList.length + 1, the name will be the value of the parameter name, and completed will be false, you add the new task to the array using todoList.push(newTask), render the list, reset the value of the input and finally the setTimeout is to uncheck the input a moment after the task is added
    function addTask(name) {
      const newTask = {
        id: todoList.length + 1,
        name,
        completed: false,
      };
    
      todoList.push(newTask);
      renderTodoList();
      inputAddTask.value = "";
    
      setTimeout(() => {
        checkToAddTask.checked = false;
      }, 200);
    }
    
    1. Filter Task Function: This function receives the filter parameter and will be in charge of keeping the task lists updated in their respective category filters, first we create a copy of the tasks so as not to change the original array and avoid deleting certain tasks when switch categories, then use tasksCopy.filter to filter the categories: if filter === "all" return all tasks else if : filter === "completed" ? task.completed returns the tasks where task.completed equals true otherwise those with task.completed equal false, then stores the filter in the currentFilter variable and calls the renderTodoList(taskFiltered) function passing the filtered tasks as parameters.
    function filterTask(filter) {
      const tasksCopy = [...todoList];
      const taskFiltered = tasksCopy.filter((task) =>
        filter === "all"
          ? task
          : filter === "completed"
          ? task.completed
          : !task.completed
      );
    
      currentFilter = filter;
      renderTodoList(taskFiltered);
    }
    
    1. Update task: This function receives the id of the task to update and to update the status of completed to said task we map the todoList, what this code does is that if the task.id is equal to the taskId passed as a parameter to the function task.id === taskId returns a copy of the task and updates the property completed ? { ...task, completed: !task.completed } this ...task is known as "spread operator" if you want to dig further into the topic, in the completed: ! task.completed, doing this !task.completed means it will change to the opposite if it was true the new value will be false and vice versa. Finally we call the function filterTask(currentFilter) passing the variable currentFilter as a parameter
    function updateTask(taskId) {
      todoList = todoList.map((task) =>
        task.id === taskId ? { ...task, completed: !task.completed } : task
      );
      filterTask(currentFilter);
    }
    
    1. Delete and Clear completed: Both functions use the filter method, in the deleteTask function the filter method returns the tasks whose id is different from the id of the task to delete task.id !== taskId, and in the clearCompletedTasks function it returns the tasks whose value is completed is false
    function deleteTask(taskId) {
      todoList = todoList.filter((task) => task.id !== taskId);
      filterTask(currentFilter);
    }
    
    function clearCompletedTasks() {
      todoList = todoList.filter((task) => !task.completed);
      filterTask(currentFilter);
    }
    
    1. Update Items Left: It is in charge of updating the number of elements in each category, the variable label is created, the result of which will be: if label is equal to "all" it returns "left", otherwise the name of the filter, then it is dynamically updates the quantity: Resulting in: eg: 5 items left, 2 items active, 3 items completed... this according to the selected category
    function updateItemsLeftCounter(list) {
      const remainingItemCount = list.filter((task) => !task.completed).length;
      const label = currentFilter === "all" ? "left" : currentFilter;
    
      itemsCountElement.textContent = `${remainingItemCount} items ${label}`;
    }
    

    At the events listeners will have:

    checkToAddTask.addEventListener("click", (e) => {
      const name = inputAddTask.value.trim(); //Value of the input without spaces
      if (name === "") { //If name is an empty string, we prevent the input from being checked
        e.preventDefault();
      } else { //Otherwise add the new task
        addTask(name);
      }
    });
    
    inputAddTask.addEventListener("keydown", (e) => {
      const name = inputAddTask.value.trim();
      if (e.key === "Enter" && name !== "") { // In the input if key Enter is pressed and name is not equal to an empty string add the new task
        checkToAddTask.checked = true;
        addTask(name);
      }
    });
    
    1. The clear button listener: buttonClear.addEventListener("click", clearCompletedTasks);

    2. For the event listener of task kill buttons instead of selecting all buttons with "querySelectorAll" and using forEach, you can listen for the click event on the task container, for example:

      1. Here, event.target refers to the element that was clicked. .closest(".delete-button") finds the closest ancestor (parent) that has the class delete-button. This allows you to determine if the delete button was clicked, regardless of whether the click happened directly on the button or on an element internal to the button, the same goes for toggleCompleted.
      2. If deleteButton exists then we get the taskId which is passed as data-task-id="${task.id}" attribute to the task container in the renderTodoList function, the deleteButton.closest(".todo-list") part will access the parent element having the class "todo-list" and .dataset.taskId will get the data-task-id attribute and will calls the deleteTask function using the retrieved id as parameter.
    todoListContainer.addEventListener("click", (event) => {
      // 1
      const deleteButton = event.target.closest(".delete-button");
      const toggleCompleted = event.target.closest(".checkbox");
    
      if (deleteButton) {
        // 2
        const taskId = parseInt(deleteButton.closest(".todo-list").dataset.taskId);
        deleteTask(taskId);
      }
    
      if (toggleCompleted) {
        const taskId = parseInt(
          toggleCompleted.closest(".todo-list").dataset.taskId
        );
        updateTask(taskId);
      }
    });
    

    And finally, the category filter function

    categoryButtons.forEach((button) =>
      button.addEventListener("click", () => {
        //Find an element with the class "is-toggled"
        const toggled = document.querySelector(".is-toggled");
    
        // If there is, remove the class is toggled
        if (toggled) toggled.classList.remove("is-toggled");
    
        // And add it to the button pressed
        button.classList.add("is-toggled");
    
        // Get the name of the filter, remove the trailing spaces and change to lowercase
        const filter = button.textContent.trim().toLowerCase();
    
        // Call the filterTask function with the name of the filter
        filterTask(filter);
      })
    );
    

    In the CSS in the todo-list-container class you are defining a height of 390px, this will mean that when adding new tasks they will not be displayed since the height is fixed (always 390px), you can solve it by changing height by min-height this way it will be at least 390px and the container will be able to increase in size as you add new tasks, You can also add a background-color to the container since when deleting the tasks its background is transparent.

    .todo-list-container {
      background-color: var(--color)
      min-height: 390px;
    }
    

    Here are some resources related to the code:

    Maybe the feedback is too long: D, but I hope it helps you and that it has helped you learn something new. Good luck!.

    Marked as helpful

    1
  • @JAleXDesigN

    Posted

    Hello @tejastej1997, regarding how you can improve your Javascript code:

    1. Improve Variable Naming: Instead of using generic variable names like boolean, percentage, and btn, consider using more descriptive names that convey their purpose. Use camelCase for variable names to follow the standard JavaScript naming convention.
    2. Reduce Repetition: You can store repeated elements or class names in variables to avoid repeated querying of the DOM. This can also help if the class names ever need to change in the future.
    3. Break down your code into smaller functions or methods. This makes the code easier to understand, test, and maintain. Create separate functions for different functionalities like calculating the tip, updating the UI, handling error messages, etc.
    4. Validation: Add validation to input fields to ensure that users enter valid values. For example, ensure that bill amount and number of people are positive numbers.

    Here's an example of how you might apply some of these improvements:

    //Uso de "const" para definir variables de elementos seleccionados y cambiar a **camelCase** sus nombres
    
    const customBtn = document.getElementById("custom-btn");
    const customInput = document.getElementById("custom-input");
    const reset = document.getElementById("reset");
    
    //Change the selector of all buttons with tipcalculator__options-opt classes from: ".tipcalculator__options-opt" to ".tipcalculator__options-opt:not(.custom)", this to prevent it from including the "custom" button since it is being accessed this in the constant "customBtn" and will have its own eventListener
    const tipPercentageButtons = document.querySelectorAll(
      ".tipcalculator__options-opt:not(.custom)"
    );
    
    const selectedTip = document.querySelector(".tipcalculator__selected-tip");
    const enterButton = document.getElementById("enter-button");
    const billInput = document.getElementById("bill-input");
    const billAmount = document.getElementById("bill-ammount");
    const perPerson = document.getElementById("per-person");
    const peopleInput = document.getElementById("people-input");
    const numberOfPeople = document.getElementById("number-of-people");
    const tipInput = document.getElementById("tip-input");
    const tipAmount = document.getElementById("tip-ammount");
    const totalBill = document.getElementById("total-bill");
    const percentageInput = document.getElementById("percentage-input");
    const inputs = document.querySelectorAll("input");
    
    //Add more descriptive variable names
    
    let selectedTipValue;
    let customTipSelected = false;
    let percentageSelected = false;
    
    customBtn.addEventListener("click", () => {
      customInput.classList.remove("hide");
      selectedTip.classList.add("hide");
      customTipSelected = true;
    });
    
    reset.addEventListener("click", () => {
      selectedTip.classList.add("hide");
      perPerson.innerHTML = "$0.00";
      totalBill.innerHTML = "$0.00";
    
      inputs.forEach((inputField) => {
        inputField.value = "";
      });
    });
    
    function updateTipPercentage(tipValue) {
      tipAmount.innerHTML = `${tipValue}%`;
    }
    
    //Change selectedTip[0] to selectedTip
    
    tipPercentageButtons.forEach((button) => {
      button.addEventListener("click", () => {
        selectedTipValue = button.value;
        updateTipPercentage(selectedTipValue);
        selectedTip.classList.remove("hide");
        customTipSelected = false;
        percentageSelected = true;
        customInput.classList.add("hide");
        percentageInput.classList.add("hide");
      });
    });
    
    customInput.addEventListener("keyup", () => {
      toggleElementVisibility(selectedTip, true);
      updateTipPercentage(customInput.value);
    });
    
    //Separate the validation and calculation logic in a separate function, and pass to the click event of the "enterButton"
    
    function calculateAndDisplay() {
      const bill = parseFloat(billAmount.value);
      const numberOfPeopleValue = parseInt(numberOfPeople.value);
    
      if (isNaN(bill) || bill <= 0) {
        billInput.classList.remove("hide");
        billInput.innerHTML =
          bill === 0
            ? "Bill ammount cannot be Zero"
            : "Bill ammount cannot be empty";
        perPerson.innerHTML = "$0.00";
      }
    
      if (isNaN(numberOfPeopleValue) || numberOfPeopleValue <= 0) {
        peopleInput.classList.remove("hide");
        peopleInput.innerHTML =
          numberOfPeopleValue === 0 ? "Cannot be zero" : "Cannot be empty";
        return;
      }
    
      let tipPercentage = customTipSelected
        ? parseFloat(customInput.value)
        : parseFloat(selectedTipValue);
    
      if (isNaN(tipPercentage) || tipPercentage < 0) {
        percentageInput.classList.remove("hide");
        billInput.classList.add("hide");
        return;
      }
    
      peopleInput.classList.add("hide");
      const perPersonAmount = (bill * tipPercentage) / 100 / numberOfPeopleValue;
      perPerson.innerHTML = perPersonAmount.toFixed(2);
      totalBill.innerHTML = (perPersonAmount * numberOfPeopleValue).toFixed(2);
    }
    
    enterButton.addEventListener("click", calculateAndDisplay);
    

    Regarding the css code:

    • Only import the necessary fonts: For this project you only use "Space Mono", so it is not necessary to import the rest of the fonts using @font-face just import:
    @font-face {
      font-family: "Space Mono";
      font-style: normal;
      font-weight: 700;
      font-display: swap;
      src: url(https://fonts.gstatic.com/s/spacemono/v13/i7dMIFZifjKcF5UAWdDRaPpZYFI.ttf)
        format("truetype");
    }
    
    • You can add the "font-family" to the "body" and so you would not have to add it to each class of each element:
    body {
      font-family: "Space Mono", monospace;
    }
    
    • Consolidate Repeated Styles: If you have repeated styles for certain elements, consider consolidating them into a single CSS class. This can help reduce redundancy and make your code more maintainable.
    • Use CSS Variables for Colors: Instead of hardcoding colors directly into your CSS, consider using CSS variables for colors. This makes it easier to change color schemes throughout your application.
    :root {
      --main-bg-color: #c5e4e7;
      --primary-color: #00494d;
      --secondary-color: #5e7a7d;
      --highlight-color: #26c0ab;
      --text-color: #00494d;
    }
    
    • Group Media Queries: Group media queries that apply to the same element. This can help reduce duplicated code and make media queries more organized.

    I hope my recommendations help you. Good luck!

    0
  • Felix 70

    @felix-toledo

    Submitted

    Hope its Ok. The unic problem that I think i've found was that I've heard is not a "good practice" to use var. But when I calculate the age, I can't found a way to do it without var.

    var newAge = actualYear - dateToCalculate[2] var newMonth = 0 var newDay = 0

    Those lines are in App.jsx

    @JAleXDesigN

    Posted

    Hi Felix, since you are re-assigning values to those variables you can use let instead of var

    let newAge = actualYear - dateToCalculate[2]
    let newMonth = 0
    let newDay = 0
    

    In this way the code would work the same and you would not use var.

    Marked as helpful

    1
  • @JAleXDesigN

    Posted

    Hi @BBualdo, regarding the first question, the reason why you can't directly use the value of an input in JavaScript via a global variable like for example: const startingDay = document.querySelector('.js-day-input').value, and expecting the value to be automatically updated in other functions may be related to when that line of code is executed.

    When you define startingDay using document.querySelector('.js-day-input').value, you are actually storing the current value of the input at that moment in the startingDay variable. However, if the value of the input changes later (for example, due to user interaction), the startingDay variable will not automatically update to reflect that change.

    In the second question it seems that you are referring to the section where you check if the entered date is in the past. To handle this case along with other errors, you can bundle all error checks into a single function (like lookForErrors) and then, within this function, call calculateDates only if all errors are hidden. This will ensure that all error checks are done before doing the calculations and animation.

    To resolve the button background image, you need to fix the url to: url(../assets/images/icon-arrow.svg), and you don't need to use the url twice, if you're trying to thicken the white border of the image, you can edit the svg image with the text editor and change the stroke-width="2" property to a larger number, e.g. stroke-width="3",

    As for how you could make your code cleaner, here are some suggestions:

    • Instead of repeating the same code to show errors and apply styles on error, you can create a function that takes care of this, which will make your code cleaner and easier to maintain, for example:
    function displayError(input, errorElement, errorMessage, label) {
      errorElement.innerHTML = errorMessage;
      errorElement.classList.remove("hidden");
      input.classList.add("input-error");
      label.classList.add("label-error");
    }
    
    function clearError(input, errorElement, label) {
      errorElement.classList.add("hidden");
      input.classList.remove("input-error");
      label.classList.remove("label-error");
    }
    

    And you would use these functions like this:

    Display error: displayError(startingDay, dayError, 'This field is required', dayLabel);

    Clear Error:clearError(startingDay, dayError, dayLabel);

    To check if it is a valid date you could use an auxiliary function like for example:

    function isValidDate (date) => {
      const [year, month, day] = date.split("-").map(Number);
      const dateObj = new Date(year, month - 1, day);
    
      return (
        dateObj.getFullYear() === year &&
        dateObj.getMonth() + 1 === month &&
        dateObj.getDate() === day
      );
    };
    
    1. The function takes a text string as an argument, which represents a date in the format "YYYY-MM-DD".
    2. date.split("-").map(Number) splits the date string into three parts separated by hyphens ("-"). Each part represents the year, month, and day of the date. The Number function is then applied to each of these parts to convert the strings to numeric values.
    3. The line const dateObj = new Date(year, month - 1, day); creates a date object using the numeric values extracted from the date string. The subtraction of 1 from the month value is because in the JavaScript Date class, months are indexed from 0 (January is 0, February is 1, etc.).
    4. Then, three checks are performed to determine if the date is valid:

    In this case if we pass a date that does not exist, for example:

    isValidDate("2022-02-29") //Returns false
    //the function will do this
    function isValidDate (date) {
      const [year, month, day] = date.split("-").map(Number);
      //[2022, 02, 29]
    
      const dateObj = new Date(year, month - 1, day);
      //Being a date that is not valid because February had 28 days in that year, the result of dateObj will be: Date: "2022-03-01T05:00:00.000Z"
    
      //When performing the validations we will have to coincide in the year but not in the month so it would return false
      return (
        dateObj.getFullYear() === year &&
        dateObj.getMonth() + 1 === month &&
        dateObj.getDate() === day
      );
    };
    

    For animations you can use the requestAnimationFrame method to perform the animation instead of using setInterval, which will provide finer control over the animation, you can do it in the following way:

    function calculateAnimation() {
      // Select all <span> elements
      let valueDisplays = document.querySelectorAll("span");
    
      // Define a function to animate the value change
      function animateValue(displayElement, start, end, duration) {
        const startTime = performance.now();
    
        // Define a function to update the animated value
        function updateValue(currentTime) {
          const elapsedTime = currentTime - startTime;
          const progress = Math.min(elapsedTime / duration, 1);
    
          // Calculate the animated value based on progress
          const animatedValue = Math.round(start + progress * (end - start));
    
          // Update the displayed value
          displayElement.textContent = animatedValue;
    
          // If animation is not complete, request the next animation frame
          if (progress < 1) {
            requestAnimationFrame(updateValue);
          }
        }
    
        // Start the animation by requesting the first animation frame
        requestAnimationFrame(updateValue);
      }
    
      // Iterate through each <span> element and animate its value change
      valueDisplays.forEach((valueDisplay) => {
        const startValue = 0;
        const endValue = parseInt(valueDisplay.innerHTML);
        const animationDuration = 1000; // Duration in milliseconds
    
        // Initiate the animation for the current value display
        animateValue(valueDisplay, startValue, endValue, animationDuration);
      });
    }
    

    and you would call this function at the end of calculateDates():

    function calculateDates() {
      // Rest of the code....
    
      calculateAnimation();
      // animation
    }
    

    These are just some recommendations, I hope they help you to continue improving.

    Marked as helpful

    1
  • @tabascum

    Submitted

    I'm not happy with the result at all. While all the fields can be filled out, the validations are not working as expected. Initially, the form cannot be submitted if the fields are empty, but the error messages don't disappear when they're filled. Additionally, I couldn't implement a validation for the expiration date. I could use a similar approach as the card number validation, but that would mean repeating the previous validation. If someone could take a look at my solution, I would appreciate it.

    @JAleXDesigN

    Posted

    Hello, so that the errors disappear when the fields are full, you can perform a validation with the onblur event, if the field meets the validation requirements, you remove the error from said field. To validate the expiration date: Here is an example of how you can do it:

    inputExpMonth.onblur = () => {
    	//Since you are using an input type text you must convert the value to a number, if you use inputs type number this is not necessary
    	const month = Number(inputExpMonth.value);
      
    	//You check if it is a valid number with !isNaN() and also that it is between 1 and 12
    	const isValidMonth = !isNaN(month) && month > 0 && month <= 12;
    
    	if (!isValidMonth) {
    		//if it is not valid
    	}
    }
    
    inputExpYear.onblur = () => {
    	const year = Number(inputExpYear.value);
    	//Get the last two digits of the current year
      const actualYear = new Date().getFullYear() % 100;
      
    	//check if it is a valid number and that it is greater than or equal to the current year
    	const isValidYear = !isNaN(year) && year >= actualYear;
      
    	if (!isValidMonth) {
    		//if it is not valid
    	}
    }
    
    

    Marked as helpful

    0
  • @JAleXDesigN

    Posted

    Hi, you can use npm install @wits/next-themes, I have used this a few times for themes in Next js with appDir and it works fine.

    You wrap the layout with the ServerThemeProvider

    import "./globals.css";
    import { Inter } from "next/font/google";
    import { ServerThemeProvider } from "@wits/next-themes";
    import Providers from "./Providers";
    
    const inter = Inter({ subsets: ["latin"] });
    
    export default function RootLayout({
    children,
    }: {
    children: React.ReactNode;
    }) {
    return (
    <ServerThemeProvider>
    <html lang="en">
    <body className={inter.className}>
    <Providers>{children}</Providers>
    </body>
    </html>
    </ServerThemeProvider>
    );
    }
    

    and for the client side provider you can do it in a separate component and use it in layout

    "use client";
    
    import type { FC, PropsWithChildren } from "react";
    
    import { ThemeProvider } from "@wits/next-themes";
    
    const Providers: FC<PropsWithChildren> = ({ children }) => {
    return <ThemeProvider>{children}</ThemeProvider>;
    };
    
    export default Providers;
    

    Marked as helpful

    0
  • @BarbarosTeoman

    Submitted

    I used the svgs in an img tag, I could see them on localhost, but after deployment, the svgs weren't there. If you know the solution for this I would really appreciate it.

    The page is fully responsive between 320px and 1440px.

    @JAleXDesigN

    Posted

    Hello, to solve the image issue you can import the image in this way

    Import the image using the path relative to the component where you use it in this case the Description component is inside the components folder, therefore to go to the assets folder we leave the components folder with "../" and enter the assets folder "../assets" and choose the image in this case "../assets/illustration-hero.svg"

    import illustrationHero from "../assets/illustration-hero.svg";
    
    export default function Description() {
    return (
    <div className="description">
    <div className="descriptionLeft">{/**Content */}</div>
    <div className="descriptionRight">
    <img
    src={illustrationHero} //And you make use of the imported image via 
    the src prop of the img tag
    alt=""
    />
    <div className="elipse" />
    </div>
    </div>
    );
    }
    

    Another way would be to place the images inside the public folder of your project, and the way to use them in the image tag would be: If you have for example an image in the public folder with the name illustration-hero.svg

    <img
    src="/illustration-hero.svg" //You access the public folder starting with "/" 
    followed by the name of the image
    alt=""
    />
    

    In this way, when making the production build the images will also be included, since they are not currently being included in the build. I hope my answer helps you, and if you have any questions I am here to help you.

    Marked as helpful

    1
  • P
    Michael 380

    @Mlchaell

    Submitted

    I only have one question - How would I do the .extra-card-section without using the media queries? If I do it without the queries, the a tag gets pushed out of the section on the right

    Also, if anyone else ideas/feedback on how I could do anything else better, let me know!

    @JAleXDesigN

    Posted

    Hi Michael, good job!!.. Here are some recommendations: In the .card class, in addition to the "max-width: 25em" you must put a "width: 90%" it can be another percentage depending on the width you want the card to occupy when the screen is not 25em wide.

    If you leave it only with the maximum width, the card will always have that width and will not adapt to mobile devices. The code would look like this and the margin would not be necessary:

      .card {
          max-width: 25em;
          width: 90%;
          margin: 2em 2em 2em 2em; //You can delete this line.
      }
    

    Here is a link on how to shorthand certain css properties CSS - shorthand properties

    Regarding your question about how to put the link on the right, you can wrap the .stack-items container and the "change" link in a "div" like so:

          <div class="extra-card-info">
              <div class="stack-items">
                  <p class="bold-dark-blue">Annual Plan</p>
                  <p class="desaturated-blue-text">$59.99/year</p>
              </div>
              <a href="#">Change</a>
          </div>
    

    And the css:

        .extra-card-info {
            width: 100%;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
    

    And you could already delete the media queries and the "margin-left: 7em;" from class .card-extra-section a {}.

    Marked as helpful

    1
  • Salamah 90

    @Salamah-Jimoh

    Submitted

    One area I find difficult when styling was making the two cards overlap in a mobile environment. According to the design, the Front-card is supposed to cover some part of the Back-card but when I reduced the margin and changed the position of the Front-card using CSS transform properties, the reverse happened and the front-card was kind of staying behind the back-card. What I did to solve this was to add both card twice in my HTML; one for desktop and mobile device while setting the display to none for each device to avoid repetition. So, for the mobile HTML, I added the back-card image first before adding the front-card image(the reverse of what I did for desktop ) and somehow that solved the problem. I'm not really sure that it's a good practice, so I would really like to know if there's a better way to go about that without having to write the markup twice... Thanks in anticipation

    @JAleXDesigN

    Posted

    Hi Salamah, congrats on completing the challenge, to place an element on top of another you can use the z-index property, for this the element to which you apply the z-index must have the "position" property, whether it is relative, absolute... . I hope my comment will be useful to you.

    1
  • Ceejay 20

    @C33jay

    Submitted

    It was diffcult building out the mobile design(375px), I don't know if I did the right thing on this section, please help me check it out. How can I center the facebook icon, it is out of place compared to the others?

    @JAleXDesigN

    Posted

    Good job, completing the challenge!! I recommend that you place each section inside a container with a maximum width so that all the content is centered horizontally for instance

    <main class="container container-main">
           <div class="col-img">
           </div>
           <div class="col-text">
           </div>
    </main>
    <footer class="container-main">
    </footer> 
    

    and in the CSS:

    .container-main {
        margin: 0 auto; //or use flex to center
        max-width: 1200px;
        width: 90%;
    }
    

    Set the main image to width 100% so that it is adaptable

    .col-img img{
        width: 100%;
    }
    

    And the social network icons you can align to the right or center using flex and media queries so that on large screens it aligns to the right and on small screens it aligns to the center. you can achieve it like this:

    <footer class="container-main">
        <div class="social-icons">
            <i class="fa fa-facebook" aria-hidden="true"></i>
            <i class="fa fa-twitter" aria-hidden="true"></i>
            <i class="fa fa-instagram" aria-hidden="true"></i>
        </div>
    </footer>
    

    and in the css

    .social-icons {
        display: flex;
    }
    @media (min-width: 982px) {
        .social-icons {
            justify-content: end;
        }
    }
    @media (max-width: 981px) {
        .social-icons {
            justify-content: center;
        }
    }
    

    It is a quick example, I hope I have helped you. Congratulations!!

    0