148 lines
4.2 KiB
JavaScript
148 lines
4.2 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
|
import axios from "axios";
|
|
import { useInView } from "react-intersection-observer";
|
|
import Skeleton from "react-loading-skeleton";
|
|
import "react-loading-skeleton/dist/skeleton.css";
|
|
import CharacterCard from "../components/CharacterCard";
|
|
|
|
const Home = () => {
|
|
const [page, setPage] = useState(1);
|
|
const [data, setData] = useState([]);
|
|
const [filteredData, setFilteredData] = useState([]);
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
const [loading, setLoading] = useState(true);
|
|
const [showSkeleton, setShowSkeleton] = useState(false);
|
|
const [suggestions, setSuggestions] = useState([]);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
|
|
const { ref, inView } = useInView({
|
|
threshold: 0.1,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!hasMore) return;
|
|
|
|
setShowSkeleton(true);
|
|
setTimeout(() => {
|
|
axios
|
|
.get(`https://swapi.py4e.com/api/people/?page=${page}`)
|
|
.then((res) => {
|
|
const results = res.data.results;
|
|
if (results.length > 0) {
|
|
setData((prevData) => [...prevData, ...results]);
|
|
setFilteredData((prevData) => [...prevData, ...results]);
|
|
} else {
|
|
setHasMore(false);
|
|
}
|
|
setLoading(false);
|
|
setShowSkeleton(false);
|
|
})
|
|
.catch(() => {
|
|
setShowSkeleton(false);
|
|
setHasMore(false);
|
|
});
|
|
}, 1000);
|
|
}, [page]);
|
|
|
|
useEffect(() => {
|
|
if (inView && hasMore && !loading) {
|
|
setPage((prevPage) => prevPage + 1);
|
|
}
|
|
}, [inView, hasMore, loading]);
|
|
|
|
const handleSearch = (e) => {
|
|
const query = e.target.value.toLowerCase();
|
|
setSearchQuery(query);
|
|
|
|
if (query === "") {
|
|
setFilteredData(data);
|
|
setSuggestions([]);
|
|
return;
|
|
}
|
|
|
|
const filtered = data.filter((character) =>
|
|
character.name.toLowerCase().includes(query)
|
|
);
|
|
setFilteredData(filtered);
|
|
|
|
const autoSuggestions = data
|
|
.map((character) => character.name)
|
|
.filter((name) => name.toLowerCase().includes(query))
|
|
.slice(0, 5);
|
|
setSuggestions(autoSuggestions);
|
|
};
|
|
|
|
const handleSuggestionClick = (suggestion) => {
|
|
setSearchQuery(suggestion);
|
|
setFilteredData(
|
|
data.filter(
|
|
(character) => character.name.toLowerCase() === suggestion.toLowerCase()
|
|
)
|
|
);
|
|
setSuggestions([]);
|
|
};
|
|
|
|
return (
|
|
<div className="main">
|
|
<div className="header-container">
|
|
<div className="header">
|
|
{loading ? (
|
|
<Skeleton width={120} height={60} />
|
|
) : (
|
|
<img src="/images/logo.png" alt="Logo" />
|
|
)}
|
|
</div>
|
|
|
|
<div className="search-container">
|
|
{loading ? (
|
|
<Skeleton width={280} height={52} />
|
|
) : (
|
|
<input
|
|
type="text"
|
|
placeholder="Search by name..."
|
|
value={searchQuery}
|
|
onChange={handleSearch}
|
|
className="search-input"
|
|
/>
|
|
)}
|
|
|
|
{suggestions.length > 0 && (
|
|
<ul className="suggestions">
|
|
{suggestions.map((suggestion, index) => (
|
|
<li
|
|
key={index}
|
|
className="suggestion-item"
|
|
onClick={() => handleSuggestionClick(suggestion)}
|
|
>
|
|
{suggestion}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<CharacterCard data={filteredData} />
|
|
{showSkeleton && hasMore && (
|
|
<ul className="skeleton-character-container">
|
|
{[...Array.from({ length: 5 })].map((_, index) => (
|
|
<li className="skeleton-character-card skeleton" key={index}>
|
|
<div className="skeleton-character-details">
|
|
<div className="skeleton-line skeleton-title"></div>
|
|
<div className="skeleton-line"></div>
|
|
<div className="skeleton-line"></div>
|
|
<div className="skeleton-line"></div>
|
|
<div className="skeleton-line"></div>
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
|
|
{hasMore && <div ref={ref}></div>}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Home;
|