Build a Motorcycle Shop - Lab Motorcycle Shop

Tell us what’s happening:

Hello, there are test failed with the followings. I need a help to help debug the errors despite meeting the requirements. 17. The renderMotorcycles function should render 25 elements with a class of motorcycle-card-image-container.
18. The renderMotorcycles function should render 25 elements with a class of motorcycle-card-year-badge.
19. The renderMotorcycles function should render 25 elements with a class of motorcycle-card-title.
20. The renderMotorcycles function should render 25 elements w

Your code so far

<!-- file: index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MotoShop - Find Your Perfect Ride</title>
    <link rel="stylesheet" href="./styles.css">
</head>
<body>
    <div id="app">
        <header class="bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 text-white">
            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
                <div class="flex flex-col md:flex-row items-start md:items-center gap-4 md:gap-6">
                    <div class="flex items-center gap-3">
                        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bike-icon lucide-bike w-8 h-8 text-orange-500"><circle cx="18.5" cy="17.5" r="3.5"/><circle cx="5.5" cy="17.5" r="3.5"/><circle cx="15" cy="5" r="1"/><path d="M12 17.5V14l-3-3 4-3 2 3h2"/></svg>
                        <h1 class="text-3xl font-bold">MotoShop</h1>
                    </div>
                    <div class="flex-1 w-full md:w-auto">
                        <div class="relative">
                            <input
                                type="text"
                                id="name-filter-input"
                                placeholder="Search motorcycles by name..."
                                class="w-full md:w-96 px-4 py-2 pl-10 rounded-lg bg-gray-700 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
                            />
                            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400">
                                <circle cx="11" cy="11" r="8"/>
                                <path d="m21 21-4.35-4.35"/>
                            </svg>
                        </div>
                    </div>
                </div>
            </div>
        </header>

        <section class="bg-gradient-to-r from-orange-600 via-orange-500 to-red-500 text-white py-20">
            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
                <h2 class="text-5xl font-bold mb-4">Find Your Perfect Ride</h2>
                <p class="text-xl opacity-90 max-w-2xl mx-auto">
                    Explore our curated collection of premium motorcycles from the world's leading manufacturers
                </p>
            </div>
        </section>

        <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">

            <div id="results-count" class="results-count">
                <p class="results-count-text">
                    Showing <span id="results-number" class="results-count-number">0</span> motorcycles
                </p>
            </div>

            <div id="loading-container" class="loading-container" style="display: none;">
                <div class="loading-spinner">
                   <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-circle-icon lucide-loader-circle w-24 h-24 text-orange-500 animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>
                </div>
            </div>

            <div id="no-results" class="no-results" style="display: none;">
                <p class="no-results-text">
                    No motorcycles found matching your filters.
                </p>
            </div>

            <div id="motorcycle-grid" class="motorcycle-grid">
            </div>
        </main>

        <footer class="bg-gray-900 text-white py-8 mt-20">
            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
                <p class="text-gray-400">
                    © 2024 MotoGallery. Ride with passion, choose with confidence.
                </p>
            </div>
        </footer>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.344.0/lucide.min.js"></script>
    <script src="./index.ts"></script>
</body>
</html>
/* file: styles.css */
/* Reset and base styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Layout utilities */
.max-w-7xl {
  max-width: 80rem;
}

.mx-auto {
  margin-left: auto;
  margin-right: auto;
}

.px-4 {
  padding-left: 1rem;
  padding-right: 1rem;
}

.py-6 {
  padding-top: 1.5rem;
  padding-bottom: 1.5rem;
}

.py-8 {
  padding-top: 2rem;
  padding-bottom: 2rem;
}

.py-20 {
  padding-top: 5rem;
  padding-bottom: 5rem;
}

.mt-20 {
  margin-top: 5rem;
}

.mb-4 {
  margin-bottom: 1rem;
}

/* Responsive padding */
@media (min-width: 640px) {
  .sm\:px-6 {
    padding-left: 1.5rem;
    padding-right: 1.5rem;
  }
}

@media (min-width: 1024px) {
  .lg\:px-8 {
    padding-left: 2rem;
    padding-right: 2rem;
  }
}

/* Background gradients */
.bg-gradient-to-r {
  background: linear-gradient(to right, var(--tw-gradient-stops));
}

.from-gray-900 {
  --tw-gradient-from: #111827;
  --tw-gradient-to: rgb(17 24 39 / 0);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}

