웹: PC 중심 화면으로 재구성

- 공통 상단 메뉴(사이트 이름·홈·게임 목록·관리자·어디서나 코드 입력·내 정보)
- 홈: 방 만들기 + 코드 입력 카드, 게임 격자, 지금 열린 공개 방 목록(/api/public-rooms)
- 대기실: 두 칸(초대·설정 바로 수정·규칙 요약 / 자리·시작 버튼·채팅 항상 보임)
- 게임 화면: 판(화면 높이에 맞춤, 좌표 표시) + 오른쪽 패널(상태·선수·시계·버튼·기보·채팅)
- 오목: 마우스 올리면 미리 보기 돌, PC는 한 번 클릭으로 착수(두 번 눌러 확정은 터치 기기 기본값,
  기기별 설정), GameUI에 Side 패널 추가
- 채팅: PC는 패널로 항상 표시(Enter로 바로 입력), 휴대폰은 시트
- 관리자: 왼쪽 사이드바, 사용자 표, 규칙 입력 두 칸 격자, 저장 바 고정
- 좁은 화면은 같은 화면을 한 칸으로 접어서 지원(방 상단 줄 줄바꿈 수정)
- E2E: PC 1440×900 기준(방장 PC, 친구 휴대폰), 관리자 E2E도 PC

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-10-04 05:04:00 +09:00
parent 847a714077
commit f81f20a4bf
20 changed files with 1172 additions and 392 deletions

View File

@@ -38,7 +38,7 @@ const step = async (name: string, fn: () => Promise<void>) => {
};
try {
const adminCtx = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true, locale: 'ko-KR' });
const adminCtx = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'ko-KR' });
// Fake Discord: Playwright does not route redirect targets, so intercept our own /start, let the server
// set its state cookie, and bounce straight back to the callback with the superadmin's ID as the code.
await adminCtx.route(`${ORIGIN}/api/auth/discord/start*`, async (route) => {
@@ -70,7 +70,7 @@ try {
});
await step('게임 설정: 오목 렌주룰 고정 + 한 수 45초 저장', async () => {
await a.getByRole('link', { name: '게임' }).click();
await a.locator('.admin-side').getByRole('link', { name: '게임', exact: true }).click();
await a.getByRole('button', { name: /오목/ }).click();
await a.getByText('기본 규칙').waitFor();
const rule = a.locator('div.stack:has(> div.row > strong:text-is("규칙"))').first();
@@ -78,13 +78,13 @@ try {
await rule.getByLabel(/방장 변경 금지/).check();
const per = a.locator('div.stack:has(> div.row > strong:text-is("한 수 제한(초)"))').first();
await per.locator('input[type=number]').fill('45');
await a.screenshot({ path: join(SHOTS, 'admin-2-game-editor.png'), fullPage: true });
await a.screenshot({ path: join(SHOTS, 'admin-2-game-editor.png') });
await a.getByRole('button', { name: '저장', exact: true }).click();
await a.getByText('저장했어요.').waitFor();
});
await step('관리자 탭: 디스코드 ID로 어드민 추가', async () => {
await a.getByRole('link', { name: '관리자' }).click();
await a.locator('.admin-side').getByRole('link', { name: '관리자', exact: true }).click();
await a.getByLabel('디스코드 ID(숫자)').fill('222222222222222222');
await a.getByLabel('메모(선택)').fill('친구');
await a.getByRole('button', { name: '추가', exact: true }).click();
@@ -93,7 +93,7 @@ try {
});
await step('기록 탭에 변경 내역', async () => {
await a.getByRole('link', { name: '기록' }).click();
await a.locator('.admin-side').getByRole('link', { name: '기록', exact: true }).click();
await a.getByText('어드민 추가').waitFor();
await a.getByText('게임 설정 변경').waitFor();
});