use the new prettier config and add a network offline loader
Some checks failed
Deploy / deploy (push) Failing after 2m17s

This commit is contained in:
Lee
2024-09-30 22:16:55 +01:00
parent f32c329eb1
commit f38925c113
69 changed files with 517 additions and 1118 deletions

View File

@ -14,8 +14,7 @@ import ScoreSaberPlayer from "@/common/model/player/impl/scoresaber-player";
import { scoresaberService } from "@/common/service/impl/scoresaber";
import { Input } from "@/components/ui/input";
import { clsx } from "clsx";
const INPUT_DEBOUNCE_DELAY = 250; // milliseconds
import { useDebounce } from "@uidotdev/usehooks";
type Props = {
initialScoreData?: ScoreSaberPlayerScoresPageToken;
@ -49,40 +48,19 @@ const scoreAnimation: Variants = {
visible: { opacity: 1, x: 0, transition: { staggerChildren: 0.03 } },
};
export default function PlayerScores({
initialScoreData,
initialSearch,
player,
sort,
page,
}: Props) {
export default function PlayerScores({ initialScoreData, initialSearch, player, sort, page }: Props) {
const { width } = useWindowDimensions();
const controls = useAnimation();
const [pageState, setPageState] = useState<PageState>({ page, sort });
const [previousPage, setPreviousPage] = useState(page);
const [currentScores, setCurrentScores] = useState<
ScoreSaberPlayerScoresPageToken | undefined
>(initialScoreData);
const [searchState, setSearchState] = useState({
query: initialSearch || "",
});
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(
initialSearch || "",
);
const [currentScores, setCurrentScores] = useState<ScoreSaberPlayerScoresPageToken | undefined>(initialScoreData);
const [searchTerm, setSearchTerm] = useState(initialSearch || "");
const debouncedSearchTerm = useDebounce(searchTerm, 250);
const isSearchActive = debouncedSearchTerm.length >= 3;
const [shouldFetch, setShouldFetch] = useState(false); // New state to control fetching
// Debounce the search query
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearchTerm(searchState.query);
}, INPUT_DEBOUNCE_DELAY);
return () => clearTimeout(handler);
}, [searchState.query]);
const {
data: scores,
isError,
@ -98,15 +76,11 @@ export default function PlayerScores({
});
},
staleTime: 30 * 1000, // 30 seconds
enabled:
shouldFetch &&
(debouncedSearchTerm.length >= 3 || debouncedSearchTerm.length === 0), // Only enable if we set shouldFetch to true
enabled: shouldFetch && (debouncedSearchTerm.length >= 3 || debouncedSearchTerm.length === 0), // Only enable if we set shouldFetch to true
});
const handleScoreLoad = useCallback(async () => {
await controls.start(
previousPage >= pageState.page ? "hiddenRight" : "hiddenLeft",
);
await controls.start(previousPage >= pageState.page ? "hiddenRight" : "hiddenLeft");
setCurrentScores(scores);
await controls.start("visible");
}, [scores, controls, previousPage, pageState.page]);
@ -124,15 +98,11 @@ export default function PlayerScores({
useEffect(() => {
const newUrl = `/player/${player.id}/${pageState.sort}/${pageState.page}${isSearchActive ? `?search=${debouncedSearchTerm}` : ""}`;
window.history.replaceState(
{ ...window.history.state, as: newUrl, url: newUrl },
"",
newUrl,
);
window.history.replaceState({ ...window.history.state, as: newUrl, url: newUrl }, "", newUrl);
}, [pageState, debouncedSearchTerm, player.id, isSearchActive]);
const handleSearchChange = (query: string) => {
setSearchState({ query });
setSearchTerm(query);
if (query.length >= 3) {
setShouldFetch(true); // Set to true to trigger fetch
} else {
@ -141,23 +111,18 @@ export default function PlayerScores({
};
const clearSearch = () => {
setSearchState({ query: "" });
setDebouncedSearchTerm(""); // Clear the debounced term
setSearchTerm("");
};
const invalidSearch =
searchState.query.length >= 1 && searchState.query.length < 3;
const invalidSearch = searchTerm.length >= 1 && searchTerm.length < 3;
return (
<Card className="flex gap-1">
<div className="flex flex-col items-center w-full gap-2 relative">
<div className="flex gap-2">
{scoreSort.map((sortOption) => (
{scoreSort.map(sortOption => (
<Button
key={sortOption.value}
variant={
sortOption.value === pageState.sort ? "default" : "outline"
}
variant={sortOption.value === pageState.sort ? "default" : "outline"}
onClick={() => handleSortChange(sortOption.value)}
size="sm"
className="flex items-center gap-1"
@ -174,12 +139,12 @@ export default function PlayerScores({
placeholder="Search..."
className={clsx(
"pr-10", // Add padding right for the clear button
invalidSearch && "border-red-500",
invalidSearch && "border-red-500"
)}
value={searchState.query}
onChange={(e) => handleSearchChange(e.target.value)}
value={searchTerm}
onChange={e => handleSearchChange(e.target.value)}
/>
{searchState.query && ( // Show clear button only if there's a query
{searchTerm && ( // Show clear button only if there's a query
<button
onClick={clearSearch}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-300 hover:brightness-75 transform-gpu transition-all cursor-default"
@ -194,10 +159,7 @@ export default function PlayerScores({
{currentScores && (
<>
<div className="text-center">
{isError ||
(currentScores.playerScores.length === 0 && (
<p>No scores found. Invalid Page or Search?</p>
))}
{isError || (currentScores.playerScores.length === 0 && <p>No scores found. Invalid Page or Search?</p>)}
</div>
<motion.div
@ -216,12 +178,9 @@ export default function PlayerScores({
<Pagination
mobilePagination={width < 768}
page={pageState.page}
totalPages={Math.ceil(
currentScores.metadata.total /
currentScores.metadata.itemsPerPage,
)}
totalPages={Math.ceil(currentScores.metadata.total / currentScores.metadata.itemsPerPage)}
loadingPage={isLoading ? pageState.page : undefined}
onPageChange={(newPage) => {
onPageChange={newPage => {
setPreviousPage(pageState.page);
setPageState({ ...pageState, page: newPage });
setShouldFetch(true); // Set to true to trigger fetch on page change