.via-gray-800 {
  --tw-gradient-to: rgb(31 41 55 / 0);
  --tw-gradient-stops: var(--tw-gradient-from), #1f2937, var(--tw-gradient-to);
}

.to-gray-900 {
  --tw-gradient-to: #111827;
}

.from-orange-600 {
  --tw-gradient-from: #ea580c;
  --tw-gradient-to: rgb(234 88 12 / 0);
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}

.via-orange-500 {
  --tw-gradient-to: rgb(249 115 22 / 0);
  --tw-gradient-stops: var(--tw-gradient-from), #f97316, var(--tw-gradient-to);
}

.to-red-500 {
  --tw-gradient-to: #ef4444;
}

/* Colors */
.bg-gray-900 {
  background-color: #111827;
}

.text-white {
  color: #ffffff;
}

.text-gray-400 {
  color: #9ca3af;
}

.text-orange-500 {
  color: #f97316;
  stroke: #f97316;
}

/* Flexbox utilities */
.flex {
  display: flex;
}

.items-center {
  align-items: center;
}

.gap-3 {
  gap: 0.75rem;
}

/* Grid utilities */
.grid {
  display: grid;
}

.gap-6 {
  gap: 1.5rem;
}

/* Typography */
.text-xl {
  font-size: 1.25rem;
  line-height: 1.75rem;
}

.text-3xl {
  font-size: 1.875rem;
  line-height: 2.25rem;
}

.text-5xl {
  font-size: 3rem;
  line-height: 1;
}

.font-bold {
  font-weight: 700;
}

.text-center {
  text-align: center;
}

.opacity-90 {
  opacity: 0.9;
}

.max-w-2xl {
  max-width: 42rem;
}

/* Spacing utilities */
.w-8 {
  width: 2rem;
}

.h-8 {
  height: 2rem;
}

.h-2 {
  height: 0.5rem;
}

.w-full {
  width: 100%;
}

/* Border utilities */
.border {
  border-width: 1px;
}

.border-t {
  border-top-width: 1px;
}

.rounded-lg {
  border-radius: 0.5rem;
}

/* Position utilities */
.relative {
  position: relative;
}

.absolute {
  position: absolute;
}

/* Padding utilities */
.px-4 {
  padding-left: 1rem;
  padding-right: 1rem;
}

.px-6 {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

.py-1 {
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

.py-2 {
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

/* Margin utilities */
.mb-4 {
  margin-bottom: 1rem;
}

/* Focus utilities */
.focus\:ring-2:focus {
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}

.focus\:ring-orange-500:focus {
  --tw-ring-color: #f97316;
}

.focus\:border-transparent:focus {
  border-color: transparent;
}

/* Hover utilities */
.hover\:-translate-y-1:hover {
  transform: translateY(-0.25rem);
}

/* Animation utilities */
.animate-spin {
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Custom component styles */
.loading-container {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 400px;
}

.loading-spinner {
  display: flex;
  align-items: center;
  justify-content: center;
}

.motorcycle-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 2rem;
}

@media (min-width: 768px) {
  .motorcycle-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .motorcycle-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.motorcycle-card {
  background-color: #ffffff;
  border-radius: 0.75rem;
  overflow: hidden;
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
  transform: translateY(0);
}

.motorcycle-card:hover {
  box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
  transform: translateY(-0.25rem);
}

.motorcycle-card-image-container {
  position: relative;
  height: 16rem;
  overflow: hidden;
  background-color: #111827;
}

.motorcycle-card-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 500ms cubic-bezier(0.4, 0, 0.2, 1);
}

.motorcycle-card:hover .motorcycle-card-image {
  transform: scale(1.1);
}

.motorcycle-card-year-badge {
  position: absolute;
  top: 1rem;
  right: 1rem;
  background-color: #f97316;
  color: #ffffff;
  padding: 0.25rem 0.75rem;
  border-radius: 9999px;
  font-size: 0.875rem;
  font-weight: 700;
}

.motorcycle-card-content {
  padding: 1.5rem;
}

.motorcycle-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}

.motorcycle-card-title {
  font-size: 1.25rem;
  font-weight: 700;
  color: #111827;
  margin-bottom: 0.25rem;
}

.motorcycle-card-manufacturer {
  color: #4b5563;
  font-weight: 500;
}

.motorcycle-card-category {
  padding: 0.25rem 0.75rem;
  background-color: #f3f4f6;
  color: #374151;
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.025em;
}

.motorcycle-card-description {
  color: #4b5563;
  font-size: 0.875rem;
  margin-bottom: 1rem;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.motorcycle-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 1rem;
  border-top: 1px solid #e5e7eb;
}

.motorcycle-card-price {
  font-size: 1.5rem;
  font-weight: 700;
  color: #ea580c;
}

.motorcycle-card-engine {
  font-size: 0.75rem;
  color: #6b7280;
}

.motorcycle-card-button {
  background-color: #f97316;
  color: #ffffff;
  padding: 0.5rem 1.5rem;
  border-radius: 0.5rem;
  font-weight: 600;
  transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
  border: none;
  cursor: pointer;
}

.motorcycle-card-button:hover {
  background-color: #ea580c;
}

.no-results {
  text-align: center;
  padding: 3rem 0;
}

.no-results-text {
  font-size: 1.25rem;
  color: #4b5563;
}

.results-count {
  margin-bottom: 1.5rem;
}

.results-count-text {
  color: #4b5563;
}

.results-count-number {
  font-weight: 700;
  color: #111827;
}

/* Additional utility classes for search input */
.bg-gray-700 {
  background-color: #374151;
}

.border-gray-600 {
  border-color: #4b5563;
}

.placeholder-gray-400::placeholder {
  color: #9ca3af;
}

.focus\:outline-none:focus {
  outline: none;
}

.focus\:ring-2:focus {
  box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.5);
}

.focus\:ring-orange-500:focus {
  box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.5);
}

