diff --git a/e2e-realtime/realtime.spec.ts b/e2e-realtime/realtime.spec.ts index 64282b4..57e7b22 100644 --- a/e2e-realtime/realtime.spec.ts +++ b/e2e-realtime/realtime.spec.ts @@ -12,8 +12,9 @@ test('GM hosts a session; a player on another device sees live combat', async ({ const gmCtx = await browser.newContext(); const playerCtx = await browser.newContext(); - // GM: load the sample campaign and host a session. + // GM: load the sample campaign and host a session (hosting requires a signed-in GM). const gm = await fresh(gmCtx); + await gm.evaluate(() => localStorage.setItem('ttrpg-cloud-user', 'GM')); gm.on('dialog', (d) => d.dismiss()); // dismiss the optional-password prompt await gm.getByLabel('Settings').click(); await gm.getByRole('button', { name: 'Load sample campaign' }).click(); diff --git a/e2e-realtime/seats.spec.ts b/e2e-realtime/seats.spec.ts index e89e787..498742c 100644 --- a/e2e-realtime/seats.spec.ts +++ b/e2e-realtime/seats.spec.ts @@ -12,8 +12,9 @@ test('a player claims a seat and manages their character; edits round-trip to th const gmCtx = await browser.newContext(); const playerCtx = await browser.newContext(); - // GM: sample campaign + host. + // GM: sample campaign + host (hosting requires a signed-in GM). const gm = await fresh(gmCtx); + await gm.evaluate(() => localStorage.setItem('ttrpg-cloud-user', 'GM')); gm.on('dialog', (d) => d.dismiss()); // optional-password prompt await gm.getByLabel('Settings').click(); await gm.getByRole('button', { name: 'Load sample campaign' }).click(); diff --git a/src/features/play/SessionControl.tsx b/src/features/play/SessionControl.tsx index d5feed1..095bdf1 100644 --- a/src/features/play/SessionControl.tsx +++ b/src/features/play/SessionControl.tsx @@ -1,13 +1,16 @@ import { useState } from 'react'; +import { useNavigate } from '@tanstack/react-router'; import { useLiveQuery } from 'dexie-react-hooks'; import { useSessionStore, type SeatRequest } from '@/stores/sessionStore'; import { hostSession, stopSession, grantSeat } from '@/lib/sync/wsSync'; import { charactersRepo } from '@/lib/db/repositories'; +import { cloudUsername } from '@/lib/cloud/client'; import { useActiveCampaign } from '@/features/campaigns/hooks'; import { Button } from '@/components/ui/Button'; /** Header control: GM hosts a live session, shares the code, and approves seats. */ export function SessionControl() { + const navigate = useNavigate(); const campaign = useActiveCampaign(); const role = useSessionStore((s) => s.role); const status = useSessionStore((s) => s.status); @@ -20,6 +23,14 @@ export function SessionControl() { if (role === 'off') { if (!campaign) return null; + // Hosting a shared session requires the GM to be signed in to an account. + if (!cloudUsername()) { + return ( + + ); + } return (