Build an fCC Forum Leaderboard - Build an fCC Forum Leaderboard

Tell us what’s happening:

Step 24 and 33 Tests Not Passing for Build a fCC Forum Leaderboard

  1. Each img element in the string returned by the avatars function should have the src with the value of the avatar_template property of the poster. In case avatar_template contains a relative path, it should be set to /<avatar_template>.

  2. The second td element of each table row from the string returned by showLatestPosts should contain the images returned by the avatars function called with posters and users as arguments, nested within a div element with the class of avatar-container.

Despite meeting the described logic (“add avatarUrl prefix if relative path” and “wrap in div.avatar-container”), the tests for 24 and 33 still fail.

Could someone confirm whether the test conditions are too strict (e.g., expecting no extra slash in the path or a specific avatar path prefix), or if there’s a known issue with these two steps?

Your code so far

<!-- file: index.html -->
<!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>
/* file: styles.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;
  }
}
/* file: script.js */
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" },
  425: { category: "Queries", className: "queries" },
  430: { category: "Back End Development", className: "back-end-development" },
};

const postsContainer = document.getElementById("posts-container");

const timeAgo = (time) => {
  const currentTime = new Date();
  const lastPost = new Date(time);
  const timeDifference = currentTime - lastPost;
  const msPerMinute = 1000 * 60;
  const minutes = Math.floor(timeDifference / msPerMinute);
  const hours = Math.floor(minutes / 60);
  const days = Math.floor(hours / 24);

  if (minutes < 60) {
    return `${minutes}m ago`;
  } else if (hours < 24) {
    return `${hours}h ago`;
  } else {
    return `${days}d ago`;
  }
};

const viewCount = (views) => {
  if (views >= 1000) {
    return `${Math.floor(views / 1000)}k`;
  }
  return views;
};

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


const avatars = (posters, users) => {
  return posters
    .map((poster) => {
      const user = users.find((u) => u.id === poster.user_id);
      if (!user) return "";
      const template = user.avatar_template.replace("{size}", "30");
      const src = template.startsWith("/user_avatar/") 
        ? `${avatarUrl}/${template}` 
        : template;
      return `<img src="${src}" alt="${user.name}" />`;
    })
    .join("");
};

const showLatestPosts = (data) => {
  const { users, topic_list } = data;
  const { topics } = topic_list;
  const postsContainer = document.querySelector("#posts-container");

  const postsHTML = topics
    .map((topic) => {
      const {
        id,
        title,
        views,
        posts_count,
        slug,
        posters,
        category_id,
        bumped_at,
      } = topic;
      
      return `
        <tr>
          <td>
            <a class="post-title" href="${forumTopicUrl}${slug}/${id}" target="_blank">${title}</a>
            ${forumCategory(category_id)}
          </td>
          <td>
            <div class="avatar-container">${avatars(posters, users)}</div>
          </td>
          <td>${posts_count - 1}</td>
          <td>${viewCount(views)}</td>
          <td>${timeAgo(bumped_at)}</td>
        </tr>
      `;
    })
    .join("");

  postsContainer.innerHTML = postsHTML;
};

const fetchData = async () => {
  try {
    const res = await fetch(forumLatest);
    const data = await res.json();
    showLatestPosts(data);
  } catch (err) {
    console.log(err);
  }
};

fetchData();

Your browser information:

User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36

Challenge Information:

Build an fCC Forum Leaderboard - Build an fCC Forum Leaderboard

if you add console.log(src) in the avatars function, then run the tests, and look at the browser console to see what is printed, you will see the issue

Hi ILM,

I’ve updated my avatars function according to your suggestion and added a console.log(src) to check the generated URLs. Here’s my current code snippet:

const avatars = (posters, users) => {
  return posters
    .map(poster => {
      const user = users.find(u => u.id === poster.user_id);
      if (!user) return "";

      const template = user.avatar_template.startsWith("/")
        ? user.avatar_template.slice(1)
        : user.avatar_template;

      const src = `${avatarUrl}/${template}`.replace("{size}", "30");
      console.log(src);

      return `<img src="${src}" alt="${user.name || "Unknown User"}" />`;
    })
    .join("");
};

The console prints:

https://sea1.discourse-cdn.com/freecodecamp/user_avatar/QuincyLarson_30.png
https://sea1.discourse-cdn.com/freecodecamp/user_avatar/JOY-OKORO_30.png

However, the tests for user stories 24 and 33 are still failing. I’m not sure why, since the URLs appear correct and match the /<avatar_template> format. Could you clarify what exactly the test expects here?

Thanks for your guidance!

I’ve updated my avatars function according to your suggestion and added a console.log(src) to check the generated URLs. The console prints: https://sea1.discourse-cdn.com/freecodecamp/user_avatar/QuincyLarson_30.png https://sea1.discourse-cdn.com/freecodecamp/user_avatar/JOY-OKORO_30.png

you have the wrong value for avatarUrl, copy your code and reset the step to get the right value