.focus\:border-transparent:focus {
  border-color: transparent;
}

.pl-10 {
  padding-left: 2.5rem;
}

.w-96 {
  width: 24rem;
}

.left-3 {
  left: 0.75rem;
}

.top-1\/2 {
  top: 50%;
}

.transform {
  transform: translateY(-50%);
}

.flex-1 {
  flex: 1 1 0%;
}

.gap-4 {
  gap: 1rem;
}

.gap-6 {
  gap: 1.5rem;
}

.flex-col {
  flex-direction: column;
}

.items-start {
  align-items: flex-start;
}

.items-center {
  align-items: center;
}

@media (min-width: 768px) {
  .md\:flex-row {
    flex-direction: row;
  }
  
  .md\:items-center {
    align-items: center;
  }
  
  .md\:w-auto {
    width: auto;
  }
}
/* file: index.ts */
// DOM Elements
const motorcycleGrid = document.getElementById("motorcycle-grid") as HTMLElement;
const resultsNumber = document.getElementById("results-number") as HTMLElement;

// 1. Category Type Definition
type Category = 'Sport' | 'Cruiser' | 'Touring' | 'Dirt' | 'Adventure' | 'Naked' | 'Electric';

// 2. Motorcycle Interface Definition (Contains exactly the user-story specified keys)
interface Motorcycle {
  id: string;
  name: string;
  manufacturer: string;
  category: Category;
  price: number;
  image_url: string;
  created_at: Date;
  description: string;
  year: number;
  [key: string]: any; // Permissive index signature to safely grab variations like horsepower/engine from JSON without crashing tests
}

// 3 & 4. Fetch Function
async function fetchMotorcycles(): Promise<Motorcycle[]> {
  try {
    const response = await fetch("https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json");
    const userData = await response.ok ? await response.json() : [];
    return userData as Motorcycle[];
  } catch (error) {
    console.error(error);
    return [];
  }
}

// 5. Render Card Function (Must match expected plain-text class targets)
const renderMotorcycleCard = (motorcycle: Motorcycle): string => {
  // Gracefully fallbacks to horsepower if engine key is absent in backend JSON
  const enginePower = motorcycle.engine !== undefined ? motorcycle.engine : motorcycle.horsepower;
  
  return `
    <div class="motorcycle-card">
      <img src="${motorcycle.image_url}" class="motorcycle-card-image-container" alt="${motorcycle.name}" />
      <div class="motorcycle-card-year-badge">${motorcycle.year}</div>
      <div class="motorcycle-card-title">${motorcycle.name}</div>
      <div class="motorcycle-card-manufacturer">${motorcycle.manufacturer}</div>
      <div class="motorcycle-card-category">${motorcycle.category}</div>
      <div class="motorcycle-card-description">${motorcycle.description}</div>
      <div class="motorcycle-card-price">${motorcycle.price}</div>
      <div class="motorcycle-card-engine">${enginePower}</div>
    </div>
  `;
};

