Add initial project skeleton
This commit is contained in:
71
public/app.js
Normal file
71
public/app.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
const $$ = (sel) => Array.from(document.querySelectorAll(sel));
|
||||
|
||||
const playerInput = $('#player');
|
||||
const resultsEl = $('#results');
|
||||
const searchBtn = $('#searchBtn');
|
||||
|
||||
async function fetchJSON(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`Request failed ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
|
||||
async function search() {
|
||||
resultsEl.innerHTML = '';
|
||||
const player = playerInput.value.trim();
|
||||
if (!player) {
|
||||
resultsEl.innerHTML = `<div class="empty">Enter a player name to search.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set('player', player);
|
||||
|
||||
try {
|
||||
const data = await fetchJSON(`/api/ebay/search?${params.toString()}`);
|
||||
renderResults(data.results || []);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
resultsEl.innerHTML = `<div class="empty">Something went wrong. Try again.</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderResults(items) {
|
||||
if (!items.length) {
|
||||
resultsEl.innerHTML = `<div class="empty">No cards found. Try adjusting filters or the name.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const tpl = document.getElementById('card-template');
|
||||
items.forEach(c => {
|
||||
const node = tpl.content.firstElementChild.cloneNode(true);
|
||||
const img = node.querySelector('.card-img');
|
||||
img.src = c.image || '';
|
||||
img.alt = c.title || 'Listing image';
|
||||
const title = node.querySelector('.player');
|
||||
title.textContent = c.title || 'Sold listing';
|
||||
const price = (c.price ? `${c.currency || ''} ${Number(c.price).toFixed(2)}` : '');
|
||||
const ended = (c.ended ? new Date(c.ended).toLocaleString() : '');
|
||||
node.querySelector('.secondary').textContent = [price, ended].filter(Boolean).join(' • ');
|
||||
// Wrap card with link to eBay
|
||||
node.addEventListener('click', () => {
|
||||
if (c.url) window.open(c.url, '_blank');
|
||||
});
|
||||
resultsEl.appendChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
searchBtn.addEventListener('click', () => {
|
||||
search();
|
||||
});
|
||||
|
||||
// Press Enter to search
|
||||
playerInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
search();
|
||||
}
|
||||
});
|
||||
|
||||
39
public/index.html
Normal file
39
public/index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Baseball Cards Search</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Baseball Cards Search</h1>
|
||||
<p class="subtitle">Search eBay sold listings for a player's cards.</p>
|
||||
</header>
|
||||
|
||||
<section class="controls">
|
||||
<div class="control grow">
|
||||
<input id="player" type="text" placeholder="Enter a player name (e.g., Derek Jeter)" autocomplete="off" />
|
||||
</div>
|
||||
<div class="control">
|
||||
<label> </label>
|
||||
<button id="searchBtn">Search Sold</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="results" class="results"></section>
|
||||
|
||||
<template id="card-template">
|
||||
<div class="card">
|
||||
<img class="card-img" alt="Card image" />
|
||||
<div class="meta">
|
||||
<div class="line player"></div>
|
||||
<div class="line secondary"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="/app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
37
public/styles.css
Normal file
37
public/styles.css
Normal file
@@ -0,0 +1,37 @@
|
||||
* { box-sizing: border-box; }
|
||||
:root {
|
||||
--bg: #0e0f13;
|
||||
--panel: #171923;
|
||||
--muted: #9aa4b2;
|
||||
--text: #e6e9ef;
|
||||
--accent: #4f8cff;
|
||||
--accent-2: #7c4dff;
|
||||
--border: #262a34;
|
||||
}
|
||||
body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); }
|
||||
header { padding: 24px 24px 8px; }
|
||||
header h1 { margin: 0 0 6px; font-size: 24px; }
|
||||
header .subtitle { margin: 0; color: var(--muted); font-size: 14px; }
|
||||
|
||||
.controls { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 40px 24px; max-width: 800px; margin: 0 auto; min-height: 50vh; }
|
||||
.control { display: flex; flex-direction: column; gap: 6px; width: 100%; max-width: 560px; }
|
||||
.control.grow { width: 100%; max-width: 560px; }
|
||||
label { font-size: 12px; color: var(--muted); }
|
||||
select, input[type="text"] { height: 48px; color: var(--text); background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 0 14px; outline: none; }
|
||||
select:focus, input[type="text"]:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79,140,255,0.15); }
|
||||
button { height: 48px; border: none; border-radius: 10px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: white; font-weight: 600; cursor: pointer; padding: 0 16px; width: 200px; align-self: center; }
|
||||
button:hover { filter: brightness(1.05); }
|
||||
|
||||
.results { padding: 20px 24px 36px; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
|
||||
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.card-img { width: 100%; height: 300px; object-fit: cover; background: #111; }
|
||||
.meta { padding: 10px 12px 12px; }
|
||||
.meta .line { line-height: 1.2; }
|
||||
.meta .player { font-weight: 700; }
|
||||
.meta .secondary { color: var(--muted); margin-top: 2px; font-size: 14px; }
|
||||
|
||||
.empty { color: var(--muted); text-align: center; grid-column: 1/-1; padding: 40px 0; }
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.controls { min-height: 40vh; padding: 28px 16px; }
|
||||
}
|
||||
Reference in New Issue
Block a user