Skip to content

Commit 2e7f020

Browse files
authored
Merge pull request #184 from TaloDev/develop
Release 0.26.0
2 parents 242bd50 + 5b2be91 commit 2e7f020

File tree

9 files changed

+247
-74
lines changed

9 files changed

+247
-74
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Talo's dashboard lets you see your players and interact with your game directly.
1111
- 💾 [Game saves](https://trytalo.com/saves)
1212
- 📊 [Game stats](https://trytalo.com/stats) (global and per-player)
1313
- ⚙️ [Live config](https://trytalo.com/live-config) (update your game config from the web, no releases required)
14+
- 🔧 [Steamworks integration](https://trytalo.com/steamworks-integration)
1415

1516
## Docs
1617

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"lint-staged": {
7272
"*.{js,jsx}": "eslint --fix"
7373
},
74-
"version": "0.25.2",
74+
"version": "0.26.0",
7575
"engines": {
7676
"node": "16.x"
7777
}

src/Router.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const PlayerProfile = lazy(() => import(/* webpackChunkName: 'player-profile' */
3838
const PlayerLeaderboardEntries = lazy(() => import(/* webpackChunkName: 'player-leaderboard-entries' */ './pages/PlayerLeaderboardEntries'))
3939
const ForgotPassword = lazy(() => import(/* webpackChunkName: 'forgot-password' */ './pages/ForgotPassword'))
4040
const ResetPassword = lazy(() => import(/* webpackChunkName: 'reset-password' */ './pages/ResetPassword'))
41+
const PlayerSaves = lazy(() => import(/* webpackChunkName: 'player-saves' */ './pages/PlayerSaves'))
42+
const PlayerSaveContent = lazy(() => import(/* webpackChunkName: 'player-save-content' */ './pages/PlayerSaveContent'))
4143

4244
function Router({ intendedUrl }) {
4345
const user = useRecoilValue(userState)
@@ -91,6 +93,8 @@ function Router({ intendedUrl }) {
9193
{canViewPage(user, routes.gameProps) && <Route exact path={routes.gameProps} element={<GameProps />} />}
9294
<Route exact path={routes.playerProfile} element={<PlayerProfile />} />
9395
<Route exact path={routes.playerLeaderboardEntries} element={<PlayerLeaderboardEntries />} />
96+
<Route exact path={routes.playerSaveContent} element={<PlayerSaveContent />} />
97+
<Route exact path={routes.playerSaves} element={<PlayerSaves />} />
9498
</>
9599
}
96100

src/api/usePlayerSaves.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import useSWR from 'swr'
2+
import buildError from '../utils/buildError'
3+
import api from './api'
4+
5+
const usePlayerEvents = (activeGame, playerId) => {
6+
const fetcher = async (url) => {
7+
const res = await api.get(url)
8+
return res.data
9+
}
10+
11+
const { data, error } = useSWR(
12+
`/games/${activeGame.id}/players/${playerId}/saves`,
13+
fetcher
14+
)
15+
16+
return {
17+
saves: data?.saves ?? [],
18+
loading: !data && !error,
19+
error: error && buildError(error),
20+
errorStatusCode: error && error.response?.status
21+
}
22+
}
23+
24+
export default usePlayerEvents

src/constants/routes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export default {
2222
playerLeaderboardEntries: '/players/:id/leaderboard-entries',
2323
playerProfile: '/players/:id/profile',
2424
playerProps: '/players/:id/props',
25+
playerSaves: '/players/:id/saves',
26+
playerSaveContent: '/players/:id/saves/:saveId',
2527
playerStats: '/players/:id/stats',
2628
recover: '/recover',
2729
register: '/register',

src/pages/PlayerProfile.jsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Table from '../components/tables/Table'
1212
import SecondaryTitle from '../components/SecondaryTitle'
1313
import PlayerAliases from '../components/PlayerAliases'
1414
import Identifier from '../components/Identifier'
15-
import { IconBolt, IconChartBar, IconSettings, IconTrophy } from '@tabler/icons-react'
15+
import { IconBolt, IconChartBar, IconDeviceFloppy, IconSettings, IconTrophy } from '@tabler/icons-react'
1616
import Button from '../components/Button'
1717
import Loading from '../components/Loading'
1818

@@ -36,6 +36,11 @@ const links = [
3636
name: 'Entries',
3737
icon: IconTrophy,
3838
route: routes.playerLeaderboardEntries
39+
},
40+
{
41+
name: 'Saves',
42+
icon: IconDeviceFloppy,
43+
route: routes.playerSaves
3944
}
4045
]
4146

src/pages/PlayerSaveContent.jsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useCallback, useEffect, useRef, useState } from 'react'
2+
import { useLocation, useNavigate, useParams } from 'react-router-dom'
3+
import routes from '../constants/routes'
4+
import Page from '../components/Page'
5+
6+
export default function PlayerSaveContent() {
7+
const { id: playerId } = useParams()
8+
9+
const location = useLocation()
10+
const save = location.state?.save
11+
12+
const [isLoading, setLoading] = useState(true)
13+
14+
useEffect(() => {
15+
console.log(save)
16+
if (!save) {
17+
navigate(routes.playerSaves.replace(':id', playerId))
18+
} else {
19+
setTimeout(() => {
20+
setContent()
21+
}, 200)
22+
}
23+
}, [save])
24+
25+
const navigate = useNavigate()
26+
27+
const embedRef = useRef()
28+
29+
const setContent = useCallback(() => {
30+
embedRef.current.contentWindow.postMessage({
31+
json: save.content,
32+
options: {
33+
theme: 'dark',
34+
direction: 'RIGHT'
35+
}
36+
}, '*')
37+
38+
setLoading(false)
39+
}, [])
40+
41+
return (
42+
<Page
43+
showBackButton
44+
title={save?.name ?? 'Save content'}
45+
isLoading={isLoading}
46+
>
47+
<iframe
48+
ref={embedRef}
49+
src='https://jsoncrack.com/widget'
50+
title='save-content'
51+
className='w-full h-[75vh] lg:h-[66vh] xl:h-[75vh] rounded border-2 border-gray-700 overflow-hidden'
52+
/>
53+
</Page>
54+
)
55+
}

src/pages/PlayerSaves.jsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { useEffect } from 'react'
2+
import { useNavigate, useParams } from 'react-router-dom'
3+
import ErrorMessage from '../components/ErrorMessage'
4+
import TableCell from '../components/tables/TableCell'
5+
import TableBody from '../components/tables/TableBody'
6+
import routes from '../constants/routes'
7+
import usePlayerSaves from '../api/usePlayerSaves'
8+
import { format } from 'date-fns'
9+
import DateCell from '../components/tables/cells/DateCell'
10+
import useSortedItems from '../utils/useSortedItems'
11+
import PlayerIdentifier from '../components/PlayerIdentifier'
12+
import Page from '../components/Page'
13+
import usePlayer from '../utils/usePlayer'
14+
import Table from '../components/tables/Table'
15+
import activeGameState from '../state/activeGameState'
16+
import { useRecoilValue } from 'recoil'
17+
import Button from '../components/Button'
18+
19+
export default function PlayerSaves() {
20+
const activeGame = useRecoilValue(activeGameState)
21+
22+
const { id: playerId } = useParams()
23+
const [player] = usePlayer()
24+
25+
const { saves, loading: savesLoading, error, errorStatusCode } = usePlayerSaves(activeGame, playerId)
26+
const sortedSaves = useSortedItems(saves, 'updatedAt')
27+
28+
const navigate = useNavigate()
29+
30+
const loading = !player || savesLoading
31+
32+
useEffect(() => {
33+
if (errorStatusCode === 404) {
34+
navigate(routes.players, { replace: true })
35+
}
36+
}, [errorStatusCode])
37+
38+
const viewSaveContent = (save) => {
39+
const path = routes.playerSaveContent
40+
.replace(':id', playerId)
41+
.replace(':saveId', save.id)
42+
43+
navigate(path, {
44+
state: {
45+
save
46+
}
47+
})
48+
}
49+
50+
return (
51+
<Page
52+
showBackButton
53+
title='Player saves'
54+
isLoading={loading}
55+
>
56+
<PlayerIdentifier player={player} />
57+
58+
{!error && !loading && sortedSaves.length === 0 &&
59+
<p>This player has not created any saves yet</p>
60+
}
61+
62+
{!error && sortedSaves.length > 0 &&
63+
<Table columns={['Name', 'Created at', 'Updated at', '']}>
64+
<TableBody iterator={sortedSaves}>
65+
{(save) => (
66+
<>
67+
<TableCell className='min-w-60'>{save.name}</TableCell>
68+
<DateCell>{format(new Date(save.createdAt), 'dd MMM Y, HH:mm')}</DateCell>
69+
<DateCell>{format(new Date(save.updatedAt), 'dd MMM Y, HH:mm')}</DateCell>
70+
<TableCell className='w-60'>
71+
<Button variant='grey' onClick={() => viewSaveContent(save)}>View content</Button>
72+
</TableCell>
73+
</>
74+
)}
75+
</TableBody>
76+
</Table>
77+
}
78+
79+
{error && <ErrorMessage error={error} />}
80+
</Page>
81+
)
82+
}

0 commit comments

Comments
 (0)