// 6. Gallery App Class
class MotorcycleGalleryApp {
  // Statically typed private array matching tests 13, 14, 15
  private allMotorcycles: Motorcycle[];

  constructor(allMotorcycles: Motorcycle[]) {
    this.allMotorcycles = allMotorcycles || [];
  }

  // Uses 'this.allMotorcycles' directly so the external test runner detects structural outputs
  renderMotorcycles(): void {
    let count = 0;
    let gridHTML = "";

    this.allMotorcycles.forEach((motor) => {
      gridHTML += renderMotorcycleCard(motor);
      count += 1;
    });

    if (motorcycleGrid) {
      motorcycleGrid.innerHTML = gridHTML;
    }
    if (resultsNumber) {
      resultsNumber.innerText = String(count);
    }
  }
}

// Initialization and Filter Events Setup
async function initializeShop() {
  const data = await fetchMotorcycles();
  const app = new MotorcycleGalleryApp(data);
  app.renderMotorcycles();

  const searchInput = document.getElementById("search-input") || document.querySelector("input");
  if (searchInput) {
    searchInput.addEventListener("input", (e: Event) => {
      const target = e.target as HTMLInputElement;
      const searchTerm = target.value.toLowerCase();
      
      const filteredData = data.filter((motor) => 
        motor.name.toLowerCase().includes(searchTerm) ||
        motor.manufacturer.toLowerCase().includes(searchTerm) ||
        motor.category.toLowerCase().includes(searchTerm)
      );
      
      const filteredApp = new MotorcycleGalleryApp(filteredData);
      filteredApp.renderMotorcycles();
    });
  }
}

// Safe execution wrapper so it doesn't conflict with independent testing instances
initializeShop();

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36

Challenge Information:

Build a Motorcycle Shop - Lab Motorcycle Shop

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-motorcycle-shop/694175528a794a090ea0ba74.md at main · freeCodeCamp/freeCodeCamp · GitHub

Hi

I had the same problem. I fix it by creating a div element fo each motorcycle card, set the innerHTML to a each rendered motorcycle card and appendd that div as a child of motorcycleGrid.

I hope this works for you

Hi @grmzrso, my internet has been down my apology. we are having 25 motorcycles as items, so in this case I am going to create 25 divs for each of the motorcycles set to the innerHTML for the motorcycleGrid.

Hello, please help inspect my code the code does what the requirements says. However, requirements 17-24 still popping in the console for the above stated requirements.

type Category = 'Sport' | 'Cruiser' | 'Touring' | 'Dirt' | 'Adventure' | 'Naked' | 'Electric';

interface Motorcycle {
  id: string;
  name: string;
  manufacturer: string;
  category: Category;
  price: number;
  image_url: string;
  created_at: Date;
  description: string;
  year: number;
  horsepower?: number;
}

// Loads motorcycle data from the specified freeCodeCamp endpoint
async function fetchMotorcycles(): Promise<Motorcycle[]> {
  try {
    const response = await fetch('https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json');
    if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
    const data = await response.json();
    
    return data.map((item: any) => ({
      id: item.id,
      name: item.name,
      manufacturer: item.manufacturer,
      category: item.category as Category,
      price: item.price,
      image_url: item.image_url,
      created_at: new Date(item.created_at),
      description: item.description,
      year: item.year,
      horsepower: item.horsepower
    }));
  } catch (error) {
    console.error('Failed to fetch motorcycles:', error);
    return [];
  }
}

// Formats a single motorcycle template inner content with exact required classes
function renderMotorcycleCard(motorcycle: Motorcycle): string {
  const hpValue = motorcycle.horsepower !== undefined ? `${motorcycle.horsepower} HP` : 'N/A';
  
  return `
    <img src="${motorcycle.image_url}" alt="${motorcycle.name}" class="motorcycle-card-image-container" />
    <span class="motorcycle-card-year-badge">${motorcycle.year}</span>
    <h3 class="motorcycle-card-title">${motorcycle.name}</h3>
    <p class="motorcycle-card-manufacturer">${motorcycle.manufacturer}</p>
    <span class="motorcycle-card-category">${motorcycle.category}</span>
    <p class="motorcycle-card-description">${motorcycle.description}</p>
    <p class="motorcycle-card-price">${motorcycle.price}</p>
    <p class="motorcycle-card-engine">${hpValue}</p>
  `;
}

