fix(crazygames): guest username on logout, hide fullscreen, in-game pop-ups (#4538)

Addresses four requests from the CrazyGames platform team.
Supersedes #4520 (closed when its branch was renamed to `crazygames`).

## 1. Username reverts to guest on CrazyGames logout
When a player logged into their CrazyGames account and then logged out,
the username stayed the CrazyGames name. It now reverts to a fresh guest
(`AnonXXX`) name. A `crazyGamesLoggedIn` flag guards this so it only
fires on a real login→logout transition (not on the initial "not logged
in" state, which would otherwise wipe a stored name).

## 2. Fullscreen button hidden on CrazyGames
CrazyGames provides its own fullscreen control in the game frame, so our
in-game fullscreen button is now hidden when running on CrazyGames (and
unchanged everywhere else).

## 3. Native browser prompts → in-game pop-ups
The `showInGameConfirm()` / `showInGameAlert()` helpers (promise-based,
callable from non-Lit contexts like WebSocket/popstate handlers) drive
the existing shared `<confirm-dialog>` component, so styling stays
consistent with the rest of the app. Every native `confirm()`/`alert()`
shown **during gameplay** now uses it:
- Exit-game confirmation (right sidebar + browser-back path)
- Kick-player confirmation
- Host-left notice (dispatches leave-lobby only after dismiss,
preserving the old blocking UX)
- Connection-refused notice (also removes a stale `// TODO: make this a
modal`)

## 4. `gameplayStop` when Settings menu / pop-up is open
- The pop-up helpers report `gameplayStop()` while shown and
`gameplayStart()` on dismiss.
- Settings + Graphics Settings modals now report `gameplayStop` whenever
the menu is open — previously it only fired when the modal *also* paused
the sim (singleplayer / lobby-creator), so regular multiplayer players
never triggered it. Also fixes graphics-settings so gameplay resumes
correctly on close for those players.

## Scope
Per request, this covers **in-game dialogs only**. The main-menu native
alerts (store/checkout, account, subscriptions, friends, login) were
intentionally left as-is.

## Testing
- `tsc --noEmit`, `eslint`, and `prettier --check` all pass.
- Verified the confirm and alert pop-ups render correctly in the running
app (shared `<confirm-dialog>`, danger/warning variants), resolve their
promises, and tear down their portal.
- CrazyGames-iframe-only behaviors (username revert, `gameplayStop`,
fullscreen hiding) are verified by code inspection — they require
running inside the actual CrazyGames frame.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Evan
2026-07-08 12:02:37 -07:00
committed by evanpelle
parent 9e963dedf1
commit e2d6231309
11 changed files with 137 additions and 50 deletions
@@ -26,9 +26,15 @@ vi.mock("../../../../src/client/components/ui/ActionButton", () => ({
actionButton: vi.fn((props: unknown) => props),
}));
vi.mock("../../../../src/client/InGameModal", () => ({
showInGameConfirm: vi.fn(),
showInGameAlert: vi.fn(),
}));
import { actionButton } from "../../../../src/client/components/ui/ActionButton";
import { PlayerModerationModal } from "../../../../src/client/hud/layers/PlayerModerationModal";
import { PlayerPanel } from "../../../../src/client/hud/layers/PlayerPanel";
import { showInGameConfirm } from "../../../../src/client/InGameModal";
import { SendKickPlayerIntentEvent } from "../../../../src/client/Transport";
import { PlayerView } from "../../../../src/client/view";
import { PlayerType } from "../../../../src/core/game/Game";
@@ -120,15 +126,12 @@ describe("PlayerPanel - kick player moderation", () => {
});
describe("PlayerModerationModal - kick confirmation", () => {
const originalConfirm = globalThis.confirm;
afterEach(() => {
vi.clearAllMocks();
globalThis.confirm = originalConfirm;
});
test("emits SendKickPlayerIntentEvent and dispatches kicked when confirmed", () => {
(globalThis as any).confirm = vi.fn(() => true);
test("emits SendKickPlayerIntentEvent and dispatches kicked when confirmed", async () => {
(showInGameConfirm as ReturnType<typeof vi.fn>).mockResolvedValue(true);
const modal = new PlayerModerationModal();
const eventBus = { emit: vi.fn() };
@@ -148,7 +151,7 @@ describe("PlayerModerationModal - kick confirmation", () => {
const kickedListener = vi.fn();
modal.addEventListener("kicked", kickedListener as any);
(modal as any).handleKickClick({ stopPropagation: vi.fn() });
await (modal as any).handleKickClick({ stopPropagation: vi.fn() });
expect(eventBus.emit).toHaveBeenCalledTimes(1);
const event = eventBus.emit.mock.calls[0][0] as SendKickPlayerIntentEvent;
@@ -160,8 +163,8 @@ describe("PlayerModerationModal - kick confirmation", () => {
expect(kickedEvent.detail).toEqual({ playerId: "2" });
});
test("does not emit when confirmation is cancelled", () => {
(globalThis as any).confirm = vi.fn(() => false);
test("does not emit when confirmation is cancelled", async () => {
(showInGameConfirm as ReturnType<typeof vi.fn>).mockResolvedValue(false);
const modal = new PlayerModerationModal();
const eventBus = { emit: vi.fn() };
@@ -181,7 +184,7 @@ describe("PlayerModerationModal - kick confirmation", () => {
const kickedListener = vi.fn();
modal.addEventListener("kicked", kickedListener as any);
(modal as any).handleKickClick({ stopPropagation: vi.fn() });
await (modal as any).handleKickClick({ stopPropagation: vi.fn() });
expect(eventBus.emit).not.toHaveBeenCalled();
expect(kickedListener).not.toHaveBeenCalled();