mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: more login progress
This commit is contained in:
1 parent
1e64cb9e7d
commit
513105a624
18 files changed
+290
-295
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.22.6",
|
||||
"version": "2.0.0",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
|
||||
@@ -27,7 +27,7 @@ export * from './modelDefinitions';
|
||||
export * from './normalizeBoundingBox';
|
||||
// export * from './parseBkni';
|
||||
// export * from './patientFetch';
|
||||
// export * from './patientFetchJSON';
|
||||
export * from './patientFetchJSON';
|
||||
export * from './progressiveStat';
|
||||
export * from './provisionDefinitions';
|
||||
export * from './resolveDpm';
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import {
|
||||
BkrlBaseEntry,
|
||||
BkrlFormat,
|
||||
BkrlSuperset1Entry,
|
||||
BkrlWriteStream,
|
||||
CdonValue,
|
||||
CdonWriteStream,
|
||||
getAccountInfo,
|
||||
patientFetchJSON,
|
||||
RatingInfo,
|
||||
RatingNeighbors,
|
||||
RatingPlayer,
|
||||
RegionSubdomain,
|
||||
regionSubdomainToRegion,
|
||||
superCompress,
|
||||
} from '@blitzkit/core';
|
||||
import { chunk } from 'lodash-es';
|
||||
import { argv } from 'process';
|
||||
import { commitAssets } from './core/github/commitAssets';
|
||||
import { FileChange } from './core/github/commitMultipleFiles';
|
||||
|
||||
/*
|
||||
* Central North American Time (UTC-5): use 0 5 * * *.
|
||||
* Central European Time (UTC+1): use 0 23 * * *.
|
||||
* Central Asia Standard Time (UTC+7): use 0 17 * * *.
|
||||
*/
|
||||
|
||||
const production = argv.includes('--production');
|
||||
const target = argv
|
||||
.find((arg) => arg.startsWith('--target='))
|
||||
?.split('=')[1] as 'midnight' | 'latest' | undefined;
|
||||
const regionSubdomains = argv
|
||||
.find((arg) => arg.startsWith('--region='))
|
||||
?.split('=')[1];
|
||||
|
||||
if (!target) throw new Error('Target parameter not specified');
|
||||
if (!regionSubdomains) throw new Error('Region parameter not specified');
|
||||
|
||||
regionSubdomains.split(',').forEach(async (regionSubdomain) => {
|
||||
const region = regionSubdomainToRegion(regionSubdomain as RegionSubdomain);
|
||||
|
||||
console.log(`Building ${target} rating leaderboard for ${region}`);
|
||||
|
||||
const NEIGHBORS = 2 ** 8;
|
||||
const PLAYERS = await patientFetchJSON<RatingInfo>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||
).then((data) => (data.detail === undefined ? data.count : undefined));
|
||||
|
||||
if (PLAYERS === undefined) {
|
||||
throw new Error('No current rating season running');
|
||||
}
|
||||
|
||||
const players: Record<number, RatingPlayer> = {};
|
||||
const leaderboard: BkrlBaseEntry[] = [];
|
||||
let registered = 0;
|
||||
|
||||
async function branchFromPlayer(id: number) {
|
||||
const { neighbors } = await patientFetchJSON<RatingNeighbors>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`,
|
||||
);
|
||||
|
||||
const firstPlayer = neighbors[0];
|
||||
const lastPlayer = neighbors[neighbors.length - 1];
|
||||
|
||||
const branches: Promise<void>[] = [];
|
||||
if (!players[firstPlayer.number])
|
||||
branches.push(branchFromPlayer(firstPlayer.spa_id));
|
||||
if (!players[lastPlayer.number])
|
||||
branches.push(branchFromPlayer(lastPlayer.spa_id));
|
||||
|
||||
neighbors.forEach((neighbor) => {
|
||||
if (players[neighbor.number]) return;
|
||||
|
||||
players[neighbor.number] = neighbor;
|
||||
registered++;
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Registered ${registered} / ${PLAYERS} (${(
|
||||
(registered / PLAYERS!) *
|
||||
100
|
||||
).toFixed(2)}%)`,
|
||||
);
|
||||
|
||||
await Promise.all(branches);
|
||||
}
|
||||
|
||||
async function branchFromLeague(id: number) {
|
||||
const { result } = await patientFetchJSON<{ result: RatingPlayer[] }>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
|
||||
);
|
||||
|
||||
const firstPlayer = result[0];
|
||||
const lastPlayer = result.at(-1)!;
|
||||
|
||||
await Promise.all([
|
||||
branchFromPlayer(firstPlayer.spa_id),
|
||||
branchFromPlayer(lastPlayer.spa_id),
|
||||
]);
|
||||
}
|
||||
|
||||
console.log('Branching from leagues...');
|
||||
await Promise.all([
|
||||
branchFromLeague(0),
|
||||
branchFromLeague(1),
|
||||
branchFromLeague(2),
|
||||
branchFromLeague(3),
|
||||
branchFromLeague(4),
|
||||
]);
|
||||
|
||||
console.log('Converting to an array...');
|
||||
for (let index = 0; index < PLAYERS; index++) {
|
||||
const listing = players[index + 1];
|
||||
if (listing) {
|
||||
leaderboard[index] = { id: listing.spa_id, score: listing.score };
|
||||
} else {
|
||||
console.warn(`Position ${index + 1} had no player`);
|
||||
leaderboard[index] = { id: 0, score: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Requesting further stats...');
|
||||
const comprehensiveLeaderboard: BkrlSuperset1Entry[] = [];
|
||||
let chunkIndex = 0;
|
||||
for (const leaderboardChunk of chunk(leaderboard, 100)) {
|
||||
console.log(
|
||||
`Chunk ${chunkIndex + 1} of ${Math.ceil(leaderboard.length / 100)}`,
|
||||
);
|
||||
|
||||
const stats = await getAccountInfo(
|
||||
region,
|
||||
leaderboardChunk.map(({ id }) => id),
|
||||
['statistics.rating'],
|
||||
);
|
||||
|
||||
stats.forEach((stat, index) => {
|
||||
const trueIndex = chunkIndex * 100 + index;
|
||||
comprehensiveLeaderboard[trueIndex] =
|
||||
leaderboardChunk[index].id === 0 ||
|
||||
stat?.statistics.rating === undefined
|
||||
? {
|
||||
battles: 0,
|
||||
damage: 0,
|
||||
id: 0,
|
||||
kills: 0,
|
||||
score: 0,
|
||||
survived: 0,
|
||||
wins: 0,
|
||||
}
|
||||
: {
|
||||
id: leaderboardChunk[index].id,
|
||||
score: leaderboardChunk[index].score,
|
||||
battles: stat.statistics.rating.battles,
|
||||
damage: stat.statistics.rating.damage_dealt,
|
||||
kills: stat.statistics.rating.frags,
|
||||
survived: stat.statistics.rating.survived_battles,
|
||||
wins: stat.statistics.rating.wins,
|
||||
};
|
||||
});
|
||||
|
||||
chunkIndex++;
|
||||
}
|
||||
|
||||
const info = await patientFetchJSON<RatingInfo & { detail: undefined }>(
|
||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||
);
|
||||
const normalizedServer = regionSubdomain === 'na' ? 'com' : regionSubdomain;
|
||||
const infoContent = new CdonWriteStream().cdon(
|
||||
info as unknown as CdonValue,
|
||||
).uint8Array;
|
||||
const leaderboardWriteStream = new BkrlWriteStream();
|
||||
|
||||
leaderboardWriteStream.bkrl({
|
||||
format: BkrlFormat.Superset1,
|
||||
entries: comprehensiveLeaderboard,
|
||||
});
|
||||
|
||||
const changes: FileChange[] = [
|
||||
{
|
||||
path: `regions/${normalizedServer}/rating/${info.current_season}/info.cdon.lz4`,
|
||||
content: superCompress(infoContent),
|
||||
encoding: 'base64',
|
||||
},
|
||||
{
|
||||
path: `regions/${normalizedServer}/rating/${info.current_season}/${target}.bkrl`,
|
||||
content: Buffer.from(leaderboardWriteStream.uint8Array).toString(
|
||||
'base64',
|
||||
),
|
||||
encoding: 'base64',
|
||||
},
|
||||
];
|
||||
|
||||
commitAssets(`rating leaderboard ${regionSubdomain} ${target}`, changes);
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
asset,
|
||||
decodeProtobuf,
|
||||
encodeProtobufToBase64,
|
||||
encodePB64,
|
||||
Reviews,
|
||||
toUniqueId,
|
||||
Video,
|
||||
@@ -27,13 +26,7 @@ console.log('Finding reviews...');
|
||||
|
||||
const currentReviews = await fetch(asset('definitions/reviews.pb'))
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((buffer) =>
|
||||
decodeProtobuf<Reviews>(
|
||||
'reviews',
|
||||
'blitzkit.Reviews',
|
||||
new Uint8Array(buffer),
|
||||
),
|
||||
);
|
||||
.then((buffer) => Reviews.decode(new Uint8Array(buffer)));
|
||||
const auth = await google.auth.getClient({
|
||||
scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
|
||||
});
|
||||
@@ -131,12 +124,10 @@ for (const tank of tanksSanitized) {
|
||||
}
|
||||
}
|
||||
|
||||
const content = await encodeProtobufToBase64(
|
||||
'reviews',
|
||||
'blitzkit.Reviews',
|
||||
reviews,
|
||||
);
|
||||
|
||||
await commitAssets('reviews', [
|
||||
{ content, encoding: 'base64', path: 'definitions/reviews.pb' },
|
||||
{
|
||||
content: encodePB64(Reviews, reviews),
|
||||
encoding: 'base64',
|
||||
path: 'definitions/reviews.pb',
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,199 @@
|
||||
'use client';
|
||||
|
||||
import { idToRegion } from '@blitzkit/core';
|
||||
import { Button, Flex, Heading, Link, Text } from '@radix-ui/themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { PatreonAuthResponse } from '../pages/auth/[provider]/_index';
|
||||
import { App } from '../stores/app';
|
||||
import { CURRENT_POLICIES_AGREEMENT_INDEX } from '../stores/app/constants';
|
||||
|
||||
type Extension =
|
||||
| {
|
||||
status: 'ok';
|
||||
data: {
|
||||
access_token: string;
|
||||
account_id: number;
|
||||
expires_at: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
status: 'error';
|
||||
};
|
||||
|
||||
export function Checks() {
|
||||
return (
|
||||
<App.Provider>
|
||||
<Content />
|
||||
</App.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wargaming: 14 (refresh: 7)
|
||||
* Patreon: 31 (refresh: 15)
|
||||
*/
|
||||
function Content() {
|
||||
const logins = App.use((state) => state.logins);
|
||||
const mutateApp = App.useMutation();
|
||||
const appStore = App.useStore();
|
||||
const policiesAgreementIndex = App.use(
|
||||
(state) => state.policiesAgreementIndex,
|
||||
);
|
||||
const isLegal = window.location.pathname.startsWith('/legal');
|
||||
const [showPoliciesAgreement, setShowPoliciesAgreement] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function unsubscribe() {
|
||||
window.removeEventListener('scroll', show);
|
||||
window.removeEventListener('pointermove', show);
|
||||
window.removeEventListener('pointerdown', show);
|
||||
}
|
||||
|
||||
function show() {
|
||||
setShowPoliciesAgreement(true);
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
if (
|
||||
policiesAgreementIndex !== CURRENT_POLICIES_AGREEMENT_INDEX &&
|
||||
!isLegal
|
||||
) {
|
||||
window.addEventListener('scroll', show);
|
||||
window.addEventListener('pointermove', show);
|
||||
window.addEventListener('pointerdown', show);
|
||||
}
|
||||
|
||||
return unsubscribe;
|
||||
}, [policiesAgreementIndex, isLegal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (logins.wargaming) {
|
||||
const expiresIn = logins.wargaming.expires - Date.now();
|
||||
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
|
||||
|
||||
if (expiresInDays < 0) {
|
||||
mutateApp((draft) => {
|
||||
draft.logins.wargaming = undefined;
|
||||
});
|
||||
} else if (expiresInDays < 7) {
|
||||
const { wargaming } = appStore.getState().logins;
|
||||
|
||||
if (!wargaming) return;
|
||||
|
||||
fetch(
|
||||
`https://api.worldoftanks.${idToRegion(
|
||||
wargaming.id,
|
||||
)}/wot/auth/prolongate/?application_id=${
|
||||
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID
|
||||
}&access_token=${wargaming.token}`,
|
||||
)
|
||||
.then((response) => response.json() as Promise<Extension>)
|
||||
.then((json) => {
|
||||
mutateApp((draft) => {
|
||||
if (json.status === 'error') {
|
||||
draft.logins.wargaming = undefined;
|
||||
} else {
|
||||
draft.logins.wargaming = {
|
||||
id: wargaming.id,
|
||||
token: json.data.access_token,
|
||||
expires: json.data.expires_at * 1000,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (logins.patreon) {
|
||||
const expiresIn = logins.patreon.expires - Date.now();
|
||||
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
|
||||
|
||||
if (expiresInDays < 0) {
|
||||
mutateApp((draft) => {
|
||||
draft.logins.patreon = undefined;
|
||||
});
|
||||
} else if (expiresInDays < 15) {
|
||||
const { patreon } = appStore.getState().logins;
|
||||
|
||||
if (!patreon) return;
|
||||
|
||||
fetch(`/api/patreon/refresh/${patreon.refreshToken}`)
|
||||
.then((response) => response.json() as Promise<PatreonAuthResponse>)
|
||||
.then((data) => {
|
||||
mutateApp((draft) => {
|
||||
draft.logins.patreon = {
|
||||
token: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
expires: Date.now() + data.expires_in * 1000,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!showPoliciesAgreement) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
align="end"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: 'var(--color-overlay)',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
p="8"
|
||||
style={{
|
||||
width: '100%',
|
||||
background: 'var(--color-panel-solid)',
|
||||
}}
|
||||
justify="center"
|
||||
>
|
||||
<Flex
|
||||
align="start"
|
||||
gap="4"
|
||||
style={{
|
||||
maxWidth: 640 * 2,
|
||||
}}
|
||||
direction="column"
|
||||
>
|
||||
<Flex direction="column" gap="2">
|
||||
<Heading>
|
||||
{policiesAgreementIndex === -1
|
||||
? "BlitzKit's policies"
|
||||
: "BlitzKit's policies have changed"}
|
||||
</Heading>
|
||||
|
||||
<Text>
|
||||
This website utilizes cookies to perform analytics and personalize
|
||||
your experience. You can learn more through{' '}
|
||||
<Link href="/docs/legal/privacy-policy">our privacy policy</Link>.
|
||||
You may opt out of personalized advertizement by visiting{' '}
|
||||
<Link href="https://optout.aboutads.info/">WebChoices</Link>. By
|
||||
using BlitzKit, you also agree to our{' '}
|
||||
<Link href="/docs/legal/terms-of-service">terms of service</Link>.
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Button
|
||||
mt="2"
|
||||
onClick={() => {
|
||||
appStore.setState({
|
||||
policiesAgreementIndex: CURRENT_POLICIES_AGREEMENT_INDEX,
|
||||
});
|
||||
setShowPoliciesAgreement(false);
|
||||
}}
|
||||
>
|
||||
I agree
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { patreonLoginUrl } from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { PersonIcon } from '@radix-ui/react-icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -14,8 +13,6 @@ import {
|
||||
} from '@radix-ui/themes';
|
||||
import { Suspense } from 'react';
|
||||
import { App } from '../../stores/app';
|
||||
import { $patreonLogin } from '../../stores/patreonLogin';
|
||||
import { $wargamingLogin } from '../../stores/wargamingLogin';
|
||||
import { BlitzKitTheme } from '../BlitzKitTheme';
|
||||
import { PatreonIcon } from '../PatreonIcon';
|
||||
import { WargamingAccountName } from '../WargamingAccountName';
|
||||
@@ -23,8 +20,16 @@ import { WargamingIcon } from '../WargamingIcon';
|
||||
import { WargamingLoginButton } from '../WargamingLoginButton';
|
||||
|
||||
export function NavbarAccount() {
|
||||
const patreon = useStore($patreonLogin);
|
||||
const wargaming = useStore($wargamingLogin);
|
||||
return (
|
||||
<App.Provider>
|
||||
<Content />
|
||||
</App.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
const logins = App.use((state) => state.logins);
|
||||
const mutateApp = App.useMutation();
|
||||
|
||||
return (
|
||||
<App.Provider>
|
||||
@@ -41,13 +46,13 @@ export function NavbarAccount() {
|
||||
width="320px"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
{(patreon.token || wargaming.token) && (
|
||||
{(logins.patreon || logins.wargaming) && (
|
||||
<Flex direction="column" gap="2">
|
||||
<Text align="center" color="gray">
|
||||
Logged in accounts
|
||||
</Text>
|
||||
|
||||
{patreon.token && (
|
||||
{logins.patreon && (
|
||||
<Flex align="center" gap="2" justify="center">
|
||||
<PatreonIcon width={15} height={15} />
|
||||
<Text>Patreon</Text>
|
||||
@@ -55,7 +60,9 @@ export function NavbarAccount() {
|
||||
color="red"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
$patreonLogin.setKey('token', undefined);
|
||||
mutateApp((draft) => {
|
||||
draft.logins.patreon = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
@@ -63,7 +70,7 @@ export function NavbarAccount() {
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{wargaming.token && (
|
||||
{logins.wargaming && (
|
||||
<Flex align="center" gap="2" justify="center">
|
||||
<WargamingIcon width={15} height={15} />
|
||||
<Text>
|
||||
@@ -77,7 +84,9 @@ export function NavbarAccount() {
|
||||
color="red"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
$wargamingLogin.setKey('token', undefined);
|
||||
mutateApp((draft) => {
|
||||
draft.logins.wargaming = undefined;
|
||||
});
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
@@ -87,19 +96,19 @@ export function NavbarAccount() {
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{!patreon.token !== !wargaming.token && (
|
||||
{!logins.patreon !== !logins.wargaming && (
|
||||
<Inset side="x">
|
||||
<Separator my="4" size="4" />
|
||||
</Inset>
|
||||
)}
|
||||
|
||||
{(!patreon.token || !wargaming.token) && (
|
||||
{(!logins.patreon || !logins.wargaming) && (
|
||||
<Flex direction="column" gap="2">
|
||||
<Text align="center" color="gray">
|
||||
Log in with...
|
||||
</Text>
|
||||
|
||||
{!patreon.token && (
|
||||
{!logins.patreon && (
|
||||
<Link style={{ width: '100%' }} href={patreonLoginUrl()}>
|
||||
<Button style={{ width: '100%' }} color="tomato">
|
||||
<PatreonIcon width={15} height={15} /> Patreon
|
||||
@@ -107,7 +116,7 @@ export function NavbarAccount() {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{!wargaming.token && <WargamingLoginButton />}
|
||||
{!logins.wargaming && <WargamingLoginButton />}
|
||||
</Flex>
|
||||
)}
|
||||
</Popover.Content>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { assertSecret, patreonLoginUrl } from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { ArrowRightIcon } from '@radix-ui/react-icons';
|
||||
import {
|
||||
Badge,
|
||||
@@ -11,12 +10,20 @@ import {
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { useAdExempt } from '../hooks/useAdExempt';
|
||||
import { $patreonLogin } from '../stores/patreonLogin';
|
||||
import { App } from '../stores/app';
|
||||
import { PatreonIcon } from './PatreonIcon';
|
||||
|
||||
export function Plugs() {
|
||||
return (
|
||||
<App.Provider>
|
||||
<Content />
|
||||
</App.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function Content() {
|
||||
const exempt = useAdExempt();
|
||||
const hasPatreon = useStore($patreonLogin).token !== undefined;
|
||||
const hasPatreon = App.use((state) => state.logins.patreon !== undefined);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
+4
-3
@@ -9,11 +9,12 @@ import {
|
||||
Text,
|
||||
} from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
import { App } from '../../../../../../stores/app';
|
||||
import { Duel } from '../../../../../../stores/duel';
|
||||
import { $wargamingLogin } from '../../../../../../stores/wargamingLogin';
|
||||
import { Stars } from '../../Stars';
|
||||
|
||||
export function VoteCaster() {
|
||||
const appStore = App.useStore();
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const [easiness, setEasiness] = useState<StarsInt | null>(null);
|
||||
const [firepower, setFirepower] = useState<StarsInt | null>(null);
|
||||
@@ -97,9 +98,9 @@ export function VoteCaster() {
|
||||
survivability === null)
|
||||
}
|
||||
onClick={() => {
|
||||
const wargaming = $wargamingLogin.get();
|
||||
const { wargaming } = appStore.getState().logins;
|
||||
|
||||
if (!wargaming.token) {
|
||||
if (!wargaming) {
|
||||
return setShowDialogue(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { formatCompact } from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Flex, Heading, Skeleton, Text } from '@radix-ui/themes';
|
||||
import { memo } from 'react';
|
||||
import { useTankVotes } from '../../../../../hooks/useTankVotes';
|
||||
import { App } from '../../../../../stores/app';
|
||||
import { Duel } from '../../../../../stores/duel';
|
||||
import { $wargamingLogin } from '../../../../../stores/wargamingLogin';
|
||||
import { WargamingLoginButton } from '../../../../WargamingLoginButton';
|
||||
import { StarRow } from './components/StarRow';
|
||||
import { VoteCaster } from './components/VoteCaster';
|
||||
@@ -13,7 +12,7 @@ export const Votes = memo(
|
||||
() => {
|
||||
const id = Duel.use((state) => state.protagonist.tank.id);
|
||||
const votes = useTankVotes(id);
|
||||
const wargaming = useStore($wargamingLogin);
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="3">
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { getAccountInfo, idToRegion } from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Text } from '@radix-ui/themes';
|
||||
import { useMemo } from 'react';
|
||||
import usePromise from 'react-promise-suspense';
|
||||
import { $wargamingLogin } from '../stores/wargamingLogin';
|
||||
import { App } from '../stores/app';
|
||||
|
||||
export function WargamingAccountName() {
|
||||
const wargaming = useStore($wargamingLogin);
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
const promise = useMemo(async () => {
|
||||
if (!wargaming.token) return null;
|
||||
if (!wargaming) return null;
|
||||
|
||||
const data = await getAccountInfo(
|
||||
idToRegion(Number(wargaming.id)),
|
||||
Number(wargaming.id),
|
||||
idToRegion(wargaming.id),
|
||||
wargaming.id,
|
||||
[],
|
||||
{
|
||||
access_token: wargaming.token,
|
||||
},
|
||||
{ access_token: wargaming.token },
|
||||
);
|
||||
|
||||
return data?.nickname as string | undefined;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { $patreonLogin } from '../stores/patreonLogin';
|
||||
import { App } from '../stores/app';
|
||||
|
||||
let cache: Record<string, boolean | Promise<boolean>> = {};
|
||||
|
||||
export function useAdExempt() {
|
||||
const patreon = useStore($patreonLogin);
|
||||
const patreon = App.use((state) => state.logins.patreon);
|
||||
const [exempt, setExempt] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!patreon.token) return setExempt(false);
|
||||
if (!patreon) return setExempt(false);
|
||||
|
||||
if (!(patreon.token in cache)) {
|
||||
cache[patreon.token] = new Promise<boolean>(async (resolve) => {
|
||||
|
||||
@@ -3,18 +3,17 @@ import {
|
||||
type BlitzkitResponse,
|
||||
type TankVotes,
|
||||
} from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { $wargamingLogin } from '../stores/wargamingLogin';
|
||||
import { App } from '../stores/app';
|
||||
|
||||
const cache: Record<string, Promise<TankVotes> | TankVotes> = {};
|
||||
|
||||
export function useTankVotes(id: number) {
|
||||
const [votes, setVotes] = useState<TankVotes | null>(null);
|
||||
const wargaming = useStore($wargamingLogin);
|
||||
const wargaming = App.use((state) => state.logins.wargaming);
|
||||
|
||||
useEffect(() => {
|
||||
if (!wargaming.token) return;
|
||||
if (!wargaming) return;
|
||||
|
||||
if (id in cache) {
|
||||
Promise.all([cache[id]]).then(([votes]) => setVotes(votes));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { BlitzKitTheme } from '../components/BlitzKitTheme';
|
||||
import { Checks } from '../components/Checks';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Navbar from '../components/Navbar.astro';
|
||||
import Party3 from '../components/Party3.astro';
|
||||
@@ -43,11 +44,9 @@ const isRoot = pathname === '/';
|
||||
<Flex direction="column" minHeight="100vh">
|
||||
<Navbar />
|
||||
|
||||
<!-- <Checks /> -->
|
||||
<Checks client:only />
|
||||
|
||||
<!-- <Suspense fallback={(<PageLoader />)}> -->
|
||||
<slot />
|
||||
<!-- </Suspense> -->
|
||||
|
||||
<Footer />
|
||||
</Flex>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { $patreonLogin } from '../../../stores/patreonLogin';
|
||||
import { App } from '../../../stores/app';
|
||||
import { $wargamingLogin } from '../../../stores/wargamingLogin';
|
||||
|
||||
export const AUTH_PROVIDERS = ['wargaming', 'patreon'] as const;
|
||||
@@ -25,6 +25,8 @@ interface AuthorizeProps {
|
||||
}
|
||||
|
||||
export function Authorize({ provider }: AuthorizeProps) {
|
||||
const mutateApp = App.useMutation();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
@@ -56,10 +58,12 @@ export function Authorize({ provider }: AuthorizeProps) {
|
||||
);
|
||||
const data = (await response.json()) as PatreonAuthResponse;
|
||||
|
||||
$patreonLogin.set({
|
||||
token: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
expires: `${Date.now() + data.expires_in * 1000}`,
|
||||
mutateApp((draft) => {
|
||||
draft.logins.patreon = {
|
||||
token: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
expires: Date.now() + data.expires_in * 1000,
|
||||
};
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { PageWrapper } from '../../../components/PageWrapper';
|
||||
import { TankSearch } from '../../../components/TankSearch';
|
||||
import { App } from '../../../stores/app';
|
||||
|
||||
export function Page() {
|
||||
return (
|
||||
<PageWrapper color="purple" size={1028}>
|
||||
<TankSearch />
|
||||
<App.Provider>
|
||||
<TankSearch />
|
||||
</App.Provider>
|
||||
</PageWrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,26 @@
|
||||
import { merge } from 'lodash-es';
|
||||
import { create } from 'zustand';
|
||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||
import { createContextualStore } from '../../core/zustand/createContextualStore';
|
||||
|
||||
interface AppStore {
|
||||
developerMode: boolean;
|
||||
policiesAgreementIndex: number;
|
||||
logins: {
|
||||
wargaming?: { token: string; id: number; expires: number };
|
||||
patreon?: { token: string; refreshToken: string; expires: number };
|
||||
};
|
||||
}
|
||||
|
||||
export const App = createContextualStore(() =>
|
||||
create<AppStore>()(() => ({
|
||||
developerMode: false,
|
||||
policiesAgreementIndex: 0,
|
||||
})),
|
||||
create<AppStore>()(
|
||||
persist(
|
||||
subscribeWithSelector<AppStore>(() => ({
|
||||
developerMode: false,
|
||||
policiesAgreementIndex: -1,
|
||||
logins: {},
|
||||
})),
|
||||
{ name: 'app', merge: (a, b) => merge(b, a) },
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1,15 +0,0 @@
|
||||
import { persistentMap } from '@nanostores/persistent';
|
||||
|
||||
type PatreonLogin =
|
||||
| {
|
||||
token: undefined;
|
||||
}
|
||||
| {
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
expires: string;
|
||||
};
|
||||
|
||||
export const $patreonLogin = persistentMap<PatreonLogin>('patreon:', {
|
||||
token: undefined,
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { persistentMap } from '@nanostores/persistent';
|
||||
|
||||
type WargamingLogin =
|
||||
| {
|
||||
token: undefined;
|
||||
}
|
||||
| {
|
||||
token: string;
|
||||
id: string;
|
||||
expires: string;
|
||||
};
|
||||
|
||||
export const $wargamingLogin = persistentMap<WargamingLogin>('wargaming:', {
|
||||
token: undefined,
|
||||
});
|
||||
Reference in new issue
Block a user