import { useState, useEffect } from "react"; // Mock database const DB = Array.from({ length: 25 }, (_, i) => ({ id: i + 1, name: `Transaction #${1000 + i}`, amount: (Math.random() * 100).toFixed(2) })); // Add a specific one for testing search DB.push({ id: 99, name: "Special Refund 99", amount: "5.00" }); async function fetchPage(page, search) { await new Promise(r => setTimeout(r, 300)); const filtered = DB.filter(item => item.name.toLowerCase().includes(search.toLowerCase())); const start = (page - 1) * 5; return { items: filtered.slice(start, start + 5), totalPages: Math.ceil(filtered.length / 5) || 1 }; } export default function App() { const [page, setPage] = useState(1); const [search, setSearch] = useState(""); const [data, setData] = useState({ items: [], totalPages: 1 }); const [loading, setLoading] = useState(true); useEffect(() => { const b = document.body; b.style.background = "#fafafa"; b.style.margin = "0"; return () => { b.style.background = ""; b.style.margin = ""; }; }, []); useEffect(() => { setLoading(true); let active = true; fetchPage(page, search).then((res) => { if (active) { setData(res); setLoading(false); } }); return () => { active = false; }; }, [page, search]); return ( <div style={styles.page}> <div style={styles.card}> <h1 style={styles.h1}>Transactions</h1> <input data-testid="search-input" value={search} // BUG: changing search does not reset page to 1. // If you are on page 3 and search for an item that only has 1 page of results, // the query asks for page 3 of that search result, which is empty. onChange={(e) => setSearch(e.target.value)} placeholder="Search by name..." style={styles.input} /> <div style={styles.tableContainer}> {loading ? ( <div data-testid="loading" style={styles.loading}>Loading...</div> ) : data.items.length === 0 ? ( <div data-testid="empty-state" style={styles.empty}>0 items found</div> ) : ( <table style={styles.table}> <tbody> {data.items.map(item => ( <tr key={item.id} data-testid="table-row"> <td style={styles.tdName}>{item.name}</td> <td style={styles.tdAmount}>${item.amount}</td> </tr> ))} </tbody> </table> )} </div> <div style={styles.pagination}> <button data-testid="prev-btn" disabled={page === 1} onClick={() => setPage(p => p - 1)} style={styles.btn}>Prev</button> <span data-testid="page-indicator" style={styles.pageText}>Page {page} of {data.totalPages}</span> <button data-testid="next-btn" disabled={page >= data.totalPages} onClick={() => setPage(p => p + 1)} style={styles.btn}>Next</button> </div> </div> </div> ); } const styles = { page: { display: "flex", justifyContent: "center", padding: "40px 20px", fontFamily: "system-ui, sans-serif" }, card: { background: "#fff", borderRadius: 12, padding: "24px", width: "100%", maxWidth: 480, boxShadow: "0 4px 6px -1px rgba(0,0,0,0.1)" }, h1: { margin: "0 0 16px", fontSize: 20, fontWeight: 600, color: "#111827" }, input: { width: "100%", boxSizing: "border-box", padding: "10px", borderRadius: 6, border: "1px solid #D1D5DB", marginBottom: 16 }, tableContainer: { minHeight: 220, border: "1px solid #E5E7EB", borderRadius: 8, overflow: "hidden" }, loading: { padding: 32, textAlign: "center", color: "#6B7280" }, empty: { padding: 32, textAlign: "center", color: "#6B7280" }, table: { width: "100%", borderCollapse: "collapse" }, tdName: { padding: "12px 16px", borderBottom: "1px solid #E5E7EB", fontSize: 14, color: "#374151" }, tdAmount: { padding: "12px 16px", borderBottom: "1px solid #E5E7EB", fontSize: 14, color: "#111827", textAlign: "right", fontWeight: 500 }, pagination: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 16 }, btn: { padding: "6px 12px", borderRadius: 6, border: "1px solid #D1D5DB", background: "#fff", cursor: "pointer", fontSize: 14 }, pageText: { fontSize: 14, color: "#6B7280" } };
Click "Run checks" to verify your solution.