diff --git a/src/app/client/page.tsx b/src/app/client/page.tsx index f454b1a7..49e3d4ed 100644 --- a/src/app/client/page.tsx +++ b/src/app/client/page.tsx @@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { useTranslations } from "next-intl"; -import { ContentCard, EmptyState, SkeletonCard } from "@/components/public/ui"; +import { ContentCard, SkeletonCard } from "@/components/public/ui"; export default function ClientPage() { const t = useTranslations("pages.client"); @@ -13,7 +13,8 @@ export default function ClientPage() { const [error, setError] = useState(null); const [errorType, setErrorType] = useState<"auth" | "generic" | null>(null); const [hotelName, setHotelName] = useState(""); - const [launched, setLaunched] = useState(false); + const [isFullscreen, setIsFullscreen] = useState(false); + const [onlineCount, setOnlineCount] = useState(0); const fetchedRef = useRef(false); useEffect(() => { @@ -33,7 +34,7 @@ export default function ClientPage() { setTicket(data.ticket); setHotelName(data.hotelName); if (data.clientUrl) setClientUrl(data.clientUrl); - } catch (e) { + } catch { setErrorType("generic"); setError(t("errors.generic")); } finally { @@ -43,20 +44,47 @@ export default function ClientPage() { fetchTicket(); }, [t]); - const openClient = useCallback((tk: string) => { - const sep = clientUrl.includes("?") ? "&" : "?"; - const fullUrl = `${clientUrl}${sep}sso=${encodeURIComponent(tk)}`; - window.open(fullUrl, "_blank", "noopener,noreferrer"); - }, [clientUrl]); + useEffect(() => { + async function fetchOnline() { + try { + const res = await fetch("/api/online/count"); + if (res.ok) { + const data = await res.json(); + setOnlineCount(data.count ?? 0); + } + } catch { + // ignore + } + } + fetchOnline(); + const interval = setInterval(fetchOnline, 15000); + return () => clearInterval(interval); + }, []); + + const toggleFullscreen = useCallback(() => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + setIsFullscreen(true); + } else { + document.exitFullscreen(); + setIsFullscreen(false); + } + }, []); useEffect(() => { - if (ticket && !launched) { - setLaunched(true); - openClient(ticket); + function onFullscreenChange() { + setIsFullscreen(!!document.fullscreenElement); } - }, [ticket, launched, openClient]); + document.addEventListener("fullscreenchange", onFullscreenChange); + return () => document.removeEventListener("fullscreenchange", onFullscreenChange); + }, []); + + const getClientSrc = useCallback(() => { + if (!ticket) return ""; + const sep = clientUrl.includes("?") ? "&" : "?"; + return `${clientUrl}${sep}sso=${encodeURIComponent(ticket)}`; + }, [clientUrl, ticket]); - // ── Loading ── if (loading) { return (
@@ -65,7 +93,6 @@ export default function ClientPage() { ); } - // ── Error (auth) ── if (errorType === "auth") { return (
@@ -88,7 +115,6 @@ export default function ClientPage() { ); } - // ── Error (generic) ── if (errorType === "generic" || !ticket) { return (
@@ -99,51 +125,97 @@ export default function ClientPage() { {hotelName || "Atom"}
- - {error ?? t("errors.generic")} - +

+ {error ?? t("errors.generic")} +

); } - // ── Success (client launched) ── + const clientSrc = getClientSrc(); + return ( -
-
-
- {hotelName} - - {t("connected")} - +
+ {/* Floating Toolbar */} +
+ {/* Home Button */} + + + + + + + + {/* Reload Button */} + + + {/* Fullscreen Button */} + + + {/* Online Count Button */} +
+ + + + + {onlineCount}
-
-
- -
-

- {t("welcome", { hotel: hotelName })} -

-

- {t("openingClient")} {t("fallbackLink")}{" "} - - {t("fallbackClick")} - . -

-
-
-
+
+ + {/* Game Iframe */} +