aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/app
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-03-20 23:28:39 +0900
committernsfisis <nsfisis@gmail.com>2025-03-20 23:28:39 +0900
commit1afd2781818ef5cba0f018811f12cd8653da10b6 (patch)
treef04146c6e8753b07d6d29765f615ab9094cd3f54 /frontend/app
parenta92aa377d536fe67fa1ff485566da38cf94328bb (diff)
downloadphperkaigi-2025-albatross-1afd2781818ef5cba0f018811f12cd8653da10b6.tar.gz
phperkaigi-2025-albatross-1afd2781818ef5cba0f018811f12cd8653da10b6.tar.zst
phperkaigi-2025-albatross-1afd2781818ef5cba0f018811f12cd8653da10b6.zip
fix(frontend): fix state corruption
Diffstat (limited to 'frontend/app')
-rw-r--r--frontend/app/components/Gaming/RankingTable.tsx13
-rw-r--r--frontend/app/components/GolfPlayApp.tsx15
-rw-r--r--frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx2
-rw-r--r--frontend/app/components/GolfWatchApp.tsx24
-rw-r--r--frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx10
-rw-r--r--frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx7
-rw-r--r--frontend/app/routes/golf.$gameId.play.tsx31
-rw-r--r--frontend/app/routes/golf.$gameId.watch.tsx32
8 files changed, 77 insertions, 57 deletions
diff --git a/frontend/app/components/Gaming/RankingTable.tsx b/frontend/app/components/Gaming/RankingTable.tsx
index e712ed9..a1e41f5 100644
--- a/frontend/app/components/Gaming/RankingTable.tsx
+++ b/frontend/app/components/Gaming/RankingTable.tsx
@@ -1,11 +1,6 @@
+import { useAtomValue } from "jotai";
import React from "react";
-import type { components } from "../../api/schema";
-
-type RankingEntry = components["schemas"]["RankingEntry"];
-
-type Props = {
- ranking: RankingEntry[];
-};
+import { rankingAtom } from "../../states/watch";
function TableHeaderCell({ children }: { children: React.ReactNode }) {
return (
@@ -33,7 +28,9 @@ function formatUnixTimestamp(timestamp: number) {
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
-export default function RankingTable({ ranking }: Props) {
+export default function RankingTable() {
+ const ranking = useAtomValue(rankingAtom);
+
return (
<div className="overflow-hidden border-2 border-blue-600 rounded-xl">
<table className="min-w-full divide-y divide-gray-400 border-collapse">
diff --git a/frontend/app/components/GolfPlayApp.tsx b/frontend/app/components/GolfPlayApp.tsx
index 0bb66eb..97f7cc4 100644
--- a/frontend/app/components/GolfPlayApp.tsx
+++ b/frontend/app/components/GolfPlayApp.tsx
@@ -1,4 +1,5 @@
import { useAtomValue, useSetAtom } from "jotai";
+import { useHydrateAtoms } from "jotai/utils";
import { useContext, useEffect, useState } from "react";
import { useTimer } from "react-use-precision-timer";
import { useDebouncedCallback } from "use-debounce";
@@ -15,6 +16,7 @@ import {
handleSubmitCodePostAtom,
handleSubmitCodePreAtom,
setCurrentTimestampAtom,
+ setDurationSecondsAtom,
setGameStartedAtAtom,
setLatestGameStateAtom,
} from "../states/play";
@@ -26,14 +28,21 @@ import GolfPlayAppWaiting from "./GolfPlayApps/GolfPlayAppWaiting";
type Game = components["schemas"]["Game"];
type User = components["schemas"]["User"];
+type LatestGameState = components["schemas"]["LatestGameState"];
type Props = {
game: Game;
player: User;
- initialCode: string;
+ initialGameState: LatestGameState;
};
-export default function GolfPlayApp({ game, player, initialCode }: Props) {
+export default function GolfPlayApp({ game, player, initialGameState }: Props) {
+ useHydrateAtoms([
+ [setDurationSecondsAtom, game.duration_seconds],
+ [setGameStartedAtAtom, game.started_at ?? null],
+ [setLatestGameStateAtom, initialGameState],
+ ]);
+
const apiAuthToken = useContext(ApiAuthTokenContext);
const gameStateKind = useAtomValue(gameStateKindAtom);
@@ -131,7 +140,7 @@ export default function GolfPlayApp({ game, player, initialCode }: Props) {
problemTitle={game.problem.title}
problemDescription={game.problem.description}
sampleCode={game.problem.sample_code}
- initialCode={initialCode}
+ initialCode={initialGameState.code}
onCodeChange={onCodeChange}
onCodeSubmit={onCodeSubmit}
/>
diff --git a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
index c4bd772..b9b3159 100644
--- a/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
+++ b/frontend/app/components/GolfPlayApps/GolfPlayAppGaming.tsx
@@ -2,10 +2,10 @@ import { useAtomValue } from "jotai";
import React, { useRef, useState } from "react";
import { Link } from "react-router";
import {
+ calcCodeSize,
gamingLeftTimeSecondsAtom,
scoreAtom,
statusAtom,
- calcCodeSize,
} from "../../states/play";
import type { PlayerProfile } from "../../types/PlayerProfile";
import BorderedContainer from "../BorderedContainer";
diff --git a/frontend/app/components/GolfWatchApp.tsx b/frontend/app/components/GolfWatchApp.tsx
index 5f23cdd..185f41d 100644
--- a/frontend/app/components/GolfWatchApp.tsx
+++ b/frontend/app/components/GolfWatchApp.tsx
@@ -1,4 +1,5 @@
-import { useAtom, useAtomValue, useSetAtom } from "jotai";
+import { useAtomValue, useSetAtom } from "jotai";
+import { useHydrateAtoms } from "jotai/utils";
import { useContext, useEffect, useState } from "react";
import { useTimer } from "react-use-precision-timer";
import {
@@ -12,6 +13,7 @@ import {
gameStateKindAtom,
rankingAtom,
setCurrentTimestampAtom,
+ setDurationSecondsAtom,
setGameStartedAtAtom,
setLatestGameStatesAtom,
} from "../states/watch";
@@ -23,19 +25,34 @@ import GolfWatchAppWaiting1v1 from "./GolfWatchApps/GolfWatchAppWaiting1v1";
import GolfWatchAppWaitingMultiplayer from "./GolfWatchApps/GolfWatchAppWaitingMultiplayer";
type Game = components["schemas"]["Game"];
+type LatestGameState = components["schemas"]["LatestGameState"];
+type RankingEntry = components["schemas"]["RankingEntry"];
export type Props = {
game: Game;
+ initialGameStates: { [key: string]: LatestGameState };
+ initialRanking: RankingEntry[];
};
-export default function GolfWatchApp({ game }: Props) {
+export default function GolfWatchApp({
+ game,
+ initialGameStates,
+ initialRanking,
+}: Props) {
+ useHydrateAtoms([
+ [rankingAtom, initialRanking],
+ [setDurationSecondsAtom, game.duration_seconds],
+ [setGameStartedAtAtom, game.started_at ?? null],
+ [setLatestGameStatesAtom, initialGameStates],
+ ]);
+
const apiAuthToken = useContext(ApiAuthTokenContext);
const gameStateKind = useAtomValue(gameStateKindAtom);
const setGameStartedAt = useSetAtom(setGameStartedAtAtom);
const setCurrentTimestamp = useSetAtom(setCurrentTimestampAtom);
const setLatestGameStates = useSetAtom(setLatestGameStatesAtom);
- const [ranking, setRanking] = useAtom(rankingAtom);
+ const setRanking = useSetAtom(rankingAtom);
useTimer({ delay: 1000, startImmediately: true }, setCurrentTimestamp);
@@ -135,7 +152,6 @@ export default function GolfWatchApp({ game }: Props) {
) : (
<GolfWatchAppGamingMultiplayer
gameDisplayName={game.display_name}
- ranking={ranking}
problemTitle={game.problem.title}
problemDescription={game.problem.description}
sampleCode={game.problem.sample_code}
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx
index 981f533..5b975e7 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGaming1v1.tsx
@@ -1,19 +1,19 @@
import { useAtomValue } from "jotai";
import {
+ calcCodeSize,
gamingLeftTimeSecondsAtom,
latestGameStatesAtom,
- calcCodeSize,
} from "../../states/watch";
import type { PlayerProfile } from "../../types/PlayerProfile";
import BorderedContainer from "../BorderedContainer";
-import SubmitStatusLabel from "../SubmitStatusLabel";
-import ThreeColumnLayout from "../ThreeColumnLayout";
-import TitledColumn from "../TitledColumn";
-import UserIcon from "../UserIcon";
import CodeBlock from "../Gaming/CodeBlock";
import LeftTime from "../Gaming/LeftTime";
import ProblemColumn from "../Gaming/ProblemColumn";
import ScoreBar from "../Gaming/ScoreBar";
+import SubmitStatusLabel from "../SubmitStatusLabel";
+import ThreeColumnLayout from "../ThreeColumnLayout";
+import TitledColumn from "../TitledColumn";
+import UserIcon from "../UserIcon";
type Props = {
gameDisplayName: string;
diff --git a/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx b/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx
index a6b9464..b1d6520 100644
--- a/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx
+++ b/frontend/app/components/GolfWatchApps/GolfWatchAppGamingMultiplayer.tsx
@@ -1,5 +1,4 @@
import { useAtomValue } from "jotai";
-import type { components } from "../../api/schema";
import { gamingLeftTimeSecondsAtom } from "../../states/watch";
import LeftTime from "../Gaming/LeftTime";
import ProblemColumn from "../Gaming/ProblemColumn";
@@ -7,11 +6,8 @@ import RankingTable from "../Gaming/RankingTable";
import TitledColumn from "../TitledColumn";
import TwoColumnLayout from "../TwoColumnLayout";
-type RankingEntry = components["schemas"]["RankingEntry"];
-
type Props = {
gameDisplayName: string;
- ranking: RankingEntry[];
problemTitle: string;
problemDescription: string;
sampleCode: string;
@@ -19,7 +15,6 @@ type Props = {
export default function GolfWatchAppGamingMultiplayer({
gameDisplayName,
- ranking,
problemTitle,
problemDescription,
sampleCode,
@@ -43,7 +38,7 @@ export default function GolfWatchAppGamingMultiplayer({
sampleCode={sampleCode}
/>
<TitledColumn title="順位表">
- <RankingTable ranking={ranking} />
+ <RankingTable />
</TitledColumn>
</TwoColumnLayout>
</div>
diff --git a/frontend/app/routes/golf.$gameId.play.tsx b/frontend/app/routes/golf.$gameId.play.tsx
index 1ffe45e..4f8468d 100644
--- a/frontend/app/routes/golf.$gameId.play.tsx
+++ b/frontend/app/routes/golf.$gameId.play.tsx
@@ -1,4 +1,5 @@
-import { useHydrateAtoms } from "jotai/utils";
+import { Provider as JotaiProvider, createStore } from "jotai";
+import { useMemo } from "react";
import type { LoaderFunctionArgs, MetaFunction } from "react-router";
import { useLoaderData } from "react-router";
import { ensureUserLoggedIn } from "../.server/auth";
@@ -8,11 +9,6 @@ import {
apiGetGamePlayLatestState,
} from "../api/client";
import GolfPlayApp from "../components/GolfPlayApp";
-import {
- setDurationSecondsAtom,
- setGameStartedAtAtom,
- setLatestGameStateAtom,
-} from "../states/play";
export const meta: MetaFunction<typeof loader> = ({ data }) => [
{
@@ -48,15 +44,22 @@ export default function GolfPlay() {
const { apiAuthToken, game, player, gameState } =
useLoaderData<typeof loader>();
- useHydrateAtoms([
- [setDurationSecondsAtom, game.duration_seconds],
- [setGameStartedAtAtom, game.started_at ?? null],
- [setLatestGameStateAtom, gameState],
- ]);
+ const store = useMemo(() => {
+ void game.game_id;
+ void player.user_id;
+ return createStore();
+ }, [game.game_id, player.user_id]);
return (
- <ApiAuthTokenContext.Provider value={apiAuthToken}>
- <GolfPlayApp game={game} player={player} initialCode={gameState.code} />
- </ApiAuthTokenContext.Provider>
+ <JotaiProvider store={store}>
+ <ApiAuthTokenContext.Provider value={apiAuthToken}>
+ <GolfPlayApp
+ key={game.game_id}
+ game={game}
+ player={player}
+ initialGameState={gameState}
+ />
+ </ApiAuthTokenContext.Provider>
+ </JotaiProvider>
);
}
diff --git a/frontend/app/routes/golf.$gameId.watch.tsx b/frontend/app/routes/golf.$gameId.watch.tsx
index 42bde52..cd01b17 100644
--- a/frontend/app/routes/golf.$gameId.watch.tsx
+++ b/frontend/app/routes/golf.$gameId.watch.tsx
@@ -1,4 +1,5 @@
-import { useHydrateAtoms } from "jotai/utils";
+import { Provider as JotaiProvider, createStore } from "jotai";
+import { useMemo } from "react";
import type { LoaderFunctionArgs, MetaFunction } from "react-router";
import { useLoaderData } from "react-router";
import { ensureUserLoggedIn } from "../.server/auth";
@@ -9,12 +10,6 @@ import {
apiGetGameWatchRanking,
} from "../api/client";
import GolfWatchApp from "../components/GolfWatchApp";
-import {
- rankingAtom,
- setDurationSecondsAtom,
- setGameStartedAtAtom,
- setLatestGameStatesAtom,
-} from "../states/watch";
export const meta: MetaFunction<typeof loader> = ({ data }) => [
{
@@ -57,16 +52,21 @@ export default function GolfWatch() {
const { apiAuthToken, game, ranking, gameStates } =
useLoaderData<typeof loader>();
- useHydrateAtoms([
- [rankingAtom, ranking],
- [setDurationSecondsAtom, game.duration_seconds],
- [setGameStartedAtAtom, game.started_at ?? null],
- [setLatestGameStatesAtom, gameStates],
- ]);
+ const store = useMemo(() => {
+ void game.game_id;
+ return createStore();
+ }, [game.game_id]);
return (
- <ApiAuthTokenContext.Provider value={apiAuthToken}>
- <GolfWatchApp game={game} />
- </ApiAuthTokenContext.Provider>
+ <JotaiProvider store={store}>
+ <ApiAuthTokenContext.Provider value={apiAuthToken}>
+ <GolfWatchApp
+ key={game.game_id}
+ game={game}
+ initialGameStates={gameStates}
+ initialRanking={ranking}
+ />
+ </ApiAuthTokenContext.Provider>
+ </JotaiProvider>
);
}