From d2b18360ed781546497c9b70a3fff78928e702f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:43:12 -0500 Subject: [PATCH] feat: cc account tracking --- src/app/callback/[provider]/page.tsx | 62 +++++ src/app/tools/session/page.tsx | 401 +++++++++++---------------- src/constants/regions.ts | 9 + src/stores/app.ts | 12 + src/stores/session.ts | 58 ++-- 5 files changed, 260 insertions(+), 282 deletions(-) create mode 100644 src/app/callback/[provider]/page.tsx diff --git a/src/app/callback/[provider]/page.tsx b/src/app/callback/[provider]/page.tsx new file mode 100644 index 000000000..1c21aac6c --- /dev/null +++ b/src/app/callback/[provider]/page.tsx @@ -0,0 +1,62 @@ +'use client'; + +import { Flex, Heading, Text } from '@radix-ui/themes'; +import { useRouter } from 'next/navigation'; +import { useEffect } from 'react'; +import PageWrapper from '../../../components/PageWrapper'; +import { mutateApp } from '../../../stores/app'; + +export default function Page({ + params, + searchParams, +}: { + params: { provider: string }; + searchParams: { [key: string]: string }; +}) { + const router = useRouter(); + + useEffect(() => { + switch (params.provider) { + case 'wargaming': { + if ( + !searchParams.expires_at || + !searchParams.access_token || + !searchParams.account_id + ) { + break; + } + + mutateApp((draft) => { + draft.login = { + expiresAt: Number(searchParams.expires_at), + id: Number(searchParams.account_id), + token: searchParams.access_token, + }; + }); + + break; + } + + default: { + console.error(`Unknown provider: ${params.provider}`); + } + } + + router.push(searchParams.return ? searchParams.return : '/'); + }); + + return ( + + + Verifying you... + You'll be on your way shortly. + + + ); +} diff --git a/src/app/tools/session/page.tsx b/src/app/tools/session/page.tsx index 5a7b346e0..9db52d701 100644 --- a/src/app/tools/session/page.tsx +++ b/src/app/tools/session/page.tsx @@ -1,269 +1,190 @@ 'use client'; -import { - CaretDownIcon, - CopyIcon, - PersonIcon, - ReloadIcon, -} from '@radix-ui/react-icons'; +import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; import { AlertDialog, Button, - Checkbox, - DropdownMenu, + Card, Flex, - Heading, + Link, Text, TextField, } from '@radix-ui/themes'; import { debounce } from 'lodash'; -import { ChangeEvent, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import PageWrapper from '../../../components/PageWrapper'; -import { UNLOCALIZED_REGION_NAMES } from '../../../constants/regions'; +import { UNLOCALIZED_REGION_NAMES_SHORT } from '../../../constants/regions'; +import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; +import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; +import { idToRegion } from '../../../core/blitz/idToRegion'; import searchPlayersAcrossRegions, { AccountListWithServer, } from '../../../core/blitz/searchPlayersAcrossRegions'; -import { resetSession } from '../../../core/blitzkit/resetSession'; -import { setSession } from '../../../core/blitzkit/setSession'; -import { useSession } from '../../../stores/session'; -import SessionPage from '../../embeds/session/page'; -import { Menu } from './components/Menu'; -import * as styles from './page.css'; +import { useApp } from '../../../stores/app'; +import mutateSession, { + SessionTracking, + useSession, +} from '../../../stores/session'; export default function Page() { + const [showSearch, setShowSearch] = useState(false); + const [searching, setSearching] = useState(false); + const [searchResults, setSearchResults] = useState([]); + const [showFurtherVerification, setShowFurtherVerification] = useState(false); const input = useRef(null); - const [searchResults, setSearchResults] = useState< - AccountListWithServer | undefined - >(undefined); - const [showSearchResults, setShowSearchResults] = useState(false); - const handleChange = debounce( - async (event: ChangeEvent) => { - if (event.target.value) { - setSearchResults(await searchPlayersAcrossRegions(event.target.value)); - } else { - setSearchResults(undefined); - setShowSearchResults(false); - } - }, - 500, - ); const session = useSession(); + const login = useApp((state) => state.login); + + const search = debounce(async () => { + if (!input.current) return; + + setSearchResults(await searchPlayersAcrossRegions(input.current.value, 9)); + setSearching(false); + }, 500); useEffect(() => { - if (session.isTracking) input.current!.value = session.nickname; - }, [session]); + (async () => { + if (!session.tracking) return; + + const accountInfo = await getAccountInfo( + idToRegion(session.player.id), + session.player.id, + ); + + if (accountInfo === null) { + if (login?.id === session.player.id) { + } else { + setShowFurtherVerification(true); + } + } + })(); + }, [session.tracking && session.player.id]); return ( - -
-
- { - event.stopPropagation(); + + {/* omit totally to avoid accessing undefined player */} + {showFurtherVerification && ( + + + We need further verification + + You're trying to track a CC account with private statistics. + Please sign in to verify the ownership of this account. + - if (event.target.value) { - setShowSearchResults(true); - setSearchResults(undefined); - } else { - setShowSearchResults(false); - } - - handleChange(event); - }} - onBlur={(event) => { - // TODO: remove this hack when https://github.com/radix-ui/primitives/issues/2193 (https://github.com/radix-ui/primitives/issues/1969) is fixed - if (showSearchResults) event.target.focus(); - }} - onKeyDown={(event) => { - if (event.key === 'Escape') { - setShowSearchResults(false); - input.current?.blur(); - } - }} - placeholder="Search for a player..." - > - - - - - - - -
- - - - {searchResults === undefined ? ( - Searching... - ) : searchResults.length === 0 ? ( - No results - ) : ( - searchResults?.map( - ({ account_id: id, nickname, region }, index) => ( - { - setShowSearchResults(false); - setSession(region, id, nickname); - }} - shortcut={UNLOCALIZED_REGION_NAMES[region]} - > - {nickname} - - ), - ) - )} - - -
- -
- - - - - - - - - - Are you sure you want to reset? - - - This'll discard your current session and start tracking from - scratch again. - - - - - - useSession.setState({ - resetPrompt: !checked, - }) - } - /> - Don't ask me again - - - - - - - - - - - - - - - - - - - - - - - Copied link to your clipboard - - - Instructions for streamers: - + {login && ( + + You are currently signed with a different account. + )} - - - useSession.setState({ - embedPrompt: !checked, - }) - } - /> - Don't show this again - - - - - - - - - - - - - - - - - -
-
- - - - {!session.isTracking && ( - - No player selected - - Search for a Player to get started - - + + + + + + + + + + + + )} + + { + if (!input.current) return; + + const sanitized = input.current.value.trim(); + + if (sanitized.length > 0) { + setShowSearch(true); + setSearching(true); + search(); + } else { + setShowSearch(false); + } + }} + > + + + + + {showSearch && ( + + {searching && ( + + Searching... + + )} + + {!searching && searchResults.length === 0 && ( + + No players found + + )} + + {!searching && searchResults.length > 0 && ( + + {searchResults.map((player) => ( + + ))} + + )} + + )} + ); } diff --git a/src/constants/regions.ts b/src/constants/regions.ts index 85e84a7d0..7351792f5 100644 --- a/src/constants/regions.ts +++ b/src/constants/regions.ts @@ -11,3 +11,12 @@ export const UNLOCALIZED_REGION_NAMES: Record = { asia: 'Asia', eu: 'Europe', }; + +/** + * @deprecated + */ +export const UNLOCALIZED_REGION_NAMES_SHORT: Record = { + com: 'NA', + asia: 'APAC', + eu: 'EU', +}; diff --git a/src/stores/app.ts b/src/stores/app.ts index 227ace7d2..38fbdff15 100644 --- a/src/stores/app.ts +++ b/src/stores/app.ts @@ -1,11 +1,19 @@ +import { produce } from 'immer'; import { merge } from 'lodash'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; +interface Login { + id: number; + token: string; + expiresAt: number; +} + export interface App { devBuildAgreementTime: number; developerMode: boolean; darkMode: boolean; + login?: Login; } export const useApp = create()( @@ -18,3 +26,7 @@ export const useApp = create()( { name: 'app', merge: (a, b) => merge(b, a) }, ), ); + +export function mutateApp(recipe: (draft: App) => void) { + useApp.setState(produce(recipe)); +} diff --git a/src/stores/session.ts b/src/stores/session.ts index cf4cfe48c..98854518c 100644 --- a/src/stores/session.ts +++ b/src/stores/session.ts @@ -2,56 +2,30 @@ import { produce } from 'immer'; import { merge } from 'lodash'; import { create } from 'zustand'; import { persist } from 'zustand/middleware'; -import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn'; -import { AccentColor, GrayColor } from '../constants/radixColors'; import { Region } from '../constants/regions'; -import { NormalizedTankStats } from '../types/tanksStats'; -type Session = ( - | { - isTracking: false; - } - | { - isTracking: true; +interface SessionBase {} - region: Region; - id: number; - nickname: string; - tankStats: NormalizedTankStats; - time: number; - } -) & { - title: string; - resetPrompt: boolean; - embedPrompt: boolean; - customColumns: { - display: CustomColumnDisplay; - showDelta: boolean; - }[] & { length: 4 }; - showTotal: boolean; - showCareer: boolean; - color: AccentColor | GrayColor; -}; +export interface SessionTracking extends SessionBase { + tracking: true; + player: { + id: number; + region: Region; + }; +} + +interface SessionNotTracking extends SessionBase { + tracking: false; +} + +type Session = SessionTracking | SessionNotTracking; export const useSession = create()( persist( (set) => ({ - title: 'Untitled session', - isTracking: false, - resetPrompt: true, - showTotal: true, - showCareer: true, - embedPrompt: true, - - customColumns: [ - { display: 'battles', showDelta: false }, - { display: 'winrate', showDelta: true }, - { display: 'wn8', showDelta: false }, - { display: 'damage', showDelta: true }, - ], - color: 'slate', + tracking: false, }), - { name: 'session', merge: (a, b) => merge(b, a) }, + { name: 'session-2', merge: (a, b) => merge(b, a) }, ), );