Build an fCC Forum Leaderboard - Build an fCC Forum Leaderboard

Tell us what’s happening:

Hello,
For me, all tests passes except for test 33. And I have no clue, why? There are console errors. I am unable to pinpoint the issue, please help.
Thanks

Your code so far

<!-- file: index.html -->

/* file: styles.css */

/* file: script.js */

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36

Challenge Information:

Build an fCC Forum Leaderboard - Build an fCC Forum Leaderboard

below is my solution:-

  const forumLatest =
    "https://cdn.freecodecamp.org/curriculum/forum-latest/latest.json";
  const forumTopicUrl = "https://forum.freecodecamp.org/t/";
  const forumCategoryUrl = "https://forum.freecodecamp.org/c/";
  const avatarUrl = "https://sea1.discourse-cdn.com/freecodecamp";

  const allCategories = {
    299: { category: "Career Advice", className: "career" },
    409: { category: "Project Feedback", className: "feedback" },
    417: { category: "freeCodeCamp Support", className: "support" },
    421: { category: "JavaScript", className: "javascript" },
    423: { category: "HTML - CSS", className: "html-css" },
    424: { category: "Python", className: "python" },
    432: { category: "You Can Do This!", className: "motivation" },
    560: { category: "Backend Development", className: "backend" },
  };

  function timeAgo(isoTimestamp) {
    const diffInMs = new Date() - new Date(isoTimestamp);

    const diffInMins = Math.floor(diffInMs / (1000 * 60));
    if (diffInMins < 60) {
      return `${diffInMins}m ago`;
    }

    const diffInHours = Math.floor(diffInMins / 60);
    if (diffInHours < 24) {
      return `${diffInHours}h ago`;
    }

    const diffInDays = Math.floor(diffInHours / 24);
    return `${diffInDays}d ago`;
  }

  //console.log(new Date().toISOString());
  //console.log(timeAgo("2025-03-23T12:00:00Z")); // Output depends on current time

  const viewCount = (numViews) => {
    return numViews >= 1000 ? `${Math.floor(numViews / 1000)}k` : numViews;
  };

  const forumCategory = (id) => {
    if (allCategories.hasOwnProperty(id)) {
      return `<a class="category ${allCategories[id].className}" href="${forumCategoryUrl}${allCategories[id].className}/${id}">${allCategories[id].category}</a>`;
    } else {
      return `<a class="category general" href="${forumCategoryUrl}general/${id}">General</a>`;
    }
  };

  //console.log(forumCategory("299"));

  const avatars = (posters, users) => {
    let img = "";
    users
      .filter((user) => posters.some((poster) => poster.user_id === user.id))
      .forEach((user) => {
        //console.log("user.avatar_template: ", user.avatar_template);
        let avatarTemplate = user.avatar_template.replace(/\{size\}/, "30");
        avatarTemplate = avatarTemplate.startsWith("https://")
          ? avatarTemplate
          : `${avatarUrl}${avatarTemplate}`;

        img += `<img src="${avatarTemplate}" alt="${user.name}" />\n`;
      });
    return img;
  };

  const showLatestPosts = (obj) => {
    //console.log("obj.users: ", obj.users);
    //console.log("posters: ", obj.topic_list.topics[0].posters);
    //console.log("obj.topic_list: ", obj.topic_list);
    const postContainer = document.getElementById("posts-container");
    postContainer.innerHTML = "";
    obj.topic_list.topics.forEach((topic) => {
      //console.log(topic.posters);
      //console.log("#############################################");
      //console.log(obj.users);
      postContainer.innerHTML += `<tr>
        <td>
          <a class="post-title" href="${forumTopicUrl}${topic.slug}/${topic.id}">${topic.title}</a>
          ${forumCategory(topic.category_id)}
        </td>
        <td>
          <div class="avatar-container">${avatars(topic.posters, obj.users)}</div>
        </td>
        <td>${topic.posts_count - 1}</td>
        <td>${topic.views}</td>
        <td>${timeAgo(topic.bumped_at)}</td>
  </tr>`;
    });
  };

  async function fetchData() {
    try {
      let response = await fetch(forumLatest);
      let data = await response.json();
      showLatestPosts(data);
    } catch (error) {
      console.log(error);
    }
  }


