feat : improve game loading flow
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
import { getChessComUserRecentGames } from "@/lib/chessCom";
|
||||
import { capitalize } from "@/lib/helpers";
|
||||
import { ChessComGame } from "@/types/chessCom";
|
||||
import {
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
@@ -11,42 +10,31 @@ import {
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { boardOrientationAtom } from "../analysis/states";
|
||||
import { useDebounce } from "@/hooks/useDebounce";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
interface Props {
|
||||
onSelect: (pgn: string) => void;
|
||||
}
|
||||
|
||||
export default function ChessComInput({ onSelect }: Props) {
|
||||
const [requestCount, setRequestCount] = useState(0);
|
||||
const [chessComUsername, setChessComUsername] = useLocalStorage(
|
||||
"chesscom-username",
|
||||
""
|
||||
);
|
||||
const debouncedUsername = useDebounce(chessComUsername, 200);
|
||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||
const [games, setGames] = useState<ChessComGame[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chessComUsername) {
|
||||
setGames([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(
|
||||
async () => {
|
||||
const games = await getChessComUserRecentGames(chessComUsername);
|
||||
setGames(games);
|
||||
},
|
||||
requestCount === 0 ? 0 : 500
|
||||
);
|
||||
|
||||
setRequestCount((prev) => prev + 1);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [chessComUsername]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
const {
|
||||
data: games,
|
||||
isFetching,
|
||||
isError,
|
||||
} = useQuery({
|
||||
queryKey: ["CCUserGames", debouncedUsername],
|
||||
enabled: !!debouncedUsername,
|
||||
queryFn: () => getChessComUserRecentGames(debouncedUsername ?? ""),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -68,38 +56,48 @@ export default function ChessComInput({ onSelect }: Props) {
|
||||
minHeight={100}
|
||||
size={12}
|
||||
>
|
||||
{games.map((game) => (
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
setBoardOrientation(
|
||||
chessComUsername.toLowerCase() !==
|
||||
game.black.username.toLowerCase()
|
||||
);
|
||||
onSelect(game.pgn);
|
||||
}}
|
||||
style={{ width: 350, maxWidth: 350 }}
|
||||
key={game.uuid}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`${capitalize(game.white.username) || "White"} (${
|
||||
game.white.rating || "?"
|
||||
}) vs ${capitalize(game.black.username) || "Black"} (${
|
||||
game.black.rating || "?"
|
||||
})`}
|
||||
secondary={`${capitalize(game.time_class)} played at ${new Date(
|
||||
game.end_time * 1000
|
||||
)
|
||||
.toLocaleString()
|
||||
.slice(0, -3)}`}
|
||||
slotProps={{
|
||||
primary: { noWrap: true },
|
||||
secondary: { noWrap: true },
|
||||
{isFetching ? (
|
||||
<CircularProgress />
|
||||
) : isError ? (
|
||||
<span style={{ color: "salmon" }}>
|
||||
User not found. Please check your username.
|
||||
</span>
|
||||
) : !games?.length ? (
|
||||
<span style={{ color: "salmon" }}>
|
||||
No games found. Please check your username.
|
||||
</span>
|
||||
) : (
|
||||
games.map((game) => (
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
setBoardOrientation(
|
||||
chessComUsername.toLowerCase() !==
|
||||
game.black.username.toLowerCase()
|
||||
);
|
||||
onSelect(game.pgn);
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
|
||||
{games.length === 0 && <CircularProgress />}
|
||||
style={{ width: 350, maxWidth: 350 }}
|
||||
key={game.uuid}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`${capitalize(game.white.username) || "White"} (${
|
||||
game.white.rating || "?"
|
||||
}) vs ${capitalize(game.black.username) || "Black"} (${
|
||||
game.black.rating || "?"
|
||||
})`}
|
||||
secondary={`${capitalize(game.time_class)} played at ${new Date(
|
||||
game.end_time * 1000
|
||||
)
|
||||
.toLocaleString()
|
||||
.slice(0, -3)}`}
|
||||
slotProps={{
|
||||
primary: { noWrap: true },
|
||||
secondary: { noWrap: true },
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
import { getLichessUserRecentGames } from "@/lib/lichess";
|
||||
import { capitalize } from "@/lib/helpers";
|
||||
import { LichessGame } from "@/types/lichess";
|
||||
import {
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
@@ -10,43 +9,32 @@ import {
|
||||
ListItemText,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { boardOrientationAtom } from "../analysis/states";
|
||||
import { useDebounce } from "@/hooks/useDebounce";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
interface Props {
|
||||
onSelect: (pgn: string) => void;
|
||||
}
|
||||
|
||||
export default function LichessInput({ onSelect }: Props) {
|
||||
const [requestCount, setRequestCount] = useState(0);
|
||||
const [lichessUsername, setLichessUsername] = useLocalStorage(
|
||||
"lichess-username",
|
||||
""
|
||||
);
|
||||
const debouncedUsername = useDebounce(lichessUsername, 200);
|
||||
const setBoardOrientation = useSetAtom(boardOrientationAtom);
|
||||
const [games, setGames] = useState<LichessGame[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lichessUsername) {
|
||||
setGames([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(
|
||||
async () => {
|
||||
const games = await getLichessUserRecentGames(lichessUsername);
|
||||
setGames(games);
|
||||
},
|
||||
requestCount === 0 ? 0 : 500
|
||||
);
|
||||
|
||||
setRequestCount((prev) => prev + 1);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [lichessUsername]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
const {
|
||||
data: games,
|
||||
isFetching,
|
||||
isError,
|
||||
} = useQuery({
|
||||
queryKey: ["LichessUserGames", debouncedUsername],
|
||||
enabled: !!debouncedUsername,
|
||||
queryFn: () => getLichessUserRecentGames(debouncedUsername ?? ""),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -68,40 +56,50 @@ export default function LichessInput({ onSelect }: Props) {
|
||||
minHeight={100}
|
||||
size={12}
|
||||
>
|
||||
{games.map((game) => (
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
setBoardOrientation(
|
||||
lichessUsername.toLowerCase() !==
|
||||
game.players.black.user?.name.toLowerCase()
|
||||
);
|
||||
onSelect(game.pgn);
|
||||
}}
|
||||
style={{ width: 350, maxWidth: 350 }}
|
||||
key={game.id}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`${
|
||||
capitalize(game.players.white.user?.name || "white") ||
|
||||
"White"
|
||||
} (${game.players?.white?.rating || "?"}) vs ${
|
||||
capitalize(game.players.black.user?.name || "black") ||
|
||||
"Black"
|
||||
} (${game.players?.black?.rating || "?"})`}
|
||||
secondary={`${capitalize(game.speed)} played at ${new Date(
|
||||
game.lastMoveAt
|
||||
)
|
||||
.toLocaleString()
|
||||
.slice(0, -3)}`}
|
||||
slotProps={{
|
||||
primary: { noWrap: true },
|
||||
secondary: { noWrap: true },
|
||||
{isFetching ? (
|
||||
<CircularProgress />
|
||||
) : isError ? (
|
||||
<span style={{ color: "salmon" }}>
|
||||
User not found. Please check your username.
|
||||
</span>
|
||||
) : !games?.length ? (
|
||||
<span style={{ color: "salmon" }}>
|
||||
No games found. Please check your username.
|
||||
</span>
|
||||
) : (
|
||||
games.map((game) => (
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
setBoardOrientation(
|
||||
lichessUsername.toLowerCase() !==
|
||||
game.players.black.user?.name.toLowerCase()
|
||||
);
|
||||
onSelect(game.pgn);
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
|
||||
{games.length === 0 && <CircularProgress />}
|
||||
style={{ width: 350, maxWidth: 350 }}
|
||||
key={game.id}
|
||||
>
|
||||
<ListItemText
|
||||
primary={`${
|
||||
capitalize(game.players.white.user?.name || "white") ||
|
||||
"White"
|
||||
} (${game.players?.white?.rating || "?"}) vs ${
|
||||
capitalize(game.players.black.user?.name || "black") ||
|
||||
"Black"
|
||||
} (${game.players?.black?.rating || "?"})`}
|
||||
secondary={`${capitalize(game.speed)} played at ${new Date(
|
||||
game.lastMoveAt
|
||||
)
|
||||
.toLocaleString()
|
||||
.slice(0, -3)}`}
|
||||
slotProps={{
|
||||
primary: { noWrap: true },
|
||||
secondary: { noWrap: true },
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -104,7 +104,10 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
displayEmpty
|
||||
input={<OutlinedInput label="Game origin" />}
|
||||
value={gameOrigin ?? ""}
|
||||
onChange={(e) => setGameOrigin(e.target.value as GameOrigin)}
|
||||
onChange={(e) => {
|
||||
setGameOrigin(e.target.value as GameOrigin);
|
||||
setParsingError("");
|
||||
}}
|
||||
>
|
||||
{Object.values(GameOrigin).map((origin) => (
|
||||
<MenuItem key={origin} value={origin}>
|
||||
@@ -128,7 +131,7 @@ export default function NewGameDialog({ open, onClose, setGame }: Props) {
|
||||
|
||||
{parsingError && (
|
||||
<FormControl fullWidth>
|
||||
<Typography color="red" textAlign="center" marginTop={1}>
|
||||
<Typography color="salmon" textAlign="center" marginTop={1}>
|
||||
{parsingError}
|
||||
</Typography>
|
||||
</FormControl>
|
||||
|
||||
Reference in New Issue
Block a user