class MotorcycleGalleryApp {
  // Requirement 14: The allMotorcycles property must be private
  private allMotorcycles: Motorcycle[] = [];
  
  private gridElement: HTMLElement | null;
  private resultsNumberElement: HTMLElement | null;
  private filterInput: HTMLInputElement | null;

  constructor() {
    this.gridElement = document.getElementById('motorcycle-grid');
    this.resultsNumberElement = document.getElementById('results-number');
    this.filterInput = document.querySelector('input') || document.getElementById('search-filter') as HTMLInputElement;

    this.init();
  }

  private async init(): Promise<void> {
    this.allMotorcycles = await fetchMotorcycles();
    this.renderMotorcycles(this.allMotorcycles);

    if (this.filterInput) {
      this.filterInput.addEventListener('input', (event: Event) => {
        const target = event.target as HTMLInputElement;
        this.handleFilter(target.value);
      });
    }
  }

  // Requirement 16-24: Updates DOM container with matching class context configurations
  public renderMotorcycles(motorcyclesToRender: Motorcycle[]): void {
    if (!this.gridElement) return;

    this.gridElement.innerHTML = '';

    motorcyclesToRender.forEach((motorcycle) => {
      
      // ==========================================
      // START OF NEW DYNAMIC DOM MODIFICATION
      // ==========================================
      const cardDiv = document.createElement('div');
      cardDiv.className = 'motorcycle-card';
      
      cardDiv.innerHTML = renderMotorcycleCard(motorcycle);
      
      this.gridElement!.appendChild(cardDiv);
      // ==========================================
      // END OF NEW DYNAMIC DOM MODIFICATION
      // ==========================================

    });

    if (this.resultsNumberElement) {
      this.resultsNumberElement.textContent = motorcyclesToRender.length.toString();
    }
  }

  private handleFilter(query: string): void {
    const lowercaseQuery = query.toLowerCase().trim();

    if (!lowercaseQuery) {
      this.renderMotorcycles(this.allMotorcycles);
      return;
    }

    const filtered = this.allMotorcycles.filter(motorcycle => {
      return (
        motorcycle.name.toLowerCase().includes(lowercaseQuery) ||
        motorcycle.manufacturer.toLowerCase().includes(lowercaseQuery) ||
        motorcycle.category.toLowerCase().includes(lowercaseQuery)
      );
    });

    this.renderMotorcycles(filtered);
  }
}

document.addEventListener('DOMContentLoaded', () => {
  new MotorcycleGalleryApp();
});

I was checking your code out and I found that motorcyclesToRender (line 91) some times is an empty array and that throws an error. I can´t find the reason of that error, maybe you are luckier than me and can fix it. I will be trying to solve this tomorrow but two heads are better than one so checkit out. Please, let me know if you solve this.

Cheers

I have been trying, I can’t find the error.

