Build a Product Showcase - Build a Product Showcase

Tell us what’s happening:

Hi everyone!
Even though my code works as prescribed can’t pass tests
40. Clicking the #books button should display the corresponding set of products in the #output element.
41. Clicking the #electronics button should display the corresponding set of products in the #output element.
42. Clicking the #clothing button should display the corresponding set of products in the #output element.
I tried with a different browser, resetting etc… no change.

https://forum.freecodecamp.org/t/build-a-product-showcase-build-a-product-showcase/790305

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>Product Showcase</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
  <h1>Product Showcase</h1>
  <div class="buttons">
    <button id="all">All</button>
    <button id="books">Books</button>
    <button id="electronics">Electronics</button>
    <button id="clothing">Clothing</button>
  </div>
  <div id="output" class="product-list"></div>
  <script src="index.ts"></script>
</body>
</html>
/* file: styles.css */
body {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  background: linear-gradient(135deg, #0f4c75 0%, #3282b8 100%);
  padding: 40px 20px;
  margin: 0;
  color: #1a1a1a;
  min-height: 100vh;
}

h1 {
  text-align: center;
  margin-bottom: 40px;
  color: #ffffff;
  font-size: 2.5rem;
  font-weight: 600;
  letter-spacing: -0.5px;
}

.buttons {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-bottom: 40px;
  flex-wrap: wrap;
}

button {
  padding: 12px 24px;
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  border: none;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.9);
  color: #333;
  transition: all 0.3s ease-in-out;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

button:hover {
  background: white;
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

button.active {
  background: #ff9800;
  color: #1a1a1a;
  box-shadow: 0 4px 15px rgba(255, 152, 0, 0.4);
  font-weight: 600;
}

#output {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 24px;
  max-width: 1200px;
  margin: 0 auto;
}

.item {
  background: white;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease-in-out;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.item:hover {
  transform: translateY(-8px);
  box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15);
}

.item strong {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: #0f4c75;
  font-weight: 700;
}

.item > div:not(.price) {
  font-size: 16px;
  color: #555;
  line-height: 1.5;
}

.price {
  margin-top: auto;
  font-size: 24px;
  font-weight: 700;
  color: #0f4c75;
  padding-top: 12px;
  border-top: 2px solid #f0f0f0;
}

@media (max-width: 768px) {
  #output {
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 16px;
  }

  h1 {
    font-size: 2rem;
  }

  .item {
    padding: 16px;
  }
}

@media (max-width: 480px) {
  #output {
    grid-template-columns: 1fr;
  }

  button {
    padding: 10px 16px;
    font-size: 14px;
  }

  .price {
    font-size: 20px;
  }
}
/* file: index.ts */
interface Item {
  type: 'book' | 'electronics' | 'clothing';
  id: string;
  price: number;
}

interface Book extends Item {
  type: 'book';
  title?: string;
  author?: string;
}

interface Electronics extends Item {
  type: 'electronics';
  item?: string;
  model?: string;
  warranty?: number;
}

interface Clothing extends Item {
  type: 'clothing';
  item?: string;
  brand?: string;
  size?: 'S' | 'M' | 'L';
}

type Product = Book | Electronics | Clothing;

class Collection<T> {
  private items: Array<T>;

  constructor(items: Array<T>) {
    this.items = items;
  }

  getAll() {
    return this.items
  }

  filter(callback: (element: T) => boolean): Array<T> {
    return this.items.filter(callback);
  }
}


const isValidItem = (item: unknown): item is Item => {
  const i = item as Record<string, unknown>
  // common props checks
  if (!('id' in i) || typeof i.id !== 'string') return false;
  if (!('price' in i) || typeof i.price !== 'number') return false;
  return true
}


