로그인 페이지: 디스코드 로그인 크게, 아래 게스트로 시작하기 → /login/guest에서 계정 이름 정하기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-10-07 12:12:55 +09:00
parent bdaaa23af6
commit 843b18aea1
6 changed files with 86 additions and 8 deletions

View File

@@ -4,7 +4,18 @@ import { nicknameError, normalizeNickname, suggestNickname } from '@bg/shared/li
import { api, discordLoginUrl } from '../net/api';
import { useSession } from '../store/session';
export function NicknameForm({ next, submitLabel = '이 이름으로 시작하기', onDone }: { next: string; submitLabel?: string; onDone?: () => void }) {
export function NicknameForm({
next,
submitLabel = '이 이름으로 시작하기',
onDone,
showDiscord = true,
}: {
next: string;
submitLabel?: string;
onDone?: () => void;
/** The login page shows its own big Discord button, so it hides this one. */
showDiscord?: boolean;
}) {
const [nick, setNick] = useState(() => suggestNickname());
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
@@ -49,7 +60,7 @@ export function NicknameForm({ next, submitLabel = '이 이름으로 시작하
<button className="btn btn-primary btn-block" disabled={busy}>
{busy ? '잠시만요…' : submitLabel}
</button>
{discord && (
{discord && showDiscord && (
<a className="btn btn-secondary btn-block" href={discordLoginUrl(next)}>
디스코드로 로그인
</a>

View File

@@ -70,6 +70,7 @@ function Shell() {
<Route path="/g/:gameId" element={<GamePage />} />
<Route path="/rules/:gameId" element={<RulesPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/login/guest" element={<LoginPage />} />
<Route path="/me" element={<MePage />} />
<Route path="/replay/:gamePk" element={<ReplayPage />} />
<Route path="/admin/*" element={<AdminApp />} />

View File

@@ -1,7 +1,11 @@
/** 로그인만 있는 페이지: 닉네임으로 시작 또는 디스코드 로그인. 설정은 로그인 뒤 헤더 계정 메뉴 → 설정(/me). */
import { Navigate, useNavigate, useSearchParams } from 'react-router';
/**
* 로그인 페이지: 큰 [디스코드로 로그인] + 아래 [게스트로 시작하기].
* 게스트를 고르면 /login/guest에서 계정 이름(닉네임)을 정한다. 설정은 로그인 뒤 헤더 계정 메뉴 → 설정(/me).
*/
import { Link, Navigate, useMatch, useNavigate, useSearchParams } from 'react-router';
import { TopBar } from '../components/ui';
import { NicknameForm } from '../components/NicknameForm';
import { discordLoginUrl } from '../net/api';
import { useSession } from '../store/session';
/** Only same-site paths, so ?next= can't send people to another site. */
@@ -10,15 +14,51 @@ const safeNext = (v: string | null) => (v && v.startsWith('/') && !v.startsWith(
export default function LoginPage() {
const nav = useNavigate();
const me = useSession((s) => s.me);
const next = safeNext(useSearchParams()[0].get('next'));
const discord = useSession((s) => s.discordEnabled);
const guest = useMatch('/login/guest') !== null;
const [params] = useSearchParams();
const next = safeNext(params.get('next'));
const query = params.toString() ? `?${params.toString()}` : '';
if (me) return <Navigate to={next} replace />;
if (guest) {
return (
<>
<TopBar title="게스트로 시작하기" onBack={() => nav(`/login${query}`)} />
<main className="page" style={{ maxWidth: 480 }}>
<section className="card stack" style={{ padding: 24 }}>
<h1>계정 이름 정하기</h1>
<p className="muted">게임에서 다른 사람에게 보일 이름이에요.</p>
<NicknameForm next={next} showDiscord={false} onDone={() => nav(next, { replace: true })} />
</section>
</main>
</>
);
}
return (
<>
<TopBar title="로그인" onBack={() => nav('/')} />
<main className="page" style={{ maxWidth: 480 }}>
<section className="card stack" style={{ padding: 24 }}>
<section className="card stack" style={{ padding: 24, gap: 16 }}>
<h1>로그인</h1>
<NicknameForm next={next} onDone={() => nav(next, { replace: true })} />
{discord && (
<>
<a className="btn btn-block btn-discord" href={discordLoginUrl(next)}>
디스코드로 로그인
</a>
<p className="muted small" style={{ marginTop: -6 }}>
기록이 계정에 저장돼서 다른 기기에서도 이어서 할 수 있어요.
</p>
<div className="login-divider" aria-hidden>
또는
</div>
</>
)}
<Link className={`btn btn-block ${discord ? 'btn-secondary' : 'btn-primary'}`} to={`/login/guest${query}`}>
게스트로 시작하기
</Link>
<p className="muted small">가입 없이 이름만 정하면 돼요. 기록은 이 기기에만 남아요.</p>
</section>
</main>
</>

View File

@@ -1185,3 +1185,28 @@ button.header-user {
color: var(--primary);
border-bottom-color: var(--primary);
}
/* Login page (docs/07 §3.2): big Discord button, guest below */
.btn-discord {
min-height: 68px;
font-size: 1.2rem;
background: #5865f2;
border-color: #5865f2;
color: #fff;
}
.btn-discord:hover {
background: #4752c4;
}
.login-divider {
display: flex;
align-items: center;
gap: 12px;
color: var(--text-sub);
font-size: 0.95rem;
}
.login-divider::before,
.login-divider::after {
content: '';
flex: 1;
border-top: 1px solid var(--border);
}