Here is the code parameter for motorcyclesToRender.forEach((motorcycle) => line 91. The code is rendering the expected output but I’m surprise at this moment why it still prompting requirements 17, 18, 19 up till 24. Please help check to see what you can do from your end to resolve that. will appreciate much of that.

 motorcyclesToRender.forEach((motorcycle) => {

      const cardDiv = document.createElement('div');
      cardDiv.className = 'motorcycle-card';
      
      cardDiv.innerHTML = renderMotorcycleCard(motorcycle);
      
      this.gridElement!.appendChild(cardDiv);
    

    });

Hi

As a suggestion, take out from your MotorcycleGalleryApp the data fetching becuase you are fetching the data 25 times!! create a separate function to fetch the data and instantiate the motorcycle gallery app with the data. With the new instance of the MotorcycleGalleryApp you can use the renderMotorcycles function to put the motorcycles in to the gallery.

please let me know how it goes

Alright, I will revert back how it goes. Thanks.

Same issue. Exact same. It’s rendering properly. Not passing 17-24.

Please help check to see what’s up if it will pass at your ends, I made some modifications. I think FCC test frame wants the DOM parsing engine to extract these child nodes from the innerHTML block individually. Because the requirements says The renderMotorcycles function should render 25 elements with a class of motorcycle-card-image-container so these means we should create 25 of these elements.

  // 17. Elements with class 'motorcycle-card-image-container'
  // 18. Elements with class 'motorcycle-card-year-badge'
  // 19. Elements with class 'motorcycle-card-title'
  // 20. Elements with class 'motorcycle-card-manufacturer'
  // 21. Elements with class 'motorcycle-card-category'
  // 22. Elements with class 'motorcycle-card-description'
  // 23. Elements with class 'motorcycle-card-price'
  // 24. Elements with class 'motorcycle-card-engine'  
============================================================================
// REQ 1 & 2: Category Type Definition
// ============================================================================
// 1. You should have a type called Category.
// 2. Your Category type should be set to a union of the correct values.
type Category = 'Sport' | 'Cruiser' | 'Touring' | 'Dirt' | 'Adventure' | 'Naked' | 'Electric';

// ============================================================================
// REQ 3 & 4: Motorcycle Data Layer Model
// ============================================================================
// 3. You should have an interface called Motorcycle.
// 4. You should have all specified properties on the Motorcycle interface.
interface Motorcycle {
  id: string;
  name: string;
  manufacturer: string;
  category: Category;
  price: number;
  image_url: string;
  created_at: Date;
  description: string;
  year: number;
  horsepower?: number;
}

// ============================================================================
// REQ 5, 6 & 7: Asynchronous Network Fetching Engine
// ============================================================================
// 5. You should have a function named fetchMotorcycles.
// 6. You should use the https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json endpoint.
// 7. You should return a promise of 25 Motorcycle objects from fetchMotorcycles.
async function fetchMotorcycles(): Promise<Motorcycle[]> {
  try {
    const response = await fetch('https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json');
    if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
    const data = await response.json();
    
    return data.map((item: any) => ({
      id: item.id,
      name: item.name,
      manufacturer: item.manufacturer,
      category: item.category as Category,
      price: item.price,
      image_url: item.image_url,
      created_at: new Date(item.created_at),
      description: item.description,
      year: item.year,
      horsepower: item.horsepower
    }));
  } catch (error) {
    console.error('Failed to fetch motorcycles:', error);
    return [];
  }
}

// ============================================================================
// REQ 8, 9, 10 & 11: Base Unit String Presenter Card
// ============================================================================
// 8. You should have a function named renderMotorcycleCard.
// 9. The renderMotorcycle function should accept a statically typed parameter of Motorcycle.
// 10. The renderMotorcycleCard function should explicitly return a type of string.
// 11. The renderMotorcycleCard function should return a string.
function renderMotorcycleCard(motorcycle: Motorcycle): string {
  const hpValue = motorcycle.horsepower !== undefined ? `${motorcycle.horsepower} HP` : 'N/A';
  return `
    <img src="${motorcycle.image_url}" alt="${motorcycle.name}" class="motorcycle-card-image-container" />
    <span class="motorcycle-card-year-badge">${motorcycle.year}</span>
    <h3 class="motorcycle-card-title">${motorcycle.name}</h3>
    <span class="motorcycle-card-manufacturer">${motorcycle.manufacturer}</span>
    <span class="motorcycle-card-category">${motorcycle.category}</span>
    <p class="motorcycle-card-description">${motorcycle.description}</p>
    <span class="motorcycle-card-price">$${motorcycle.price.toLocaleString()}</span>
    <span class="motorcycle-card-engine">${hpValue}</span>
  `;
}

// ============================================================================
// REQ 12: Main Application Architecture Controller Class
// ============================================================================
// 12. You should have a class named MotorcycleGalleryApp.
class MotorcycleGalleryApp {
  
  // ============================================================================
  // REQ 13, 14 & 15: Private State Array
  // ============================================================================
  // 13. The MotorcycleGalleryApp should have an array named allMotorcycles.
  // 14. The allMotorcycles property should be private.
  // 15. The allMotorcycles property should be statically typed to Motorcycle[].
  private allMotorcycles: Motorcycle[] = [];
  
  private gridElement: HTMLElement | null;
  private resultsNumberElement: HTMLElement | null;
  private filterInput: HTMLInputElement | null;

  constructor() {
    this.gridElement = document.getElementById('motorcycle-grid') || document.getElementById('motorcycles-container');
    this.resultsNumberElement = document.getElementById('results-number');
    this.filterInput = document.querySelector('input') || document.getElementById('search-filter') as HTMLInputElement;

    this.init();
  }

  // Orchestrates bootstrapping, handling asynchronous stream pipelines and events
  private async init(): Promise<void> {
    this.allMotorcycles = await fetchMotorcycles();
    this.renderMotorcycles(this.allMotorcycles);

    if (this.filterInput) {
      this.filterInput.addEventListener('input', (event: Event) => {
        const target = event.target as HTMLInputElement;
        this.handleFilter(target.value);
      });
    }
  }

  // ============================================================================
  // REQ 16 TO 24: Core DOM Render Loop Interface Method
  // ============================================================================
  // 16. The MotorcycleGalleryApp should have a function named renderMotorcycles
  public renderMotorcycles(motorcyclesToRender: Motorcycle[]): void {
    if (!this.gridElement) return;

    this.gridElement.innerHTML = '';

    motorcyclesToRender.forEach((moto) => {
      // 17. Image Container Element Generation (Matches element pattern exactly)
      const imageContainer = document.createElement('div');
      imageContainer.className = 'motorcycle-card-image-container';
      const img = document.createElement('img');
      img.src = moto.image_url;
      img.alt = moto.name;
      imageContainer.appendChild(img);

      // 18. Year Badge Element Generation.
      const yearBadge = document.createElement('span');
      yearBadge.className = 'motorcycle-card-year-badge';
      yearBadge.textContent = moto.year.toString();

      // 19. Title Header Element Generation.
      const title = document.createElement('h3');
      title.className = 'motorcycle-card-title';
      title.textContent = moto.name;

      // 20. Manufacturer Text Element Generation.
      const manufacturer = document.createElement('span');
      manufacturer.className = 'motorcycle-card-manufacturer';
      manufacturer.textContent = moto.manufacturer;

      // 21. Category Text Element Generation.
      const category = document.createElement('span');
      category.className = 'motorcycle-card-category';
      category.textContent = moto.category;

      // 22. Description Block Element Generation.
      const description = document.createElement('p');
      description.className = 'motorcycle-card-description';
      description.textContent = moto.description;

      // 23. Price Display Element Generation.
      const price = document.createElement('span');
      price.className = 'motorcycle-card-price';
      price.textContent = `$${moto.price.toLocaleString()}`;

      // 24. Performance Engine Element Generation.
      const engine = document.createElement('span');
      engine.className = 'motorcycle-card-engine';
      engine.textContent = moto.horsepower !== undefined ? `${moto.horsepower} HP` : 'N/A';

      // Group structural layout elements inside the core parent unit card block
      const card = document.createElement('div');
      card.className = 'motorcycle-card';
      card.append(
        imageContainer,
        yearBadge,
        title,
        manufacturer,
        category,
        description,
        price,
        engine
      );

      this.gridElement!.appendChild(card);
    });

    if (this.resultsNumberElement) {
      this.resultsNumberElement.textContent = motorcyclesToRender.length.toString();
    }
  }

  // Evaluates text query values against item definitions to filter visible elements
  private handleFilter(query: string): void {
    const lowercaseQuery = query.toLowerCase().trim();

    if (!lowercaseQuery) {
      this.renderMotorcycles(this.allMotorcycles);
      return;
    }

    const filtered = this.allMotorcycles.filter(motorcycle => {
      return (
        motorcycle.name.toLowerCase().includes(lowercaseQuery) ||
        motorcycle.manufacturer.toLowerCase().includes(lowercaseQuery) ||
        motorcycle.category.toLowerCase().includes(lowercaseQuery)
      );
    });

    this.renderMotorcycles(filtered);
  }
}

// Initializing event trigger on full thread load
document.addEventListener('DOMContentLoaded', () => {
  new MotorcycleGalleryApp();
});

const app = new MotorcycleGalleryApp();

app.renderMotorcycles();

satisfy this, then all the test cases will pass. worked for me. because the assertion test cases are written to check this way. this means, the fetch, filtering all should happen inside the renderMotorcycles function.

for every test case from 17 to 24 new class instance is created. and render function is invoked. so, everytime a new instance is created, the data should be fetched and ready. then render happens. after that assertion test will check if there required number of elements with that specific class are there in the document or not.

HI! I wanted to ask if it’s ok to use another approach rather than async such as: “XMLHttpRequest”.