mancala/apps/frontend/src/routes/LobyPage.tsx
Halit Aksoy a65662f695 feat(frontend): show ongoing, live, popular and recent games on the home page
- Banner for the user's game in progress with a Continue button
- Live games as cards with the real board, updated over WebSocket; the
  most watched finished games when nothing is live
- The user's last 3 games, from their side of the board, with the result
  as they would say it
- The lobby offers to continue the game in progress when a new one is
  refused
- Spectators register once connected and identified, and again after a
  reconnect, so they are counted
- The backend includes games that ended but are not saved yet in /game/:id
  and the lists
2026-10-16 00:27:06 +03:00

109 lines
4.4 KiB
TypeScript

import { CommonMancalaGame, MancalaGame } from 'mancala.js';
import * as React from 'react';
import { FunctionComponent, useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import Center from '../components/Center';
import CircularPanel from '../components/CircularPanel';
import HeaderBar from '../components/headerbar/HeaderBar';
import HeaderbarIcon from '../components/headerbar/HeaderbarIcon';
import HeaderbarTitle from '../components/headerbar/HeaderbarTitle';
import ThemeSwitchMenu from '../components/headerbar/ThemeSwitchMenu';
import PageContainer from '../components/PageContainer';
import Row from '../components/Row';
import { channel_cancel_new_game, channel_on_game_start, channel_on_new_game_rejected } from '@mancala/core';
import { Context } from '../context/context';
import { Theme } from '@mancala/core';
import { getColorByBrightness } from "@mancala/core";
import Button from "../components/Button";
import { useTranslation } from 'react-i18next';
const LobyPage: FunctionComponent<{
context: Context,
userKey?: string,
theme: Theme
}> = ({ context, userKey, theme }) => {
let navigate = useNavigate();
const { t } = useTranslation();
// Set when the server refuses a new game because the user has one in progress.
const [activeGameId, setActiveGameId] = useState<string | undefined>(undefined);
const onNewGameRejected = (message: { gameId: string }) => setActiveGameId(message.gameId);
const onGameStart = (message: Object) => {
const newGame: CommonMancalaGame = message as CommonMancalaGame;
navigate(`/game/${newGame.id}`)
}
const onCancelNewGameClick = () => {
if (context.rtmt.connectionState === "connected") {
context.rtmt.sendMessage(channel_cancel_new_game, "");
}
navigate("/")
}
useEffect(() => {
context.rtmt.addMessageListener(channel_on_game_start, onGameStart);
context.rtmt.addMessageListener(channel_on_new_game_rejected, onNewGameRejected);
context.rtmt.sendMessage("new_game", {});
return () => {
context.rtmt.removeMessageListener(channel_on_game_start, onGameStart);
context.rtmt.removeMessageListener(channel_on_new_game_rejected, onNewGameRejected);
}
}, []);
const textColorOnAppBar = getColorByBrightness(
context.themeManager.theme.appBarBgColor,
context.themeManager.theme.textColor,
context.themeManager.theme.textLightColor
);
const textColorOnBoard = getColorByBrightness(
context.themeManager.theme.boardColor,
context.themeManager.theme.textColor,
context.themeManager.theme.textLightColor
);
return (
<PageContainer theme={theme!}>
<HeaderBar color={theme?.appBarBgColor}>
<Row>
<Link style={{ textDecoration: 'none' }} to={"/"}>
<HeaderbarIcon />
</Link>
<Link style={{ textDecoration: 'none' }} to={"/"}>
<HeaderbarTitle title={t("Mancala")} color={textColorOnAppBar} />
</Link>
</Row>
<Row>
<ThemeSwitchMenu context={context} textColor={textColorOnAppBar} />
</Row>
</HeaderBar>
<Center>
<div style={{
display: "flex",
flexDirection: "column",
alignContent: "center"
}}>
<CircularPanel color={context.themeManager.theme.boardColor}>
<h4 style={{ margin: "0", color: textColorOnBoard }}>
{activeGameId ? t("GameInProgress") : `${t("SearchingOpponent")} ${t("PleaseWait")}...`}
</h4>
</CircularPanel>
{activeGameId && <Button
context={context}
color={context.themeManager.theme.pitColor}
text={t("Continue")}
onClick={() => navigate(`/game/${activeGameId}`)} />}
<Button
context={context}
color={context.themeManager.theme.boardColor}
text={t("Cancel")}
onClick={onCancelNewGameClick} />
</div>
</Center>
</PageContainer>
);
}
export default LobyPage;