What are the console errors?

I meant to say, there are no console errors. My bad, sorry.

Would you post your HTML, please?

I did not make any change in the html but here you go:-

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>fCC Forum Leaderboard</title>
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <header>
      <nav>
        <img
          class="fcc-logo"
          src="https://cdn.freecodecamp.org/platform/universal/fcc_primary.svg"
          alt="freeCodeCamp logo"
        />
      </nav>
      <h1 class="title">Latest Topics</h1>
    </header>
    <main>
      <div class="table-wrapper">
        <table>
          <thead>
            <tr>
              <th id="topics">Topics</th>
              <th id="avatars">Avatars</th>
              <th id="replies">Replies</th>
              <th id="views">Views</th>
              <th id="activity">Activity</th>
            </tr>
          </thead>
          <tbody id="posts-container"></tbody>
        </table>
      </div>
    </main>
    <script src="./script.js"></script>
  </body>
</html>

and the css:-

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --main-bg-color: #2a2a40;
  --black: #000;
  --dark-navy: #0a0a23;
  --dark-grey: #d0d0d5;
  --medium-grey: #dfdfe2;
  --light-grey: #f5f6f7;
  --peach: #f28373;
  --salmon-color: #f0aea9;
  --light-blue: #8bd9f6;
  --light-orange: #f8b172;
  --light-green: #93cb5b;
  --golden-yellow: #f1ba33;
  --gold: #f9aa23;
  --green: #6bca6b;
}

body {
  background-color: var(--main-bg-color);
}

nav {
  background-color: var(--dark-navy);
  padding: 10px 0;
}

.fcc-logo {
  width: 210px;
  display: block;
  margin: auto;
}

.title {
  margin: 25px 0;
  text-align: center;
  color: var(--light-grey);
}

.table-wrapper {
  padding: 0 25px;
  overflow-x: auto;
}

table {
  width: 100%;
  color: var(--dark-grey);
  margin: auto;
  table-layout: fixed;
  border-collapse: collapse;
  overflow-x: scroll;
}

#topics {
  text-align: start;
  width: 60%;
}

th {
  border-bottom: 2px solid var(--dark-grey);
  padding-bottom: 10px;
  font-size: 1.3rem;
}

td:not(:first-child) {
  text-align: center;
}

td {
  border-bottom: 1px solid var(--dark-grey);
  padding: 20px 0;
}

.post-title {
  font-size: 1.2rem;
  color: var(--medium-grey);
  text-decoration: none;
}

.category {
  padding: 3px;
  color: var(--black);
  text-decoration: none;
  display: block;
  width: fit-content;
  margin: 10px 0 10px;
}

.career {
  background-color: var(--salmon-color);
}

.feedback,
.html-css {
  background-color: var(--light-blue);
}

.support {
  background-color: var(--light-orange);
}

.general {
  background-color: var(--light-green);
}

.javascript {
  background-color: var(--golden-yellow);
}

.backend {
  background-color: var(--gold);
}

.python {
  background-color: var(--green);
}

.motivation {
  background-color: var(--peach);
}

.avatar-container {
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}

.avatar-container img {
  width: 30px;
  height: 30px;
}

@media (max-width: 750px) {
  .table-wrapper {
    padding: 0 15px;
  }

  table {
    width: 700px;
  }

  th {
    font-size: 1.2rem;
  }

  .post-title {
    font-size: 1.1rem;
  }
}

The first thing I notice is there is no data on your HTML page, meaning you’re not actually calling fetchData() in your JS code.

yes, you are right, fetchData() call was missing in my js code. Now I have added it but its still the same. also i believe that no change will be required in the provided html and css file, right?

const forumLatest =
  "https://cdn.freecodecamp.org/curriculum/forum-latest/latest.json";
const forumTopicUrl = "https://forum.freecodecamp.org/t/";
const forumCategoryUrl = "https://forum.freecodecamp.org/c/";
const avatarUrl = "https://sea1.discourse-cdn.com/freecodecamp";

