Fix client loading as atomcms normal

This commit is contained in:
openhands committed 2026-07-03 15:16:47 +02:00
1 parent bedf0ee7fa
commit 7e5813bd97
3 files changed
+150 -64

No files matched your search

+125 -53
View File
@@ -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<string | null>(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 (
<div className="flex min-h-screen flex-col items-center justify-center" style={{ backgroundColor: "var(--color-background)", padding: "2rem" }}>
@@ -65,7 +93,6 @@ export default function ClientPage() {
);
}
// ── Error (auth) ──
if (errorType === "auth") {
return (
<div className="flex min-h-screen flex-col" style={{ backgroundColor: "var(--color-background)" }}>
@@ -88,7 +115,6 @@ export default function ClientPage() {
);
}
// ── Error (generic) ──
if (errorType === "generic" || !ticket) {
return (
<div className="flex min-h-screen flex-col" style={{ backgroundColor: "var(--color-background)" }}>
@@ -99,51 +125,97 @@ export default function ClientPage() {
<span className="font-bold text-lg">{hotelName || "Atom"}</span>
</header>
<main className="flex-1 flex items-center justify-center">
<ContentCard icon="⚠" title={t("title")} subtitle={error ?? t("errors.generic")}>
<EmptyState icon="⚠">{error ?? t("errors.generic")}</EmptyState>
</ContentCard>
<p className="text-center" style={{ color: "var(--color-text-muted)" }}>
{error ?? t("errors.generic")}
</p>
</main>
</div>
);
}
// ── Success (client launched) ──
const clientSrc = getClientSrc();
return (
<div className="flex min-h-screen flex-col" style={{ backgroundColor: "var(--color-background)" }}>
<header
className="flex h-14 items-center justify-between border-b px-4"
style={{
backgroundColor: "var(--color-navbar)",
borderColor: "color-mix(in srgb, var(--color-text-muted) 15%, transparent)",
color: "var(--color-navbar-text)",
}}
>
<div className="flex items-center gap-4">
<span className="font-bold text-lg">{hotelName}</span>
<span className="text-sm" style={{ color: "var(--color-navbar-text)" }}>
{t("connected")}
</span>
<div className="relative w-screen h-screen overflow-hidden site-bg" style={{ backgroundColor: "var(--color-background)" }}>
{/* Floating Toolbar */}
<div className="fixed top-4 left-4 z-50 flex items-center gap-2">
{/* Home Button */}
<Link
href="/"
className="w-10 h-10 rounded-lg border-2 cursor-pointer flex items-center justify-center shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:shadow-xl active:translate-y-0"
style={{
borderColor: "color-mix(in srgb, var(--color-primary, #eeb425) 75%, black)",
background: "linear-gradient(135deg, var(--color-primary, #eeb425) 0%, color-mix(in srgb, var(--color-primary, #eeb425) 80%, black) 100%)",
color: "var(--button-text-color, #1a1a2e)",
}}
title="Home"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
</Link>
{/* Reload Button */}
<button
onClick={() => window.location.reload()}
className="w-10 h-10 rounded-lg border-2 cursor-pointer flex items-center justify-center shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:shadow-xl active:translate-y-0"
style={{
borderColor: "color-mix(in srgb, var(--color-primary, #eeb425) 75%, black)",
background: "linear-gradient(135deg, var(--color-primary, #eeb425) 0%, color-mix(in srgb, var(--color-primary, #eeb425) 80%, black) 100%)",
color: "var(--button-text-color, #1a1a2e)",
}}
title="Herladen"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="23 4 23 10 17 10" />
<polyline points="1 20 1 14 7 14" />
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" />
</svg>
</button>
{/* Fullscreen Button */}
<button
onClick={toggleFullscreen}
className="w-10 h-10 rounded-lg border-2 cursor-pointer flex items-center justify-center shadow-lg transition-all duration-200 hover:-translate-y-0.5 hover:shadow-xl active:translate-y-0"
style={{
borderColor: "color-mix(in srgb, var(--color-primary, #eeb425) 75%, black)",
background: "linear-gradient(135deg, var(--color-primary, #eeb425) 0%, color-mix(in srgb, var(--color-primary, #eeb425) 80%, black) 100%)",
color: "var(--button-text-color, #1a1a2e)",
}}
title="Fullscreen"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
</svg>
</button>
{/* Online Count Button */}
<div
className="h-10 px-3 rounded-lg border-2 flex items-center justify-center gap-1.5 shadow-lg select-none"
style={{
borderColor: "color-mix(in srgb, var(--color-primary, #eeb425) 75%, black)",
background: "linear-gradient(135deg, var(--color-primary, #eeb425) 0%, color-mix(in srgb, var(--color-primary, #eeb425) 80%, black) 100%)",
color: "var(--button-text-color, #1a1a2e)",
}}
title="Online gebruikers"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
<span className="text-xs font-bold">{onlineCount}</span>
</div>
</header>
<main className="flex-1 flex items-center justify-center" style={{ backgroundColor: "var(--color-background)" }}>
<ContentCard>
<div style={{ textAlign: "center" }}>
<h1 style={{ fontSize: "1.75rem", fontWeight: 700, marginBottom: "0.5rem" }}>
{t("welcome", { hotel: hotelName })}
</h1>
<p style={{ color: "var(--color-text-muted)", marginBottom: "2rem" }}>
{t("openingClient")} {t("fallbackLink")}{" "}
<a
href={`${clientUrl}${clientUrl.includes("?") ? "&" : "?"}sso=${encodeURIComponent(ticket)}`}
target="_blank"
rel="noopener noreferrer"
>
{t("fallbackClick")}
</a>.
</p>
</div>
</ContentCard>
</main>
</div>
{/* Game Iframe */}
<iframe
id="nitro"
src={clientSrc}
className="absolute inset-0 w-full h-full border-0 m-0 p-0 overflow-hidden"
title={hotelName}
allow="autoplay; fullscreen; gamepad"
/>
</div>
);
}