mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: cc account tracking
This commit is contained in:
1 parent
6ae86a7a70
commit
d2b18360ed
5 files changed
+260
-282
No files matched your search
@@ -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 (
|
||||||
|
<PageWrapper>
|
||||||
|
<Flex
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
direction="column"
|
||||||
|
gap="2"
|
||||||
|
>
|
||||||
|
<Heading>Verifying you...</Heading>
|
||||||
|
<Text color="gray">You'll be on your way shortly.</Text>
|
||||||
|
</Flex>
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
+161
-240
@@ -1,269 +1,190 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
|
||||||
CaretDownIcon,
|
|
||||||
CopyIcon,
|
|
||||||
PersonIcon,
|
|
||||||
ReloadIcon,
|
|
||||||
} from '@radix-ui/react-icons';
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Card,
|
||||||
DropdownMenu,
|
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import PageWrapper from '../../../components/PageWrapper';
|
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, {
|
import searchPlayersAcrossRegions, {
|
||||||
AccountListWithServer,
|
AccountListWithServer,
|
||||||
} from '../../../core/blitz/searchPlayersAcrossRegions';
|
} from '../../../core/blitz/searchPlayersAcrossRegions';
|
||||||
import { resetSession } from '../../../core/blitzkit/resetSession';
|
import { useApp } from '../../../stores/app';
|
||||||
import { setSession } from '../../../core/blitzkit/setSession';
|
import mutateSession, {
|
||||||
import { useSession } from '../../../stores/session';
|
SessionTracking,
|
||||||
import SessionPage from '../../embeds/session/page';
|
useSession,
|
||||||
import { Menu } from './components/Menu';
|
} from '../../../stores/session';
|
||||||
import * as styles from './page.css';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
const [showSearch, setShowSearch] = useState(false);
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
const [searchResults, setSearchResults] = useState<AccountListWithServer>([]);
|
||||||
|
const [showFurtherVerification, setShowFurtherVerification] = useState(false);
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
const [searchResults, setSearchResults] = useState<
|
|
||||||
AccountListWithServer | undefined
|
|
||||||
>(undefined);
|
|
||||||
const [showSearchResults, setShowSearchResults] = useState(false);
|
|
||||||
const handleChange = debounce(
|
|
||||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (event.target.value) {
|
|
||||||
setSearchResults(await searchPlayersAcrossRegions(event.target.value));
|
|
||||||
} else {
|
|
||||||
setSearchResults(undefined);
|
|
||||||
setShowSearchResults(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
500,
|
|
||||||
);
|
|
||||||
const session = useSession();
|
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(() => {
|
useEffect(() => {
|
||||||
if (session.isTracking) input.current!.value = session.nickname;
|
(async () => {
|
||||||
}, [session]);
|
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 (
|
return (
|
||||||
<PageWrapper color="blue">
|
<PageWrapper>
|
||||||
<div className={styles.toolBar}>
|
{/* omit totally to avoid accessing undefined player */}
|
||||||
<div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}>
|
{showFurtherVerification && (
|
||||||
<TextField.Root
|
<AlertDialog.Root open>
|
||||||
defaultValue={session.isTracking ? session.nickname : undefined}
|
<AlertDialog.Content>
|
||||||
ref={input}
|
<AlertDialog.Title>We need further verification</AlertDialog.Title>
|
||||||
onChange={(event) => {
|
<AlertDialog.Description>
|
||||||
event.stopPropagation();
|
You're trying to track a CC account with private statistics.
|
||||||
|
Please sign in to verify the ownership of this account.
|
||||||
|
</AlertDialog.Description>
|
||||||
|
|
||||||
if (event.target.value) {
|
{login && (
|
||||||
setShowSearchResults(true);
|
<AlertDialog.Description mt="2" color="red">
|
||||||
setSearchResults(undefined);
|
You are currently signed with a different account.
|
||||||
} 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..."
|
|
||||||
>
|
|
||||||
<TextField.Slot>
|
|
||||||
<PersonIcon height="16" width="16" />
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
|
||||||
|
|
||||||
<DropdownMenu.Root open={showSearchResults} modal={false}>
|
|
||||||
<DropdownMenu.Trigger>
|
|
||||||
<div />
|
|
||||||
</DropdownMenu.Trigger>
|
|
||||||
|
|
||||||
<DropdownMenu.Content>
|
|
||||||
{searchResults === undefined ? (
|
|
||||||
<DropdownMenu.Item disabled>Searching...</DropdownMenu.Item>
|
|
||||||
) : searchResults.length === 0 ? (
|
|
||||||
<DropdownMenu.Item disabled>No results</DropdownMenu.Item>
|
|
||||||
) : (
|
|
||||||
searchResults?.map(
|
|
||||||
({ account_id: id, nickname, region }, index) => (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
key={id}
|
|
||||||
onClick={() => {
|
|
||||||
setShowSearchResults(false);
|
|
||||||
setSession(region, id, nickname);
|
|
||||||
}}
|
|
||||||
shortcut={UNLOCALIZED_REGION_NAMES[region]}
|
|
||||||
>
|
|
||||||
{nickname}
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Root>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
|
||||||
<AlertDialog.Root>
|
|
||||||
<AlertDialog.Trigger>
|
|
||||||
<Button
|
|
||||||
variant="soft"
|
|
||||||
onClick={(event) => {
|
|
||||||
if (!session.resetPrompt) {
|
|
||||||
event.preventDefault();
|
|
||||||
resetSession();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={styles.toolbarButton}
|
|
||||||
color="red"
|
|
||||||
>
|
|
||||||
<ReloadIcon width="16" height="16" /> Reset
|
|
||||||
</Button>
|
|
||||||
</AlertDialog.Trigger>
|
|
||||||
|
|
||||||
<AlertDialog.Content>
|
|
||||||
<Flex direction="column" gap="4">
|
|
||||||
<Flex direction="column" gap="0">
|
|
||||||
<AlertDialog.Title>
|
|
||||||
Are you sure you want to reset?
|
|
||||||
</AlertDialog.Title>
|
|
||||||
<AlertDialog.Description>
|
|
||||||
This'll discard your current session and start tracking from
|
|
||||||
scratch again.
|
|
||||||
</AlertDialog.Description>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Text size="2">
|
|
||||||
<Checkbox
|
|
||||||
mr="1"
|
|
||||||
onCheckedChange={(checked) =>
|
|
||||||
useSession.setState({
|
|
||||||
resetPrompt: !checked,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
Don't ask me again
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<Flex gap="3" mt="4">
|
|
||||||
<AlertDialog.Cancel>
|
|
||||||
<Button variant="soft" color="gray">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</AlertDialog.Cancel>
|
|
||||||
|
|
||||||
<AlertDialog.Action>
|
|
||||||
<Button variant="solid" color="red" onClick={resetSession}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</AlertDialog.Action>
|
|
||||||
</Flex>
|
|
||||||
</AlertDialog.Content>
|
|
||||||
</AlertDialog.Root>
|
|
||||||
|
|
||||||
<AlertDialog.Root>
|
|
||||||
<AlertDialog.Trigger>
|
|
||||||
<Button
|
|
||||||
variant="soft"
|
|
||||||
className={styles.toolbarButton}
|
|
||||||
onClick={(event) => {
|
|
||||||
if (!session.embedPrompt) {
|
|
||||||
event.preventDefault();
|
|
||||||
resetSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
`${location.origin}/embeds/session`,
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CopyIcon width="16" height="16" /> Embed
|
|
||||||
</Button>
|
|
||||||
</AlertDialog.Trigger>
|
|
||||||
|
|
||||||
<AlertDialog.Content>
|
|
||||||
<AlertDialog.Title>
|
|
||||||
Copied link to your clipboard
|
|
||||||
</AlertDialog.Title>
|
|
||||||
<AlertDialog.Description>
|
|
||||||
Instructions for streamers:
|
|
||||||
<iframe
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
aspectRatio: '16 / 9',
|
|
||||||
border: 'none',
|
|
||||||
}}
|
|
||||||
src="https://www.youtube-nocookie.com/embed/e--GwjyrJAE?si=ftEiCmpbMI2FUY5N"
|
|
||||||
title="YouTube video player"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
||||||
></iframe>
|
|
||||||
</AlertDialog.Description>
|
</AlertDialog.Description>
|
||||||
|
)}
|
||||||
|
|
||||||
<Text size="2">
|
<Flex gap="3" mt="4" justify="end">
|
||||||
<Checkbox
|
<AlertDialog.Cancel>
|
||||||
mr="1"
|
<Button
|
||||||
onCheckedChange={(checked) =>
|
variant="soft"
|
||||||
useSession.setState({
|
color="gray"
|
||||||
embedPrompt: !checked,
|
onClick={() => {
|
||||||
})
|
mutateSession((draft) => {
|
||||||
}
|
draft.tracking = false;
|
||||||
/>
|
});
|
||||||
Don't show this again
|
setShowFurtherVerification(false);
|
||||||
</Text>
|
}}
|
||||||
|
>
|
||||||
<Flex gap="3" mt="4">
|
Cancel
|
||||||
<AlertDialog.Cancel>
|
</Button>
|
||||||
<Button variant="soft">Got it!</Button>
|
</AlertDialog.Cancel>
|
||||||
</AlertDialog.Cancel>
|
<AlertDialog.Action>
|
||||||
</Flex>
|
<Link
|
||||||
</AlertDialog.Content>
|
href={`https://api.worldoftanks.${
|
||||||
</AlertDialog.Root>
|
(session as SessionTracking).player.region
|
||||||
|
}/wot/auth/login/?application_id=${WARGAMING_APPLICATION_ID}&redirect_uri=${encodeURIComponent(
|
||||||
<DropdownMenu.Root>
|
`${location.origin}/callback/wargaming/?return=${location.href}`,
|
||||||
<DropdownMenu.Trigger>
|
)}`}
|
||||||
<Button variant="soft" className={styles.toolbarButton}>
|
>
|
||||||
Options
|
<Button variant="solid">Sign in</Button>
|
||||||
<CaretDownIcon />
|
</Link>
|
||||||
</Button>
|
</AlertDialog.Action>
|
||||||
</DropdownMenu.Trigger>
|
</Flex>
|
||||||
|
</AlertDialog.Content>
|
||||||
<Menu Builder={DropdownMenu} reset={resetSession} />
|
</AlertDialog.Root>
|
||||||
</DropdownMenu.Root>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SessionPage naked={false} />
|
|
||||||
|
|
||||||
{!session.isTracking && (
|
|
||||||
<Flex
|
|
||||||
align="center"
|
|
||||||
justify="center"
|
|
||||||
direction="column"
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
<Heading color="gray">No player selected</Heading>
|
|
||||||
<Text color="gray">
|
|
||||||
Search for a <PersonIcon /> Player to get started
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<TextField.Root
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
ref={input}
|
||||||
|
placeholder="Search players..."
|
||||||
|
onChange={() => {
|
||||||
|
if (!input.current) return;
|
||||||
|
|
||||||
|
const sanitized = input.current.value.trim();
|
||||||
|
|
||||||
|
if (sanitized.length > 0) {
|
||||||
|
setShowSearch(true);
|
||||||
|
setSearching(true);
|
||||||
|
search();
|
||||||
|
} else {
|
||||||
|
setShowSearch(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField.Slot>
|
||||||
|
<MagnifyingGlassIcon />
|
||||||
|
</TextField.Slot>
|
||||||
|
|
||||||
|
{showSearch && (
|
||||||
|
<Card
|
||||||
|
mt="2"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '100%',
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{searching && (
|
||||||
|
<Flex justify="center">
|
||||||
|
<Text color="gray">Searching...</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searching && searchResults.length === 0 && (
|
||||||
|
<Flex justify="center">
|
||||||
|
<Text color="gray">No players found</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searching && searchResults.length > 0 && (
|
||||||
|
<Flex direction="column" gap="2">
|
||||||
|
{searchResults.map((player) => (
|
||||||
|
<Button
|
||||||
|
key={player.account_id}
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => {
|
||||||
|
setShowSearch(false);
|
||||||
|
mutateSession((draft) => {
|
||||||
|
if (!input.current) return;
|
||||||
|
|
||||||
|
draft.tracking = true;
|
||||||
|
(draft as SessionTracking).player = {
|
||||||
|
id: player.account_id,
|
||||||
|
region: player.region,
|
||||||
|
};
|
||||||
|
|
||||||
|
input.current.value = player.nickname;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{player.nickname} (
|
||||||
|
{UNLOCALIZED_REGION_NAMES_SHORT[player.region]})
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</TextField.Root>
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -11,3 +11,12 @@ export const UNLOCALIZED_REGION_NAMES: Record<Region, string> = {
|
|||||||
asia: 'Asia',
|
asia: 'Asia',
|
||||||
eu: 'Europe',
|
eu: 'Europe',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const UNLOCALIZED_REGION_NAMES_SHORT: Record<Region, string> = {
|
||||||
|
com: 'NA',
|
||||||
|
asia: 'APAC',
|
||||||
|
eu: 'EU',
|
||||||
|
};
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
|
import { produce } from 'immer';
|
||||||
import { merge } from 'lodash';
|
import { merge } from 'lodash';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
|
||||||
|
interface Login {
|
||||||
|
id: number;
|
||||||
|
token: string;
|
||||||
|
expiresAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface App {
|
export interface App {
|
||||||
devBuildAgreementTime: number;
|
devBuildAgreementTime: number;
|
||||||
developerMode: boolean;
|
developerMode: boolean;
|
||||||
darkMode: boolean;
|
darkMode: boolean;
|
||||||
|
login?: Login;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useApp = create<App>()(
|
export const useApp = create<App>()(
|
||||||
@@ -18,3 +26,7 @@ export const useApp = create<App>()(
|
|||||||
{ name: 'app', merge: (a, b) => merge(b, a) },
|
{ name: 'app', merge: (a, b) => merge(b, a) },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function mutateApp(recipe: (draft: App) => void) {
|
||||||
|
useApp.setState(produce(recipe));
|
||||||
|
}
|
||||||
+16
-42
@@ -2,56 +2,30 @@ import { produce } from 'immer';
|
|||||||
import { merge } from 'lodash';
|
import { merge } from 'lodash';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
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 { Region } from '../constants/regions';
|
||||||
import { NormalizedTankStats } from '../types/tanksStats';
|
|
||||||
|
|
||||||
type Session = (
|
interface SessionBase {}
|
||||||
| {
|
|
||||||
isTracking: false;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
isTracking: true;
|
|
||||||
|
|
||||||
region: Region;
|
export interface SessionTracking extends SessionBase {
|
||||||
id: number;
|
tracking: true;
|
||||||
nickname: string;
|
player: {
|
||||||
tankStats: NormalizedTankStats;
|
id: number;
|
||||||
time: number;
|
region: Region;
|
||||||
}
|
};
|
||||||
) & {
|
}
|
||||||
title: string;
|
|
||||||
resetPrompt: boolean;
|
interface SessionNotTracking extends SessionBase {
|
||||||
embedPrompt: boolean;
|
tracking: false;
|
||||||
customColumns: {
|
}
|
||||||
display: CustomColumnDisplay;
|
|
||||||
showDelta: boolean;
|
type Session = SessionTracking | SessionNotTracking;
|
||||||
}[] & { length: 4 };
|
|
||||||
showTotal: boolean;
|
|
||||||
showCareer: boolean;
|
|
||||||
color: AccentColor | GrayColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSession = create<Session>()(
|
export const useSession = create<Session>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
title: 'Untitled session',
|
tracking: false,
|
||||||
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',
|
|
||||||
}),
|
}),
|
||||||
{ name: 'session', merge: (a, b) => merge(b, a) },
|
{ name: 'session-2', merge: (a, b) => merge(b, a) },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user