mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: search players for current season
This commit is contained in:
1 parent
d08949c597
commit
81aec6bad2
2 files changed
+197
-63
No files matched your search
+194
-60
@@ -31,10 +31,15 @@ import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo';
|
|||||||
import getRatingsInfo from '../../../core/blitz/getRatingsInfo';
|
import getRatingsInfo from '../../../core/blitz/getRatingsInfo';
|
||||||
import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague';
|
import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague';
|
||||||
import { getRatingsNeighbors } from '../../../core/blitz/getRatingsNeighbors';
|
import { getRatingsNeighbors } from '../../../core/blitz/getRatingsNeighbors';
|
||||||
import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions';
|
import regionToRegionSubdomain from '../../../core/blitz/regionToRegionSubdomain';
|
||||||
|
import {
|
||||||
|
AccountList,
|
||||||
|
AccountListItem,
|
||||||
|
} from '../../../core/blitz/searchPlayersAcrossRegions';
|
||||||
import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
||||||
import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo';
|
import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo';
|
||||||
import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard';
|
import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard';
|
||||||
|
import { withCORSProxy } from '../../../core/blitzkrieg/withCORSProxy';
|
||||||
import { noArrows } from './page.css';
|
import { noArrows } from './page.css';
|
||||||
|
|
||||||
const ROWS_OPTIONS = [5, 10, 15, 25, 30];
|
const ROWS_OPTIONS = [5, 10, 15, 25, 30];
|
||||||
@@ -83,30 +88,9 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
const positionInput = useRef<HTMLInputElement>(null);
|
const positionInput = useRef<HTMLInputElement>(null);
|
||||||
const scoreInput = useRef<HTMLInputElement>(null);
|
const scoreInput = useRef<HTMLInputElement>(null);
|
||||||
const [searchResults, setSearchResults] = useState<AccountList | undefined>(
|
const [searchResults, setSearchResults] = useState<
|
||||||
undefined,
|
(AccountListItem & { number: number })[] | undefined
|
||||||
);
|
>(undefined);
|
||||||
const handleSearchPlayerChange = debounce(
|
|
||||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const trimmedSearch = event.target.value.trim();
|
|
||||||
|
|
||||||
if (trimmedSearch) {
|
|
||||||
const encodedSearch = encodeURIComponent(trimmedSearch);
|
|
||||||
const accountList = await fetchBlitz<AccountList>(
|
|
||||||
`https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`,
|
|
||||||
);
|
|
||||||
|
|
||||||
setSearchResults(
|
|
||||||
accountList.filter(
|
|
||||||
(searchedPlayer) => players && searchedPlayer.account_id in players,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setSearchResults(undefined);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
500,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// league caching
|
// league caching
|
||||||
@@ -168,6 +152,12 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (players !== null && 0 in players) {
|
||||||
|
cacheNeighbors(players[0].id, rowsPerPage * 3);
|
||||||
|
}
|
||||||
|
}, [players !== null && 0 in players]);
|
||||||
|
|
||||||
async function cacheNeighbors(id: number, size: number) {
|
async function cacheNeighbors(id: number, size: number) {
|
||||||
const { neighbors } = await getRatingsNeighbors(region, id, size);
|
const { neighbors } = await getRatingsNeighbors(region, id, size);
|
||||||
|
|
||||||
@@ -292,6 +282,7 @@ export default function Page() {
|
|||||||
(ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) /
|
(ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) /
|
||||||
rowsPerPage,
|
rowsPerPage,
|
||||||
);
|
);
|
||||||
|
const [searchLoading, setSearchLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPage(0);
|
setPage(0);
|
||||||
@@ -431,7 +422,6 @@ export default function Page() {
|
|||||||
<Flex gap="4" justify="center">
|
<Flex gap="4" justify="center">
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
ref={scoreInput}
|
ref={scoreInput}
|
||||||
onChange={handleSearchPlayerChange}
|
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Type a score..."
|
placeholder="Type a score..."
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
@@ -465,7 +455,6 @@ export default function Page() {
|
|||||||
<Flex gap="4" justify="center">
|
<Flex gap="4" justify="center">
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
ref={positionInput}
|
ref={positionInput}
|
||||||
onChange={handleSearchPlayerChange}
|
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Type a position..."
|
placeholder="Type a position..."
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
@@ -576,48 +565,193 @@ export default function Page() {
|
|||||||
</TextField.Slot>
|
</TextField.Slot>
|
||||||
|
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
onChange={handleSearchPlayerChange}
|
onChange={debounce(
|
||||||
|
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const trimmedSearch = event.target.value.trim();
|
||||||
|
const encodedSearch = encodeURIComponent(trimmedSearch);
|
||||||
|
|
||||||
|
if (trimmedSearch) {
|
||||||
|
if (season === null) {
|
||||||
|
setSearchLoading(true);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
withCORSProxy(
|
||||||
|
`https://${regionToRegionSubdomain(
|
||||||
|
region,
|
||||||
|
)}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const accountList =
|
||||||
|
(await response.json()) as Record<
|
||||||
|
number,
|
||||||
|
{
|
||||||
|
spa_id: number;
|
||||||
|
nickname: string;
|
||||||
|
clan_tag: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
skip: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
skip: false;
|
||||||
|
mmr: number;
|
||||||
|
season_number: number;
|
||||||
|
calibrationBattlesLeft: number;
|
||||||
|
number: number;
|
||||||
|
updated_at: string;
|
||||||
|
neighbors: [];
|
||||||
|
}
|
||||||
|
)
|
||||||
|
>;
|
||||||
|
const values = Object.values(accountList).filter(
|
||||||
|
(searchedPlayer) => !searchedPlayer.skip,
|
||||||
|
);
|
||||||
|
const valuesWithTypes =
|
||||||
|
values as ((typeof values)[number] & {
|
||||||
|
skip: false;
|
||||||
|
})[];
|
||||||
|
|
||||||
|
setPlayers(
|
||||||
|
produce((draft: typeof players) => {
|
||||||
|
if (draft) {
|
||||||
|
values.map((player) => {
|
||||||
|
const playerWithTypes =
|
||||||
|
player as typeof player & { skip: false };
|
||||||
|
draft[playerWithTypes.number - 1] = {
|
||||||
|
id: playerWithTypes.spa_id,
|
||||||
|
score: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
useUsernameCache.setState(
|
||||||
|
produce((draft: typeof usernameCache) => {
|
||||||
|
valuesWithTypes.forEach((searchedPlayer) => {
|
||||||
|
if (searchedPlayer.nickname) {
|
||||||
|
draft[region][searchedPlayer.spa_id] =
|
||||||
|
searchedPlayer.nickname;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
useClanCache.setState(
|
||||||
|
produce((draft: typeof clanCache) => {
|
||||||
|
valuesWithTypes.forEach((searchedPlayer) => {
|
||||||
|
if (searchedPlayer.clan_tag) {
|
||||||
|
draft[region][searchedPlayer.spa_id] =
|
||||||
|
searchedPlayer.clan_tag;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
setSearchResults(
|
||||||
|
valuesWithTypes.map((searchedPlayer) => ({
|
||||||
|
account_id: searchedPlayer.spa_id,
|
||||||
|
nickname: searchedPlayer.nickname,
|
||||||
|
number: searchedPlayer.number,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
setSearchLoading(false);
|
||||||
|
} else {
|
||||||
|
setSearchLoading(true);
|
||||||
|
const accountList = await fetchBlitz<AccountList>(
|
||||||
|
`https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`,
|
||||||
|
);
|
||||||
|
const playerKeys = Object.keys(players!);
|
||||||
|
|
||||||
|
setSearchResults(
|
||||||
|
accountList
|
||||||
|
.filter(
|
||||||
|
(searchedPlayer) =>
|
||||||
|
players &&
|
||||||
|
searchedPlayer.account_id in players,
|
||||||
|
)
|
||||||
|
.map((searchedPlayer) => ({
|
||||||
|
...searchedPlayer,
|
||||||
|
number: parseInt(
|
||||||
|
playerKeys.find(
|
||||||
|
(index) =>
|
||||||
|
players![parseInt(index)].id ===
|
||||||
|
searchedPlayer.account_id,
|
||||||
|
)!,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
setSearchLoading(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setSearchResults(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
)}
|
||||||
placeholder="Search for player..."
|
placeholder="Search for player..."
|
||||||
/>
|
/>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
|
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
{searchResults?.map((player) => (
|
{!searchLoading &&
|
||||||
<Dialog.Close>
|
searchResults?.map((player) => (
|
||||||
<Button
|
<Dialog.Close>
|
||||||
key={player.account_id}
|
<Button
|
||||||
variant="ghost"
|
key={player.account_id}
|
||||||
onClick={() => {
|
variant="ghost"
|
||||||
if (!ratingsInfo || ratingsInfo.detail) return;
|
onClick={() => {
|
||||||
let playerIndex = -1;
|
if (season === null) {
|
||||||
|
setPage(
|
||||||
|
Math.floor((player.number - 1) / rowsPerPage),
|
||||||
|
);
|
||||||
|
cacheNeighbors(
|
||||||
|
player.account_id,
|
||||||
|
rowsPerPage * 3,
|
||||||
|
);
|
||||||
|
setHighlightedPlayerId(player.account_id);
|
||||||
|
} else {
|
||||||
|
if (!ratingsInfo || ratingsInfo.detail) return;
|
||||||
|
let playerIndex = -1;
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let index = 0;
|
let index = 0;
|
||||||
index < ratingsInfo.count;
|
index < ratingsInfo.count;
|
||||||
index++
|
index++
|
||||||
) {
|
) {
|
||||||
if (players![index].id === player.account_id) {
|
if (players![index].id === player.account_id) {
|
||||||
playerIndex = index;
|
playerIndex = index;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerPage = Math.floor(
|
||||||
|
playerIndex! / rowsPerPage,
|
||||||
|
);
|
||||||
|
|
||||||
|
setPage(playerPage);
|
||||||
|
setHighlightedPlayerId(player.account_id);
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
|
>
|
||||||
|
{player.nickname}
|
||||||
|
</Button>
|
||||||
|
</Dialog.Close>
|
||||||
|
))}
|
||||||
|
|
||||||
const playerPage = Math.floor(
|
{!searchLoading && searchResults?.length === 0 && (
|
||||||
playerIndex! / rowsPerPage,
|
|
||||||
);
|
|
||||||
|
|
||||||
setPage(playerPage);
|
|
||||||
setHighlightedPlayerId(player.account_id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{player.nickname}
|
|
||||||
</Button>
|
|
||||||
</Dialog.Close>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{searchResults?.length === 0 && (
|
|
||||||
<Button disabled variant="ghost">
|
<Button disabled variant="ghost">
|
||||||
No players found in leaderboard
|
No players found in leaderboard (try typing in the whole
|
||||||
|
name because Wargaming's search engine is finicky)
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searchLoading && searchResults === undefined && (
|
||||||
|
<Button disabled variant="ghost">
|
||||||
|
Type a username in the text field above
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{searchLoading && (
|
||||||
|
<Button disabled variant="ghost">
|
||||||
|
Searching...
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { Region } from '../../constants/regions';
|
|||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
|
|
||||||
interface Account {
|
export interface AccountListItem {
|
||||||
nickname: string;
|
nickname: string;
|
||||||
account_id: number;
|
account_id: number;
|
||||||
}
|
}
|
||||||
export type AccountList = Account[];
|
export type AccountList = AccountListItem[];
|
||||||
export type AccountListWithServer = (Account & {
|
export type AccountListWithServer = (AccountListItem & {
|
||||||
region: Region;
|
region: Region;
|
||||||
})[];
|
})[];
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user