Build a Motorcycle Shop - Lab Motorcycle Shop

Tell us what’s happening:

Hi to everyone!
I’m having trouble understanding why my code does not pass the tests 17 to 24, even though it does everything is should. Any help would be appreciated!

https://www.freecodecamp.org/learn/front-end-development-libraries-v9/lab-motorcycle-shop/lab-motorcycle-shop

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 */
// Type definition for motorcycle categories - restricts possible values
type Category =
  | "Sport"
  | "Cruiser"
  | "Touring"
  | "Dirt"
  | "Adventure"
  | "Naked"
  | "Electric";

// Interface defining the shape of a Motorcycle object
interface Motorcycle {
  id: string;
  name: string;
  manufacturer: string;
  category: Category;
  price: number;
  image_url: string;
  created_at: Date;
  description: string;
  year: number;
  engine?: string; // Optional property - may not exist on all motorcycles
}

// Fetches motorcycle data from external API, returns array or empty array on error
const fetchMotorcycles = async (): Promise<Motorcycle[]> => {
  try {
    const res = await fetch(
      "https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json",
    );
    const data = await res.json();
    return data;
  } catch (err) {
    console.error("Error fetching data: ", err);
    return []; // Return empty array as fallback to prevent crashes
  }
};

// Converts a Motorcycle object into an HTML string for display in the gallery
const renderMotorcycleCard = (motorcycle: Motorcycle): string => {
  return `<div class="motorcycle-card">
    <div class="motorcycle-card-image-container">
      <img class="motorcycle-card-image" src='${motorcycle.image_url}' alt='${motorcycle.name}' />
      <p class='motorcycle-card-year-badge'>${motorcycle.year}</p>
    </div>
    <div class='motorcycle-card-content'>
      <div class='motorcycle-card-header'>
        <div>
          <p class='motorcycle-card-title'>${motorcycle.name}</p>
          <p class='motorcycle-card-manufacturer'>${motorcycle.manufacturer}</p>
        </div>
        <p class='motorcycle-card-category'>${motorcycle.category}</p>
      </div>
      <p class='motorcycle-card-description'>${motorcycle.description}</p>
      <div class='motorcycle-card-footer'>
        <div>
          <p class='motorcycle-card-price'>$${motorcycle.price.toLocaleString()}</p>
          <p class='motorcycle-card-engine'>${motorcycle?.engine || "undefinedcc"}</p>
        </div>
        <button class='motorcycle-card-button'>View Details</button>
      </div>
    </div>
  </div>`;
};

// Main application class that manages the motorcycle gallery
class MotorcycleGalleryApp {
  private allMotorcycles: Motorcycle[] = []; // Complete unfiltered dataset
  private filteredMoto: Motorcycle[] = []; // Currently displayed dataset (after filtering)

  constructor() {
    this.loadMotorcycles(); // Fetch data when app starts
    this.setupEventListener(); // Set up search input listener
  }

  // Fetches motorcycle data from API, shows/hides loading spinner, then renders
  private async loadMotorcycles(): Promise<void> {
    const loadingContainer = document.getElementById("loading-container");
    if (loadingContainer) loadingContainer.style.display = "flex"; // Show spinner
    try {
      this.allMotorcycles = await fetchMotorcycles();
      this.filteredMoto = this.allMotorcycles;

      if (loadingContainer) loadingContainer.style.display = "none"; // Hide spinner
      this.renderMotorcycles(); // Display the motorcycles
    } catch (err) {
      console.error("Error fetching data: ", err);
    }
  }

  // Renders current filtered motorcycles to the DOM, updates count, handles empty state
  renderMotorcycles(): void {
    const moto = this.filteredMoto;
    const gridElement = document.getElementById("motorcycle-grid");
    const noResults = document.getElementById("no-results");

    // Clear previous content
    if (gridElement) {
      gridElement.textContent = "";
    }

    // Show/hide "no results" message based on filtered results
    if (moto.length === 0) {
      if (noResults) noResults.style.display = "block";
      if (gridElement) gridElement.style.display = "none";
    } else {
      if (noResults) noResults.style.display = "none";
      if (gridElement) gridElement.style.display = "grid";
    }

    // Update results count display
    const number = document.getElementById("results-number");
    if (number) {
      number.innerText = moto.length.toString();
    }

    // Generate and insert HTML for each motorcycle
    moto.forEach((moto: Motorcycle) => {
      gridElement?.insertAdjacentHTML("beforeend", renderMotorcycleCard(moto));
    });
  }

  // Filters motorcycles by name (case-insensitive) and re-renders
  handleSearch(query: string): void {
    this.filteredMoto = this.allMotorcycles.filter((moto: Motorcycle) =>
      moto.name.toLowerCase().includes(query.toLowerCase()),
    );
    console.log("filtered moto", this.filteredMoto);
    this.renderMotorcycles();
  }

  // Attaches input event listener to search box for real-time filtering
  setupEventListener(): void {
    const input = document.getElementById(
      "name-filter-input",
    ) as HTMLInputElement | null;
    input?.addEventListener("input", e => {
      const target = e.target as HTMLInputElement;
      this.handleSearch(target.value);
      console.log("target value", target.value);
    });
  }
}

// Create and start the application instance
const shop = new MotorcycleGalleryApp();

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

Everybody is having this issue at the moment