127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import { Suspense, useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { useSearchParams } from 'next/navigation';
|
|
import DicePlane from './diceplane';
|
|
import { DEFAULT_ASSETS, getAssets } from './asset-storage';
|
|
import { useRoom } from '@/lib/rooms/client';
|
|
|
|
function GamePageInner() {
|
|
const searchParams = useSearchParams();
|
|
const roomId = searchParams.get('roomId');
|
|
const [backgroundSrc, setBackgroundSrc] = useState(DEFAULT_ASSETS.background);
|
|
const [ready, setReady] = useState(false);
|
|
const [simAmount, setSimAmount] = useState(1000);
|
|
const { room, error, consumeDice, simulateDonation } = useRoom(roomId);
|
|
const chzzkOn = Boolean(room?.settings.chzzkEnabled);
|
|
const manualMode = Boolean(roomId) && room != null && !chzzkOn;
|
|
|
|
useEffect(() => {
|
|
setBackgroundSrc(getAssets().background);
|
|
setReady(true);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="relative h-screen w-screen overflow-hidden">
|
|
{ready && (
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
<img
|
|
src={backgroundSrc}
|
|
alt=""
|
|
aria-hidden
|
|
draggable={false}
|
|
className="pointer-events-none absolute inset-0 z-0 h-full w-full object-cover"
|
|
/>
|
|
)}
|
|
<div className="absolute inset-0 z-0 bg-black/25" aria-hidden />
|
|
|
|
<div className="absolute top-3 left-3 z-40 flex flex-col items-start gap-2">
|
|
<Link
|
|
href="/"
|
|
className="rounded bg-black/70 px-3 py-1.5 text-sm text-white hover:bg-black/85"
|
|
>
|
|
홈
|
|
</Link>
|
|
{roomId && chzzkOn && (
|
|
<div className="max-w-[min(100vw-2rem,20rem)] rounded-xl bg-black/75 px-3 py-2 text-xs text-white">
|
|
<p className="font-semibold">주사위 큐: {room?.diceQueue ?? '…'}</p>
|
|
{room?.settings.channelId && (
|
|
<p className="mt-1 break-all font-mono text-[10px] text-white/50">
|
|
채널 ID: {room.settings.channelId}
|
|
</p>
|
|
)}
|
|
{room && (
|
|
<p className="mt-1 text-white/70">
|
|
{room.channelName || room.settings.channelId || '—'} · {room.chatStatus}
|
|
{` · ${room.settings.cheesePerDice}원/${
|
|
room.settings.multiplyByCheese
|
|
? `배수${room.settings.maxMultiplier > 0 ? `≤${room.settings.maxMultiplier}` : ''}`
|
|
: '1회'
|
|
}`}
|
|
</p>
|
|
)}
|
|
{error && <p className="mt-1 text-red-300">{error}</p>}
|
|
{room?.chatError && <p className="mt-1 text-red-300">{room.chatError}</p>}
|
|
{room?.lastDonations?.[0] && (
|
|
<p className="mt-1 text-amber-200">
|
|
최근: {room.lastDonations[0].donatorNickname}{' '}
|
|
{room.lastDonations[0].payAmount}원 → +{room.lastDonations[0].diceAdded}
|
|
</p>
|
|
)}
|
|
<div className="mt-2 flex items-center gap-1">
|
|
<input
|
|
type="number"
|
|
min={1}
|
|
value={simAmount}
|
|
onChange={(e) => setSimAmount(Number(e.target.value) || 1)}
|
|
className="w-24 rounded border border-white/20 bg-black/50 px-2 py-1 text-white"
|
|
/>
|
|
<button
|
|
type="button"
|
|
className="rounded bg-white/15 px-2 py-1 hover:bg-white/25"
|
|
onClick={() => void simulateDonation(simAmount).catch(() => undefined)}
|
|
>
|
|
후원 시뮬
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{manualMode && (
|
|
<div className="max-w-[min(100vw-2rem,20rem)] rounded-xl bg-black/75 px-3 py-2 text-xs text-white">
|
|
<p className="font-semibold">수동 모드</p>
|
|
<p className="mt-1 text-white/70">
|
|
치지직·채팅 연동 없음 · 주사위는 자유롭게 굴릴 수 있습니다.
|
|
</p>
|
|
{error && <p className="mt-1 text-red-300">{error}</p>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="relative z-10 h-full w-full">
|
|
<DicePlane
|
|
requireDiceQueue={chzzkOn}
|
|
diceQueue={room?.diceQueue ?? 0}
|
|
onConsumeDice={consumeDice}
|
|
showChatFeed={chzzkOn}
|
|
chatFeed={chzzkOn ? room?.chatFeed ?? [] : []}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function GamePage() {
|
|
return (
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex h-screen items-center justify-center bg-black text-white">
|
|
불러오는 중…
|
|
</div>
|
|
}
|
|
>
|
|
<GamePageInner />
|
|
</Suspense>
|
|
);
|
|
}
|