P15a: hosting a shared session requires a signed-in GM
- The Host control now checks cloudUsername(): when the GM isn't signed in it shows "🔒 Sign in to host" (→ Settings) instead of "📡 Host". Players still join as guests. - Realtime e2e: the GM marks itself signed in before hosting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 (
|
||||
<Button size="sm" variant="ghost" title="Sign in to host a shared session" onClick={() => void navigate({ to: '/settings' })}>
|
||||
🔒 Sign in to host
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button size="sm" variant="ghost" title="Host a live session for players"
|
||||
onClick={() => {
|
||||
|
||||
Reference in New Issue
Block a user