mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: session persistance
This commit is contained in:
1 parent
57f6e928b7
commit
018783bb47
12 files changed
+121
-29
No files matched your search
Vendored
+2
-1
@@ -39,7 +39,8 @@
|
|||||||
"vitepress",
|
"vitepress",
|
||||||
"wargaming",
|
"wargaming",
|
||||||
"webp",
|
"webp",
|
||||||
"winrate"
|
"winrate",
|
||||||
|
"zustand"
|
||||||
],
|
],
|
||||||
"deno.enable": false,
|
"deno.enable": false,
|
||||||
"deno.unstable": false
|
"deno.unstable": false
|
||||||
|
|||||||
+2
-1
@@ -48,7 +48,8 @@
|
|||||||
"satori": "^0.10.4",
|
"satori": "^0.10.4",
|
||||||
"vite-node": "^0.34.4",
|
"vite-node": "^0.34.4",
|
||||||
"vitepress": "^1.0.0-rc.12",
|
"vitepress": "^1.0.0-rc.12",
|
||||||
"vitepress-plugin-auto-sidebar": "^1.1.0"
|
"vitepress-plugin-auto-sidebar": "^1.1.0",
|
||||||
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/ts-reset": "^0.5.1",
|
"@total-typescript/ts-reset": "^0.5.1",
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1,16 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { Region } from '../../../../../constants/regions';
|
||||||
|
import getTankStats from '../../../../../core/blitz/getTankStats';
|
||||||
|
import { NormalizedTankStats } from '../../../../../types/tanksStats';
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const region = request.nextUrl.searchParams.get('region') as Region;
|
||||||
|
const id = parseInt(request.nextUrl.searchParams.get('id')!);
|
||||||
|
const tankStats = await getTankStats(region, id);
|
||||||
|
const normalized = tankStats.reduce<NormalizedTankStats>(
|
||||||
|
(accumulator, tank) => ({ ...accumulator, [tank.tank_id]: tank }),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json(normalized);
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ import { SearchBar } from '../../../components/SearchBar';
|
|||||||
import { REGION_NAMES } from '../../../constants/regions';
|
import { REGION_NAMES } from '../../../constants/regions';
|
||||||
import { AccountListWithServer } from '../../../core/blitz/listPlayers';
|
import { AccountListWithServer } from '../../../core/blitz/listPlayers';
|
||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
|
import { useSession } from '../../../stores/session';
|
||||||
|
import { NormalizedTankStats } from '../../../types/tanksStats';
|
||||||
import * as styles from './page.css';
|
import * as styles from './page.css';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
@@ -21,7 +23,7 @@ export default function Page() {
|
|||||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (event.target.value) {
|
if (event.target.value) {
|
||||||
const players = await fetch(
|
const players = await fetch(
|
||||||
`/api/list/players?search=${event.target.value}`,
|
`/api/search/players?search=${event.target.value}`,
|
||||||
).then((response) => response.json() as Promise<AccountListWithServer>);
|
).then((response) => response.json() as Promise<AccountListWithServer>);
|
||||||
|
|
||||||
setSearchResults(players);
|
setSearchResults(players);
|
||||||
@@ -31,6 +33,7 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
500,
|
500,
|
||||||
);
|
);
|
||||||
|
const initialSession = useSession.getState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper>
|
<PageWrapper>
|
||||||
@@ -39,6 +42,9 @@ export default function Page() {
|
|||||||
<SearchBar
|
<SearchBar
|
||||||
ref={input}
|
ref={input}
|
||||||
style={{ width: '100%', boxSizing: 'border-box' }}
|
style={{ width: '100%', boxSizing: 'border-box' }}
|
||||||
|
defaultValue={
|
||||||
|
initialSession.isTracking ? initialSession.nickname : undefined
|
||||||
|
}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (event.target.value) {
|
if (event.target.value) {
|
||||||
setShowSearchResults(true);
|
setShowSearchResults(true);
|
||||||
@@ -93,13 +99,30 @@ export default function Page() {
|
|||||||
No results
|
No results
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
searchResults?.map(({ account_id, nickname, region }) => (
|
searchResults?.map(({ account_id: id, nickname, region }) => (
|
||||||
<button
|
<button
|
||||||
key={account_id}
|
key={id}
|
||||||
className={styles.searchButton}
|
className={styles.searchButton}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowSearchResults(false);
|
setShowSearchResults(false);
|
||||||
if (input.current) input.current.value = nickname;
|
input.current!.value = nickname;
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
`/api/stats/normalized/tanks?id=${id}®ion=${region}`,
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(response) =>
|
||||||
|
response.json() as Promise<NormalizedTankStats>,
|
||||||
|
)
|
||||||
|
.then((tankStats) =>
|
||||||
|
useSession.setState({
|
||||||
|
isTracking: true,
|
||||||
|
id,
|
||||||
|
region,
|
||||||
|
nickname,
|
||||||
|
tankStats,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -124,7 +147,24 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<Button className={styles.toolbarButton} color="dangerous">
|
<Button
|
||||||
|
className={styles.toolbarButton}
|
||||||
|
color="dangerous"
|
||||||
|
onClick={async () => {
|
||||||
|
const session = useSession.getState();
|
||||||
|
|
||||||
|
if (!session.isTracking) return;
|
||||||
|
|
||||||
|
const { id, region } = session;
|
||||||
|
const tankStats = await fetch(
|
||||||
|
`/api/stats/normalized/tanks?id=${id}®ion=${region}`,
|
||||||
|
).then(
|
||||||
|
(response) => response.json() as Promise<NormalizedTankStats>,
|
||||||
|
);
|
||||||
|
|
||||||
|
useSession.setState({ tankStats });
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ReloadIcon /> Reset
|
<ReloadIcon /> Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={styles.toolbarButton}>
|
<Button className={styles.toolbarButton}>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { CYCLIC_API } from '../constants/cyclic';
|
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { REGION_NAMES, Region } from '../../constants/regions';
|
import { REGION_NAMES, Region } from '../../constants/regions';
|
||||||
import { TanksStats } from '../../types/tanksStats';
|
import { TankStats } from '../../types/tanksStats';
|
||||||
import { secrets } from '../node/secrets';
|
import { secrets } from '../node/secrets';
|
||||||
import throwError from '../node/throwError';
|
import throwError from '../node/throwError';
|
||||||
import getWargamingResponse from './getWargamingResponse';
|
import getWargamingResponse from './getWargamingResponse';
|
||||||
|
|
||||||
export default async function getTankStats(server: Region, id: number) {
|
export default async function getTankStats(region: Region, id: number) {
|
||||||
const tankStats = await getWargamingResponse<TanksStats>(
|
const tankStats = await getWargamingResponse<TankStats>(
|
||||||
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${secrets.WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tankStats[id] === null) {
|
if (tankStats[id] === null) {
|
||||||
throw throwError(
|
throw throwError(
|
||||||
'No tank stats available',
|
'No tank stats available',
|
||||||
`Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_NAMES[server]} server.`,
|
`Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_NAMES[region]} server.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const secrets = SECRET_KEYS.reduce<Partial<Record<SECRET, string>>>(
|
|||||||
...accumulator,
|
...accumulator,
|
||||||
|
|
||||||
get [key](): string {
|
get [key](): string {
|
||||||
if (!(key in process.env)) throw new Error(`Key ${key} not provided`);
|
if (!(key in process.env)) throw new Error(`Secret ${key} not provided`);
|
||||||
return process.env[key]!;
|
return process.env[key]!;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
|
import { Region } from '../constants/regions';
|
||||||
|
import { NormalizedTankStats } from '../types/tanksStats';
|
||||||
|
|
||||||
|
type Session =
|
||||||
|
| {
|
||||||
|
isTracking: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isTracking: true;
|
||||||
|
|
||||||
|
region: Region;
|
||||||
|
id: number;
|
||||||
|
nickname: string;
|
||||||
|
tankStats: NormalizedTankStats;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSession = create<Session>()(
|
||||||
|
devtools(persist((set) => ({ isTracking: false }), { name: 'session' })),
|
||||||
|
);
|
||||||
+11
-13
@@ -46,18 +46,16 @@ export interface ClanStats {
|
|||||||
dropped_capture_points: number;
|
dropped_capture_points: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccountInfo = {
|
export interface AccountInfo {
|
||||||
[accountId: number]: {
|
[accountId: number]: {
|
||||||
statistics: {
|
clan: ClanStats;
|
||||||
clan: ClanStats;
|
all: AllStats;
|
||||||
all: AllStats;
|
frags: null;
|
||||||
frags: null;
|
|
||||||
};
|
|
||||||
account_id: number;
|
|
||||||
created_at: number;
|
|
||||||
updated_at: number;
|
|
||||||
private: null;
|
|
||||||
last_battle_time: number;
|
|
||||||
nickname: string;
|
|
||||||
};
|
};
|
||||||
};
|
account_id: number;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
private: null;
|
||||||
|
last_battle_time: number;
|
||||||
|
nickname: string;
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ export interface IndividualTankStats {
|
|||||||
tank_id: number;
|
tank_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TanksStats = {
|
export interface TankStats {
|
||||||
[accountId: number]: IndividualTankStats[];
|
[accountId: number]: IndividualTankStats[];
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export interface NormalizedTankStats {
|
||||||
|
[tankId: number]: IndividualTankStats;
|
||||||
|
}
|
||||||
@@ -3348,6 +3348,11 @@ update-browserslist-db@^1.0.11:
|
|||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
|
[email protected]:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
|
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||||
|
|
||||||
[email protected]:
|
[email protected]:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
@@ -3527,3 +3532,10 @@ zod@^3.22.2:
|
|||||||
version "3.22.2"
|
version "3.22.2"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"
|
||||||
integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==
|
integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg==
|
||||||
|
|
||||||
|
zustand@^4.4.1:
|
||||||
|
version "4.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.4.1.tgz#0cd3a3e4756f21811bd956418fdc686877e8b3b0"
|
||||||
|
integrity sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==
|
||||||
|
dependencies:
|
||||||
|
use-sync-external-store "1.2.0"
|
||||||
Reference in new issue
Block a user