-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJSX.jsx
More file actions
107 lines (96 loc) · 2.68 KB
/
JSX.jsx
File metadata and controls
107 lines (96 loc) · 2.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { useState, useEffect, useCallback } from 'react';
const API_BASE = '/api/v1';
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
function Badge({ count, color = 'blue' }) {
if (count === 0) return null;
return (
<span className={`badge badge-${color}`}>
{count > 99 ? '99+' : count}
</span>
);
}
function SearchBar({ onSearch, placeholder = 'Search...' }) {
const [query, setQuery] = useState('');
const debounced = useDebounce(query);
useEffect(() => {
onSearch(debounced);
}, [debounced, onSearch]);
return (
<div className="search-bar">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
aria-label="Search"
/>
{query && (
<button onClick={() => setQuery('')} aria-label="Clear">
×
</button>
)}
</div>
);
}
export default function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchUsers = useCallback(async (search = '') => {
setLoading(true);
try {
const url = search
? `${API_BASE}/users?q=${encodeURIComponent(search)}`
: `${API_BASE}/users`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
setUsers(await res.json());
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, []);
useEffect(() => { fetchUsers(); }, [fetchUsers]);
if (error) {
return (
<div className="error-panel" role="alert">
<p>Failed to load: {error}</p>
<button onClick={() => fetchUsers()}>Retry</button>
</div>
);
}
return (
<section className="user-list">
<SearchBar onSearch={fetchUsers} placeholder="Search users..." />
{loading ? (
<div className="skeleton-grid">
{Array.from({ length: 6 }, (_, i) => (
<div key={i} className="skeleton-card" />
))}
</div>
) : (
<ul>
{users.map((user) => (
<li key={user.id} className="user-card">
<img src={user.avatar} alt={user.name} loading="lazy" />
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
<Badge count={user.notifications} />
</li>
))}
</ul>
)}
</section>
);
}