치지직 앱으로 연동하고, 연동 확인된 ID만 접속할 수 있도록 수정, 개발용으로는 채널ID를 수동으로 접속할 수 있도록 변경.
최초 추천 고정 배치 추가.
This commit is contained in:
183
app/page.tsx
183
app/page.tsx
@@ -38,7 +38,7 @@ import {
|
||||
clearBoardConfig,
|
||||
getOrCreateBoardConfig,
|
||||
perimeterLength,
|
||||
randomizeAndSaveBoard,
|
||||
resetToDefaultBoard,
|
||||
resizeBoardConfig,
|
||||
setAllCellsContentVisible,
|
||||
setBoardConfig,
|
||||
@@ -169,9 +169,9 @@ function BoardEditor({
|
||||
<button
|
||||
type="button"
|
||||
className="rounded bg-violet-700 px-3 py-1.5 text-xs font-medium text-white hover:bg-violet-600"
|
||||
onClick={() => onChange(randomizeAndSaveBoard(board.width, board.height, board))}
|
||||
onClick={() => onChange(resetToDefaultBoard())}
|
||||
>
|
||||
랜덤 다시 배치
|
||||
추천 배치로 되돌리기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -379,15 +379,57 @@ export default function Home() {
|
||||
const [joinChannelInput, setJoinChannelInput] = useState('');
|
||||
const [roomBusy, setRoomBusy] = useState(false);
|
||||
const [roomError, setRoomError] = useState<string | null>(null);
|
||||
const [authMode, setAuthMode] = useState<{
|
||||
allowChannelIdJoin: boolean;
|
||||
requireChzzkLogin: boolean;
|
||||
oauthConfigured: boolean;
|
||||
} | null>(null);
|
||||
const [authSession, setAuthSession] = useState<{
|
||||
channelId: string;
|
||||
channelName: string | null;
|
||||
} | null>(null);
|
||||
|
||||
const reload = () => {
|
||||
setAssets(getAssets());
|
||||
setBoard(getOrCreateBoardConfig());
|
||||
};
|
||||
|
||||
const refreshAuth = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/auth/session', { cache: 'no-store' });
|
||||
const json = await res.json();
|
||||
setAuthMode({
|
||||
allowChannelIdJoin: Boolean(json.allowChannelIdJoin),
|
||||
requireChzzkLogin: Boolean(json.requireChzzkLogin),
|
||||
oauthConfigured: Boolean(json.oauthConfigured),
|
||||
});
|
||||
setAuthSession(
|
||||
json.session
|
||||
? {
|
||||
channelId: String(json.session.channelId),
|
||||
channelName: json.session.channelName ?? null,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
} catch {
|
||||
setAuthMode({
|
||||
allowChannelIdJoin: true,
|
||||
requireChzzkLogin: false,
|
||||
oauthConfigured: false,
|
||||
});
|
||||
setAuthSession(null);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reload();
|
||||
setReady(true);
|
||||
void refreshAuth();
|
||||
if (typeof window !== 'undefined') {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const authError = params.get('authError');
|
||||
if (authError) setRoomError(authError);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const roomCheeseOptions = () => ({
|
||||
@@ -396,6 +438,45 @@ export default function Home() {
|
||||
maxMultiplier: roomMultiplyByCheese ? roomMaxMultiplier : 0,
|
||||
});
|
||||
|
||||
const startChzzkLogin = () => {
|
||||
const opts = roomCheeseOptions();
|
||||
const q = new URLSearchParams({
|
||||
cheesePerDice: String(opts.cheesePerDice),
|
||||
multiplyByCheese: opts.multiplyByCheese ? '1' : '0',
|
||||
maxMultiplier: String(opts.maxMultiplier),
|
||||
});
|
||||
window.location.href = `/api/chzzk/login?${q.toString()}`;
|
||||
};
|
||||
|
||||
const joinWithSession = async () => {
|
||||
try {
|
||||
setRoomBusy(true);
|
||||
setRoomError(null);
|
||||
const res = await fetch('/api/rooms/join', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...roomCheeseOptions(),
|
||||
}),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!res.ok) {
|
||||
setRoomError(json.error || '룸 입장에 실패했습니다.');
|
||||
return;
|
||||
}
|
||||
router.push(`/game?roomId=${json.room.id}`);
|
||||
} catch (e) {
|
||||
setRoomError(e instanceof Error ? e.message : '룸 입장에 실패했습니다.');
|
||||
} finally {
|
||||
setRoomBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const logoutAuth = async () => {
|
||||
await fetch('/api/auth/logout', { method: 'POST' });
|
||||
setAuthSession(null);
|
||||
};
|
||||
|
||||
const joinByChzzkId = async () => {
|
||||
const value = joinChannelInput.trim();
|
||||
if (!value) {
|
||||
@@ -510,10 +591,10 @@ export default function Home() {
|
||||
<div className="mx-auto flex min-h-screen max-w-3xl flex-col gap-8 px-6 py-10">
|
||||
<header className="flex w-full flex-col items-stretch gap-5">
|
||||
<div className="w-full">
|
||||
{/*<p className="text-sm text-white/60">온라인 주루마블</p>*/}
|
||||
{/*<p className="text-sm text-white/60">알코올 마블</p>*/}
|
||||
<h1 className="relative mt-1 inline-block w-full text-center text-4xl font-bold tracking-tight">
|
||||
<span className="relative inline-block pr-12">
|
||||
온라인 주루마블!
|
||||
알코올 마블!
|
||||
<span className="pointer-events-none absolute bottom-1 left-full -ml-0.5 whitespace-nowrap text-[11px] font-normal tracking-normal text-white/35">
|
||||
by kimyu.
|
||||
</span>
|
||||
@@ -521,9 +602,10 @@ export default function Home() {
|
||||
</h1>
|
||||
<br></br>
|
||||
<p className="border-b border-white/10 w-full"></p>
|
||||
<p className="mt-2 w-full text-sm text-white/65">치지직 온라인 주루마블!</p>
|
||||
<p className="mt-2 w-full text-sm text-white/65">
|
||||
이미지와 판 칸 설정은 서버가 아니라 이 브라우저에만 저장됩니다.
|
||||
처음이면 칸은 랜덤으로 채워집니다.
|
||||
이미지와 판 칸 설정은 서버가 아니라 이 브라우저에만 저장되요.
|
||||
처음이면 추천 기본 배치(9×7)로 채워져요.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
@@ -532,7 +614,9 @@ export default function Home() {
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">룸 입장</h2>
|
||||
<p className="mt-1 text-[11px] text-white/50">
|
||||
치즈 규칙을 정한 뒤, 치지직 채널로 입장하세요. 룸이 없으면 자동으로 만들어집니다.
|
||||
{authMode?.requireChzzkLogin
|
||||
? '릴리즈 모드: 치지직 로그인으로 본인 채널만 입장할 수 있습니다.'
|
||||
: '개발 모드: 채널 ID 직접 입장과 치지직 로그인을 모두 사용할 수 있습니다.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -576,27 +660,72 @@ export default function Home() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-end gap-2 border-t border-white/10 pt-3">
|
||||
<label className="flex min-w-[220px] flex-1 flex-col gap-1 text-sm">
|
||||
<span className="text-white/70">치지직 채널 ID로 입장</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="채널ID 또는 https://chzzk.naver.com/live/…"
|
||||
value={joinChannelInput}
|
||||
onChange={(e) => setJoinChannelInput(e.target.value)}
|
||||
className="rounded-lg border border-white/15 bg-black/40 px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy || !joinChannelInput.trim()}
|
||||
onClick={() => void joinByChzzkId()}
|
||||
className="rounded-full bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-violet-500 disabled:opacity-50"
|
||||
>
|
||||
{roomBusy ? '처리 중…' : '입장'}
|
||||
</button>
|
||||
<div className="flex flex-col gap-2 border-t border-white/10 pt-3">
|
||||
<p className="text-xs font-medium text-white/70">치지직 로그인으로 입장</p>
|
||||
{authSession ? (
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p className="text-sm text-emerald-200/90">
|
||||
로그인됨: {authSession.channelName || authSession.channelId}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy}
|
||||
onClick={() => void joinWithSession()}
|
||||
className="rounded-full bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-500 disabled:opacity-50"
|
||||
>
|
||||
내 채널로 입장
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void logoutAuth()}
|
||||
className="rounded-full bg-white/10 px-3 py-2 text-xs text-white/80 hover:bg-white/20"
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy || authMode?.oauthConfigured === false}
|
||||
onClick={startChzzkLogin}
|
||||
className="self-start rounded-full bg-sky-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-sky-500 disabled:opacity-50"
|
||||
>
|
||||
치지직으로 로그인 후 입장
|
||||
</button>
|
||||
)}
|
||||
{authMode && !authMode.oauthConfigured && (
|
||||
<p className="text-[11px] text-amber-200/80">
|
||||
OAuth 미설정: `.env`에 CHZZK_CLIENT_ID / SECRET / REDIRECT_URI 를 넣으면 활성화됩니다.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{authMode?.allowChannelIdJoin !== false && (
|
||||
<div className="flex flex-wrap items-end gap-2 border-t border-white/10 pt-3">
|
||||
<label className="flex min-w-[220px] flex-1 flex-col gap-1 text-sm">
|
||||
<span className="text-white/70">
|
||||
치지직 채널 ID로 입장
|
||||
<span className="ml-1 text-[10px] text-amber-200/70">(개발용)</span>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="채널ID 또는 https://chzzk.naver.com/live/…"
|
||||
value={joinChannelInput}
|
||||
onChange={(e) => setJoinChannelInput(e.target.value)}
|
||||
className="rounded-lg border border-white/15 bg-black/40 px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy || !joinChannelInput.trim()}
|
||||
onClick={() => void joinByChzzkId()}
|
||||
className="rounded-full bg-violet-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-violet-500 disabled:opacity-50"
|
||||
>
|
||||
{roomBusy ? '처리 중…' : '입장'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={roomBusy}
|
||||
|
||||
Reference in New Issue
Block a user