-
- {showDrawProposalsButton && (
-
- )}
-
+
{rightFooterButton}
diff --git a/packages/web/src/screens/GameDetail/OverviewScreen.test.tsx b/packages/web/src/screens/GameDetail/OverviewScreen.test.tsx
new file mode 100644
index 000000000..b9c666bff
--- /dev/null
+++ b/packages/web/src/screens/GameDetail/OverviewScreen.test.tsx
@@ -0,0 +1,63 @@
+import { render, screen, within } from "@testing-library/react";
+import { MemoryRouter, Route, Routes } from "react-router";
+import { describe, expect, it, vi } from "vitest";
+
+import { OverviewScreen } from "./OverviewScreen";
+
+const mockGameData = vi.fn();
+const mockDrawProposalsData = vi.fn();
+
+vi.mock("@/api/generated/endpoints", () => ({
+ useGameRetrieveSuspense: () => ({ data: mockGameData() }),
+ useGamesDrawProposalsList: () => ({ data: mockDrawProposalsData() }),
+}));
+
+vi.mock("@/components/PlayerInfoContent", () => ({
+ PlayerInfoContent: () =>
Players
,
+}));
+
+vi.mock("@/components/GameDropdownMenu", () => ({
+ GameDropdownMenu: () =>
,
+}));
+
+vi.mock("./AppBar", () => ({
+ GameDetailAppBar: ({ rightButton }: { rightButton?: React.ReactNode }) => (
+
+ ),
+}));
+
+const renderOverview = () =>
+ render(
+
+
+ }
+ />
+
+
+ );
+
+describe("OverviewScreen draw proposals", () => {
+ it("shows the active-proposal notification badge on the button", () => {
+ mockGameData.mockReturnValue({
+ status: "active",
+ sandbox: false,
+ });
+ mockDrawProposalsData.mockReturnValue([
+ {
+ id: 1,
+ status: "pending",
+ myVote: { included: true, accepted: null },
+ },
+ ]);
+
+ renderOverview();
+
+ const link = screen.getByRole("link", { name: /draw proposals/i });
+ expect(within(link).getByText("1")).toBeInTheDocument();
+ expect(
+ screen.getByRole("button", { name: /game menu/i })
+ ).toBeInTheDocument();
+ });
+});
diff --git a/packages/web/src/screens/GameDetail/OverviewScreen.tsx b/packages/web/src/screens/GameDetail/OverviewScreen.tsx
new file mode 100644
index 000000000..b0df60064
--- /dev/null
+++ b/packages/web/src/screens/GameDetail/OverviewScreen.tsx
@@ -0,0 +1,85 @@
+import React, { Suspense } from "react";
+import { Handshake } from "lucide-react";
+import { Link, useNavigate } from "react-router";
+
+import {
+ useGameRetrieveSuspense,
+ useGamesDrawProposalsList,
+} from "@/api/generated/endpoints";
+import {
+ DrawProposalNotificationBadge,
+ getDrawProposalNotificationCount,
+} from "@/components/DrawProposalNotificationBadge";
+import { GameDropdownMenu } from "@/components/GameDropdownMenu";
+import { Panel } from "@/components/Panel";
+import { PlayerInfoContent } from "@/components/PlayerInfoContent";
+import { QueryErrorBoundary } from "@/components/QueryErrorBoundary";
+import { Button } from "@/components/ui/button";
+import { useRequiredParams } from "@/hooks";
+import { GameDetailAppBar } from "./AppBar";
+
+const OverviewScreen: React.FC = () => {
+ const navigate = useNavigate();
+ const { gameId, phaseId } = useRequiredParams<{
+ gameId: string;
+ phaseId: string;
+ }>();
+ const { data: game } = useGameRetrieveSuspense(gameId);
+
+ const isStartedGame =
+ game.status === "active" ||
+ game.status === "completed" ||
+ game.status === "abandoned";
+ const showDrawProposals = !game.sandbox && isStartedGame;
+ const { data: drawProposals } = useGamesDrawProposalsList(gameId, {
+ query: { enabled: showDrawProposals },
+ });
+ const drawProposalNotificationCount =
+ getDrawProposalNotificationCount(drawProposals);
+
+ return (
+
+
+ navigate(`/game/${gameId}/phase/${phaseId}/game-info`)
+ }
+ />
+ }
+ />
+
+
+
+
+
+ {showDrawProposals && (
+
+
+
+ )}
+
+
+
+ );
+};
+
+const OverviewScreenSuspense: React.FC = () => (
+
+ }>
+
+
+
+);
+
+export { OverviewScreenSuspense as OverviewScreen };
diff --git a/packages/web/src/screens/GameDetail/PlayerInfoScreen.tsx b/packages/web/src/screens/GameDetail/PlayerInfoScreen.tsx
index d2522b46b..4fd552bdc 100644
--- a/packages/web/src/screens/GameDetail/PlayerInfoScreen.tsx
+++ b/packages/web/src/screens/GameDetail/PlayerInfoScreen.tsx
@@ -1,39 +1,23 @@
-import React, { Suspense } from "react";
-import { useNavigate } from "react-router";
-import { GameDetailAppBar } from "./AppBar";
-import { Panel } from "@/components/Panel";
-import { PlayerInfoContent } from "@/components/PlayerInfoContent";
+import React from "react";
+import { Navigate, useLocation } from "react-router";
import { useRequiredParams } from "@/hooks";
const PlayerInfoScreen: React.FC = () => {
- const navigate = useNavigate();
+ const location = useLocation();
const { gameId, phaseId } = useRequiredParams<{
gameId: string;
phaseId: string;
}>();
return (
-
-
navigate(`/game/${gameId}/phase/${phaseId}`)}
- variant="secondary"
- />
-
-
+
);
};
-const PlayerInfoScreenSuspense: React.FC = () => (
-
}>
-