const allCategories = {
  299: { category: "Career Advice", className: "career" },
  409: { category: "Project Feedback", className: "feedback" },
  417: { category: "freeCodeCamp Support", className: "support" },
  421: { category: "JavaScript", className: "javascript" },
  423: { category: "HTML - CSS", className: "html-css" },
  424: { category: "Python", className: "python" },
  432: { category: "You Can Do This!", className: "motivation" },
  560: { category: "Backend Development", className: "backend" },
};

function timeAgo(isoTimestamp) {
  const diffInMs = new Date() - new Date(isoTimestamp);

  const diffInMins = Math.floor(diffInMs / (1000 * 60));
  if (diffInMins < 60) {
    return `${diffInMins}m ago`;
  }

  const diffInHours = Math.floor(diffInMins / 60);
  if (diffInHours < 24) {
    return `${diffInHours}h ago`;
  }

  const diffInDays = Math.floor(diffInHours / 24);
  return `${diffInDays}d ago`;
}

//console.log(new Date().toISOString());
//console.log(timeAgo("2025-03-23T12:00:00Z")); // Output depends on current time

const viewCount = (numViews) => {
  return numViews >= 1000 ? `${Math.floor(numViews / 1000)}k` : numViews;
};

const forumCategory = (id) => {
  if (allCategories.hasOwnProperty(id)) {
    return `<a class="category ${allCategories[id].className}" href="${forumCategoryUrl}${allCategories[id].className}/${id}">${allCategories[id].category}</a>`;
  } else {
    return `<a class="category general" href="${forumCategoryUrl}general/${id}">General</a>`;
  }
};

//console.log(forumCategory("299"));

const avatars = (posters, users) => {
  let img = "";
  users
    .filter((user) => posters.some((poster) => poster.user_id === user.id))
    .forEach((user) => {
      //console.log("user.avatar_template: ", user.avatar_template);
      let avatarTemplate = user.avatar_template.replace(/\{size\}/, "30");
      avatarTemplate = avatarTemplate.startsWith("https://")
        ? avatarTemplate
        : `${avatarUrl}${avatarTemplate}`;

      img += `<img src="${avatarTemplate}" alt="${user.name}" />\n`;
    });
  return img;
};

const showLatestPosts = (obj) => {
  //console.log("obj.users: ", obj.users);
  //console.log("posters: ", obj.topic_list.topics[0].posters);
  //console.log("obj.topic_list: ", obj.topic_list);
  const postContainer = document.getElementById("posts-container");
  postContainer.innerHTML = "";
  obj.topic_list.topics.forEach((topic) => {
    //console.log(topic.posters);
    //console.log("#############################################");
    //console.log(obj.users);
    postContainer.innerHTML += `<tr>
      <td>
        <a class="post-title" href="${forumTopicUrl}${topic.slug}/${topic.id}">${topic.title}</a>
        ${forumCategory(topic.category_id)}
      </td>
      <td>
        <div class="avatar-container">${avatars(topic.posters, obj.users)}</div>
      </td>
      <td>${topic.posts_count - 1}</td>
      <td>${topic.views}</td>
      <td>${timeAgo(topic.bumped_at)}</td>
</tr>`;
  });
};

async function fetchData() {
  try {
    let response = await fetch(forumLatest);
    let data = await response.json();
    showLatestPosts(data);
  } catch (error) {
    console.log(error);
  }
}

fetchData();

Yes. That’s right. All of the HTML data is being generated by your JS. Also, the tests probably are calling the fetchData() function for you, so I’ll keep looking.

UPDATE: The only thing I’m seeing is that avatars is sometimes not returning user images in same order as the users appear in the topic_list.topics.posters array.

you are right, i updated my avatars function to below and it worked, thanks!!

const avatars = (posters, users) => {
  let img = "";

  posters.forEach((poster) => {
    const user = users.find((user) => user.id === poster.user_id);
    if (user) {
      let avatarTemplate = user.avatar_template.replace(/\{size\}/, "30");
      avatarTemplate = avatarTemplate.startsWith("https://")
        ? avatarTemplate
        : `${avatarUrl}${avatarTemplate}`;

      img += `<img src="${avatarTemplate}" alt="${user.name}" />\n`;
    }
  });
  return img;
};