Add account stuff
This commit is contained in:
5 files changed
+402
-4
No files matched your search
@@ -43,7 +43,10 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
{ "url": "https://games.lncvrt.xyz/api/launcher/versions" },
|
{ "url": "https://games.lncvrt.xyz/api/launcher/versions" },
|
||||||
{ "url": "https://games.lncvrt.xyz/api/launcher/download" },
|
{ "url": "https://games.lncvrt.xyz/api/launcher/download" },
|
||||||
{ "url": "https://games.lncvrt.xyz/api/launcher/mods" }
|
{ "url": "https://games.lncvrt.xyz/api/launcher/mods" },
|
||||||
|
{ "url": "https://games.lncvrt.xyz/api/account/login" },
|
||||||
|
{ "url": "https://games.lncvrt.xyz/api/account/change-username" },
|
||||||
|
{ "url": "https://games.lncvrt.xyz/api/account/change-password" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { verifySignature } from '@/lib/util'
|
||||||
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
|
import { message } from '@tauri-apps/plugin-dialog'
|
||||||
|
import { fetch } from '@tauri-apps/plugin-http'
|
||||||
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
|
import { Dispatch, SetStateAction, useState } from 'react'
|
||||||
|
|
||||||
|
function AccountLoggedOut () {
|
||||||
|
const { account } = useGlobal()
|
||||||
|
const [username, setUsername] = useState<string>('')
|
||||||
|
const [password, setPassword] = useState<string>('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-3xl ml-4 mt-4'>Account Login</p>
|
||||||
|
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
|
||||||
|
<form
|
||||||
|
className='flex flex-col gap-2'
|
||||||
|
onSubmit={async e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
'https://games.lncvrt.xyz/api/account/login',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const signature = response.headers.get('x-signature') ?? ''
|
||||||
|
const data = await response.json()
|
||||||
|
if (!(await verifySignature(JSON.stringify(data), signature))) {
|
||||||
|
await message(
|
||||||
|
"Failed to login, there was an issue validating the data wasn't tampered with.",
|
||||||
|
{ title: 'Request tampered', kind: 'error' }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.success) {
|
||||||
|
if (data.data.session)
|
||||||
|
await account?.set('session', data.data.session)
|
||||||
|
if (data.data.username)
|
||||||
|
await account?.set('name', data.data.username)
|
||||||
|
if (data.data.id) await account?.set('id', data.data.id)
|
||||||
|
if (data.data.admin) await account?.set('admin', data.data.admin)
|
||||||
|
} else {
|
||||||
|
await message(data.message || 'n/a', {
|
||||||
|
title: 'Failed to login',
|
||||||
|
kind: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id='username'
|
||||||
|
name='username'
|
||||||
|
placeholder='Username'
|
||||||
|
type='username'
|
||||||
|
value={username}
|
||||||
|
onChange={e => setUsername(e.target.value)}
|
||||||
|
required
|
||||||
|
className='input-field my-1'
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id='password'
|
||||||
|
name='password'
|
||||||
|
placeholder='Password'
|
||||||
|
type='password'
|
||||||
|
value={password}
|
||||||
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
className='input-field my-1'
|
||||||
|
/>
|
||||||
|
<button type='submit' className='button btntheme1'>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<div className='flex justify-center flex-col gap-2 mt-6 text-center'>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={async () =>
|
||||||
|
await openUrl(
|
||||||
|
'https://games.lncvrt.xyz/account/forgot-username'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className='button btntheme1'
|
||||||
|
>
|
||||||
|
Forgot username?
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={async () =>
|
||||||
|
await openUrl(
|
||||||
|
'https://games.lncvrt.xyz/account/forgot-password'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className='button btntheme1'
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={async () =>
|
||||||
|
await openUrl('https://games.lncvrt.xyz/account/register')
|
||||||
|
}
|
||||||
|
className='button btntheme1'
|
||||||
|
>
|
||||||
|
Don't have an account?
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountChangeUsername ({
|
||||||
|
setChangingUsername
|
||||||
|
}: {
|
||||||
|
setChangingUsername: Dispatch<SetStateAction<boolean>>
|
||||||
|
}) {
|
||||||
|
const { account, accountSession } = useGlobal()
|
||||||
|
|
||||||
|
const [newUsername, setNewUsername] = useState<string>('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-3xl ml-4 mt-4'>Change Account Username</p>
|
||||||
|
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
|
||||||
|
<form
|
||||||
|
className='flex flex-col gap-2'
|
||||||
|
onSubmit={async e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
'https://games.lncvrt.xyz/api/account/change-username',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
authorization: accountSession ?? ''
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
newUsername
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const signature = response.headers.get('x-signature') ?? ''
|
||||||
|
const data = await response.json()
|
||||||
|
if (!(await verifySignature(JSON.stringify(data), signature))) {
|
||||||
|
await message(
|
||||||
|
"Failed to change username, there was an issue validating the data wasn't tampered with.",
|
||||||
|
{ title: 'Request tampered', kind: 'error' }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.success) {
|
||||||
|
if (data.data) await account?.set('session', data.data)
|
||||||
|
await account?.set('name', newUsername)
|
||||||
|
setChangingUsername(false)
|
||||||
|
} else {
|
||||||
|
await message(data.message || 'n/a', {
|
||||||
|
title: 'Failed to change username',
|
||||||
|
kind: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id='new-username'
|
||||||
|
name='new-username'
|
||||||
|
placeholder='New username'
|
||||||
|
type='username'
|
||||||
|
value={newUsername}
|
||||||
|
onChange={e => setNewUsername(e.target.value)}
|
||||||
|
required
|
||||||
|
className='input-field my-1'
|
||||||
|
/>
|
||||||
|
<button type='submit' className='button btntheme1'>
|
||||||
|
Change Username
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='button btntheme1'
|
||||||
|
onClick={() => setChangingUsername(false)}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountChangePassword ({
|
||||||
|
setChangingPassword
|
||||||
|
}: {
|
||||||
|
setChangingPassword: Dispatch<SetStateAction<boolean>>
|
||||||
|
}) {
|
||||||
|
const { account, accountSession } = useGlobal()
|
||||||
|
|
||||||
|
const [newPassword, setNewPassword] = useState<string>('')
|
||||||
|
const [retypeNewPassword, setRetypeNewPassword] = useState<string>('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-3xl ml-4 mt-4'>Change Account Password</p>
|
||||||
|
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
|
||||||
|
<form
|
||||||
|
className='flex flex-col gap-2'
|
||||||
|
onSubmit={async e => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (newPassword !== retypeNewPassword) {
|
||||||
|
alert('Passwords must match')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
'https://games.lncvrt.xyz/api/account/change-password',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
authorization: accountSession ?? ''
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
newPassword
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const signature = response.headers.get('x-signature') ?? ''
|
||||||
|
const data = await response.json()
|
||||||
|
if (!(await verifySignature(JSON.stringify(data), signature))) {
|
||||||
|
await message(
|
||||||
|
"Failed to change password, there was an issue validating the data wasn't tampered with.",
|
||||||
|
{ title: 'Request tampered', kind: 'error' }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.success) {
|
||||||
|
if (data.data) await account?.set('session', data.data)
|
||||||
|
setChangingPassword(false)
|
||||||
|
} else {
|
||||||
|
await message(data.message || 'n/a', {
|
||||||
|
title: 'Failed to change password',
|
||||||
|
kind: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id='new-password'
|
||||||
|
name='new-password'
|
||||||
|
placeholder='New password'
|
||||||
|
type='password'
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={newPassword}
|
||||||
|
onChange={e => setNewPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
className='input-field my-1'
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
id='retype-new-password'
|
||||||
|
name='retype-new-password'
|
||||||
|
placeholder='Re-type password'
|
||||||
|
type='password'
|
||||||
|
autoComplete='new-password'
|
||||||
|
value={retypeNewPassword}
|
||||||
|
onChange={e => setRetypeNewPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
className='input-field my-1'
|
||||||
|
/>
|
||||||
|
<button type='submit' className='button btntheme1'>
|
||||||
|
Change Password
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='button btntheme1'
|
||||||
|
onClick={() => setChangingPassword(false)}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccountLoggedIn () {
|
||||||
|
const { account, accountName } = useGlobal()
|
||||||
|
const [changingUsername, setChangingUsername] = useState<boolean>(false)
|
||||||
|
const [changingPassword, setChangingPassword] = useState<boolean>(false)
|
||||||
|
|
||||||
|
if (changingUsername)
|
||||||
|
return <AccountChangeUsername setChangingUsername={setChangingUsername} />
|
||||||
|
|
||||||
|
if (changingPassword)
|
||||||
|
return <AccountChangePassword setChangingPassword={setChangingPassword} />
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className='text-3xl ml-4 mt-4'>Account</p>
|
||||||
|
<div className='ml-4 mt-4 bg-(--col1) border border-(--col3) rounded-lg p-4 w-fit h-fit'>
|
||||||
|
<p>Logged in as: {accountName}</p>
|
||||||
|
<div className='flex justify-center flex-col gap-2 mt-4 text-center'>
|
||||||
|
<button
|
||||||
|
onClick={() => setChangingUsername(true)}
|
||||||
|
className='button btntheme1'
|
||||||
|
>
|
||||||
|
Change username
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setChangingPassword(true)}
|
||||||
|
className='button btntheme1'
|
||||||
|
>
|
||||||
|
Change password
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={async () => await account?.clear()}
|
||||||
|
className='button btntheme1'
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AccountsPage () {
|
||||||
|
const { accountSession, accountId, accountName } = useGlobal()
|
||||||
|
|
||||||
|
if (!accountSession || !accountId || !accountName) return <AccountLoggedOut />
|
||||||
|
else return <AccountLoggedIn />
|
||||||
|
}
|
||||||
+34
-1
@@ -86,6 +86,7 @@ export default function RootLayout ({
|
|||||||
|
|
||||||
const [settings, setSettings] = useState<Store | null>(null)
|
const [settings, setSettings] = useState<Store | null>(null)
|
||||||
const [versions, setVersions] = useState<Store | null>(null)
|
const [versions, setVersions] = useState<Store | null>(null)
|
||||||
|
const [account, setAccount] = useState<Store | null>(null)
|
||||||
|
|
||||||
const [notificationsAllowed, setNotificationsAllowed] =
|
const [notificationsAllowed, setNotificationsAllowed] =
|
||||||
useState<boolean>(false)
|
useState<boolean>(false)
|
||||||
@@ -103,6 +104,11 @@ export default function RootLayout ({
|
|||||||
Record<string, Record<string, number>>
|
Record<string, Record<string, number>>
|
||||||
>({})
|
>({})
|
||||||
|
|
||||||
|
const [accountSession, setAccountSession] = useState<string | null>(null)
|
||||||
|
const [accountId, setAccountId] = useState<string | null>(null)
|
||||||
|
const [accountName, setAccountName] = useState<string | null>(null)
|
||||||
|
const [accountAdmin, setAccountAdmin] = useState<boolean>(false)
|
||||||
|
|
||||||
const downloadVersions = useCallback(
|
const downloadVersions = useCallback(
|
||||||
async (
|
async (
|
||||||
list: Array<{
|
list: Array<{
|
||||||
@@ -658,6 +664,16 @@ export default function RootLayout ({
|
|||||||
mods: {}
|
mods: {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const accountLocal = await load('account.json', {
|
||||||
|
autoSave: true,
|
||||||
|
defaults: {
|
||||||
|
version: client,
|
||||||
|
session: null,
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
admin: false
|
||||||
|
}
|
||||||
|
})
|
||||||
const cfgVer = (await settingsLocal.get<string>('version')) ?? client
|
const cfgVer = (await settingsLocal.get<string>('version')) ?? client
|
||||||
const versVer = (await versionsLocal.get<string>('version')) ?? client
|
const versVer = (await versionsLocal.get<string>('version')) ?? client
|
||||||
if (semver.gt(cfgVer, client) || semver.gt(versVer, client)) {
|
if (semver.gt(cfgVer, client) || semver.gt(versVer, client)) {
|
||||||
@@ -667,6 +683,7 @@ export default function RootLayout ({
|
|||||||
|
|
||||||
await settingsLocal.set('version', client)
|
await settingsLocal.set('version', client)
|
||||||
await versionsLocal.set('version', client)
|
await versionsLocal.set('version', client)
|
||||||
|
await accountLocal.set('version', client)
|
||||||
|
|
||||||
if (await exists('config.json', legacyOptions)) {
|
if (await exists('config.json', legacyOptions)) {
|
||||||
const config = await readTextFile('config.json', legacyOptions)
|
const config = await readTextFile('config.json', legacyOptions)
|
||||||
@@ -720,6 +737,7 @@ export default function RootLayout ({
|
|||||||
}
|
}
|
||||||
setSettings(settingsLocal)
|
setSettings(settingsLocal)
|
||||||
setVersions(versionsLocal)
|
setVersions(versionsLocal)
|
||||||
|
setAccount(accountLocal)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
||||||
if (!(await isPermissionGranted())) {
|
if (!(await isPermissionGranted())) {
|
||||||
@@ -790,6 +808,11 @@ export default function RootLayout ({
|
|||||||
unlisteners.push(versions!.onKeyChange<T>(key, fn))
|
unlisteners.push(versions!.onKeyChange<T>(key, fn))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function watchAccount<T> (key: string, fn: (v: T | undefined) => void) {
|
||||||
|
account!.get<T>(key).then(fn)
|
||||||
|
unlisteners.push(account!.onKeyChange<T>(key, fn))
|
||||||
|
}
|
||||||
|
|
||||||
watchSettings<string>('theme', v => setTheme(v ?? 'dark'))
|
watchSettings<string>('theme', v => setTheme(v ?? 'dark'))
|
||||||
watchSettings<boolean>('notificationsAllowed', v =>
|
watchSettings<boolean>('notificationsAllowed', v =>
|
||||||
setNotificationsAllowed(v ?? true)
|
setNotificationsAllowed(v ?? true)
|
||||||
@@ -811,10 +834,15 @@ export default function RootLayout ({
|
|||||||
setModsList(v ?? {})
|
setModsList(v ?? {})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watchAccount<string | null>('session', v => setAccountSession(v ?? null))
|
||||||
|
watchAccount<string | null>('id', v => setAccountId(v ?? null))
|
||||||
|
watchAccount<string | null>('name', v => setAccountName(v ?? null))
|
||||||
|
watchAccount<boolean>('admin', v => setAccountAdmin(v ?? false))
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unlisteners.forEach(u => u.then(fn => fn()))
|
unlisteners.forEach(u => u.then(fn => fn()))
|
||||||
}
|
}
|
||||||
}, [settings, versions])
|
}, [settings, versions, account])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -867,6 +895,7 @@ export default function RootLayout ({
|
|||||||
getSpecialVersionsList,
|
getSpecialVersionsList,
|
||||||
settings,
|
settings,
|
||||||
versions,
|
versions,
|
||||||
|
account,
|
||||||
notificationsAllowed,
|
notificationsAllowed,
|
||||||
sidebarAlwaysShowGames,
|
sidebarAlwaysShowGames,
|
||||||
linuxUseWine,
|
linuxUseWine,
|
||||||
@@ -876,6 +905,10 @@ export default function RootLayout ({
|
|||||||
developerMode,
|
developerMode,
|
||||||
versionsList,
|
versionsList,
|
||||||
modsList,
|
modsList,
|
||||||
|
accountSession,
|
||||||
|
accountId,
|
||||||
|
accountName,
|
||||||
|
accountAdmin,
|
||||||
movingData,
|
movingData,
|
||||||
setMovingData,
|
setMovingData,
|
||||||
managingGame,
|
managingGame,
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import {
|
|||||||
faDownload,
|
faDownload,
|
||||||
faGamepad,
|
faGamepad,
|
||||||
faHexagonNodes,
|
faHexagonNodes,
|
||||||
faLayerGroup
|
faLayerGroup,
|
||||||
|
faUser,
|
||||||
|
faUserShield
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
|
||||||
import { platform } from '@tauri-apps/plugin-os'
|
import { platform } from '@tauri-apps/plugin-os'
|
||||||
@@ -40,7 +42,9 @@ export default function Sidebar () {
|
|||||||
linuxUseWine,
|
linuxUseWine,
|
||||||
setManagingGame,
|
setManagingGame,
|
||||||
settings,
|
settings,
|
||||||
developerMode
|
developerMode,
|
||||||
|
accountName,
|
||||||
|
accountAdmin
|
||||||
} = useGlobal()
|
} = useGlobal()
|
||||||
|
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
@@ -222,6 +226,17 @@ export default function Sidebar () {
|
|||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
draggable={false}
|
||||||
|
href='/main/account'
|
||||||
|
className={`link ${pathname === '/main/account' ? 'active' : ''}`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={accountAdmin ? faUserShield : faUser}
|
||||||
|
className='mr-1'
|
||||||
|
/>{' '}
|
||||||
|
{accountName ?? 'Account'}
|
||||||
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => openUrl('https://games.lncvrt.xyz/discord')}
|
onClick={() => openUrl('https://games.lncvrt.xyz/discord')}
|
||||||
className='link mr-auto'
|
className='link mr-auto'
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type GlobalCtxType = {
|
|||||||
getSpecialVersionsList: (game?: number) => GameVersion[]
|
getSpecialVersionsList: (game?: number) => GameVersion[]
|
||||||
settings: Store | null
|
settings: Store | null
|
||||||
versions: Store | null
|
versions: Store | null
|
||||||
|
account: Store | null
|
||||||
notificationsAllowed: boolean
|
notificationsAllowed: boolean
|
||||||
sidebarAlwaysShowGames: boolean
|
sidebarAlwaysShowGames: boolean
|
||||||
linuxUseWine: boolean
|
linuxUseWine: boolean
|
||||||
@@ -61,6 +62,10 @@ type GlobalCtxType = {
|
|||||||
developerMode: boolean
|
developerMode: boolean
|
||||||
versionsList: Record<string, number>
|
versionsList: Record<string, number>
|
||||||
modsList: Record<string, Record<string, number>>
|
modsList: Record<string, Record<string, number>>
|
||||||
|
accountSession: string | null
|
||||||
|
accountId: string | null
|
||||||
|
accountName: string | null
|
||||||
|
accountAdmin: boolean
|
||||||
movingData: boolean
|
movingData: boolean
|
||||||
setMovingData: Dispatch<SetStateAction<boolean>>
|
setMovingData: Dispatch<SetStateAction<boolean>>
|
||||||
managingGame: number | null
|
managingGame: number | null
|
||||||
|
|||||||
Reference in new issue
Block a user