const isValidProduct = (item: unknown): item is Product => {
  // check for type object and not null
  const isValid = (obj: unknown): boolean => {
    return typeof obj === 'object' && obj !== null;
  }
  if (!isValid(item)) return false;

  // cast item as object wiht string keys and flexible values
  const i = item as Record<string, unknown>

  //product specific checks
  if (i.type === 'book') {
    return typeof i.title === 'string' &&
      typeof i.author === 'string'
  }
  if (i.type === 'electronics') {
    return typeof i.item === 'string' &&
      typeof i.model === 'string'
  }
  if (i.type === 'clothing') {
    return typeof i.item === 'string' &&
      typeof i.brand === 'string'
  }
  return false
}


const renderProduct = (product: Product): string => {
  let header = '';
  let book = '';
  let electro = '';
  let clothes = '';
  try {
    if (product.type === 'book') {
      if (isValidProduct(product)) {
        book += ` 
       
        <p><strong>Book: </strong>${product.title} by ${product.author}</p>`
      }
    }
    if (product.type === 'electronics') {
      if (isValidProduct(product)) {
        electro += `
        
      <p><strong>Electronics: </strong>${product.item} - ${product.model} ${product.warranty ? `- Warranty: ${product.warranty} year(s)` : null}</p>
      `
      }
    }
    if (product.type === 'clothing') {
      if (isValidProduct(product)) {
        clothes += `
        
      <p><strong>Clothing: </strong>${product.item} by ${product.brand} ${product.size ? `- Size ${product.size}` : null}</p>`
      }
    }    
    if (isValidItem(product)) {
      header += `<div class='item' id='${product.id}'>
       ${product.type === 'book' ? book : product.type === 'electronics' ? electro : clothes}
    <div class='price'>$${product.price.toFixed(2)}</div>
   
    </div>`
    } else {
      throw new Error(`Unknown product type: ${JSON.stringify(product)}`)
    }
    return header
  } catch (error) {
    throw new Error(`Unknown product type: ${JSON.stringify(product)}`)
  }
}


const products = new Collection<Product>([
  {
    type: 'book',
    id: '12',
    price: 20,
    title: 'The Fisherman',
    author: 'Chigozie Obioma'
  },
  {
    type: 'book',
    id: '13',
    price: 30,
    title: 'Moby Dick',
    author: 'Herman Melville'
  },

  {
    type: 'electronics',
    id: 'aae34',
    price: 890,
    item: 'laptop',
    model: 'VivoBook',
    warranty: 9
  },

  {
    type: 'clothing',
    id: '34kk',
    item: 'jeans',
    price: 50,
    brand: 'Lewis',
    size: 'M'
  }
])

const showProducts = (type?: 'book' | 'electronics' | 'clothing') => {
  const output = document.querySelector('#output');
  if (output) {
    output.innerHTML = '';
    if (type) {
      const filtered = products.filter(p => p.type === type);
      output.innerHTML += filtered.map(p => renderProduct(p)).join('')
    } else {
      output.innerHTML += products.getAll().map(p => renderProduct(p)).join('')
    }
  }
}


const btns = document.querySelector('.buttons')

if (btns) {
  btns.addEventListener('click', (e) => {
    const element = e.target as HTMLElement;
    if (element.id === 'books') {
      const productType = element.id.slice(0, -1)
      showProducts(productType as 'book')
    } else if (element.id === 'electronics') {
      showProducts('electronics')
    } else if (element.id === 'clothing') {
      showProducts('clothing')
    } else {
      showProducts()
    }
  })
}

document.addEventListener('DOMContentLoaded', () => showProducts()) 

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:151.0) Gecko/20100101 Firefox/151.0

Challenge Information:

Build a Product Showcase - Build a Product Showcase

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-product-showcase/696920c0c98a1ed58eb86293.md at main · freeCodeCamp/freeCodeCamp · GitHub

Looks like tests are triggering the event on the button elements themselves, but without bubbling up the event. Therefore your event listener on the parent element will not be run.

Would you be interested in creating issue about this problem in the freeCodeCamp’s GitHub repository? Sign in to GitHub · GitHub

Hi, I’ve just created the issue on GitHub.

It seems someone fixed the bubbling up event propagation but, still my code won’t pass tests 40 to 42.

I just solved it by applying an even listener to each button…