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