diff --git a/src/app/blindbox/page.tsx b/src/app/blindbox/page.tsx index 6968e05..b0971f7 100644 --- a/src/app/blindbox/page.tsx +++ b/src/app/blindbox/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { useRouter } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; import { @@ -38,9 +38,10 @@ export default function BlindboxLobbyPage() { const [rooms, setRooms] = useState([]); const [loading, setLoading] = useState(true); - const [createName, setCreateName] = useState(""); + const createNameRef = useRef(null); + const joinCodeRef = useRef(null); + const [joinCodeLength, setJoinCodeLength] = useState(0); const [creating, setCreating] = useState(false); - const [joinCode, setJoinCode] = useState(""); const [joining, setJoining] = useState(false); const [error, setError] = useState(""); const [loadError, setLoadError] = useState(false); @@ -64,20 +65,23 @@ export default function BlindboxLobbyPage() { return () => window.removeEventListener("nowhatever_auth", handler); }, []); - const fetchRooms = useCallback(async () => { + const fetchRooms = useCallback(async (silent = false) => { const p = getCachedProfile(); if (!p) return; - setLoading(true); - setLoadError(false); + if (!silent) { + setLoading(true); + setLoadError(false); + } try { const res = await fetch(`/api/blindbox/rooms?userId=${p.id}`); if (!res.ok) throw new Error(); const data = await res.json(); setRooms(Array.isArray(data.rooms) ? data.rooms : []); + setLoadError(false); } catch { - setLoadError(true); + if (!silent) setLoadError(true); } finally { - setLoading(false); + if (!silent) setLoading(false); } }, []); @@ -86,6 +90,17 @@ export default function BlindboxLobbyPage() { else setLoading(false); }, [loggedIn, fetchRooms]); + useEffect(() => { + if (!loggedIn) return; + const onFocus = () => fetchRooms(true); + window.addEventListener("focus", onFocus); + return () => window.removeEventListener("focus", onFocus); + }, [loggedIn, fetchRooms]); + + useEffect(() => { + if (rooms.length > 0) setJoinCodeLength(0); + }, [rooms.length]); + const handleAuth = (p: UserProfile) => { setProfile(p); setLoggedIn(true); @@ -94,13 +109,14 @@ export default function BlindboxLobbyPage() { const handleCreate = async () => { if (creating || !profile) return; + const name = createNameRef.current?.value?.trim() ?? ""; setCreating(true); setError(""); try { const res = await fetch("/api/blindbox/room", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ userId: profile.id, name: createName.trim() || undefined }), + body: JSON.stringify({ userId: profile.id, name: name || undefined }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error); @@ -113,14 +129,15 @@ export default function BlindboxLobbyPage() { }; const handleJoin = async () => { - if (joining || !profile || !joinCode.trim()) return; + const code = joinCodeRef.current?.value?.toUpperCase().trim().slice(0, 6) ?? ""; + if (joining || !profile || code.length < 6) return; setJoining(true); setError(""); try { const res = await fetch("/api/blindbox/room/join", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ userId: profile.id, code: joinCode.trim() }), + body: JSON.stringify({ userId: profile.id, code }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error); @@ -282,17 +299,15 @@ export default function BlindboxLobbyPage() { 创建第一个房间,邀请 TA 一起玩

- {/* Inline create form */} -
+ {/* Inline create form — z-10 + isolation so inputs are above any overlay and receive touch */} +
{ - setCreateName(e.target.value.slice(0, 30)); - setError(""); - }} + defaultValue="" + onInput={() => setError("")} onKeyDown={(e) => { if (e.key === "Enter") handleCreate(); }} maxLength={30} size="xl" @@ -319,11 +334,14 @@ export default function BlindboxLobbyPage() {
{ - setJoinCode(e.target.value.toUpperCase().slice(0, 6)); + defaultValue="" + onInput={(e) => { + const v = (e.target as HTMLInputElement).value.toUpperCase().slice(0, 6); + (e.target as HTMLInputElement).value = v; + setJoinCodeLength(v.length); setError(""); }} onKeyDown={(e) => { if (e.key === "Enter") handleJoin(); }} @@ -336,7 +354,7 @@ export default function BlindboxLobbyPage() { onClick={handleJoin} variant="secondary" size="lg" - disabled={joinCode.trim().length < 6} + disabled={joinCodeLength < 6} loading={joining} icon={} > @@ -364,16 +382,14 @@ export default function BlindboxLobbyPage() { animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} > - {/* Create row */} +
{ - setCreateName(e.target.value.slice(0, 30)); - setError(""); - }} + defaultValue="" + onInput={() => setError("")} onKeyDown={(e) => { if (e.key === "Enter") handleCreate(); }} maxLength={30} size="lg" @@ -393,11 +409,14 @@ export default function BlindboxLobbyPage() { {/* Join row */}
{ - setJoinCode(e.target.value.toUpperCase().slice(0, 6)); + defaultValue="" + onInput={(e) => { + const v = (e.target as HTMLInputElement).value.toUpperCase().slice(0, 6); + (e.target as HTMLInputElement).value = v; + setJoinCodeLength(v.length); setError(""); }} onKeyDown={(e) => { if (e.key === "Enter") handleJoin(); }} @@ -409,7 +428,7 @@ export default function BlindboxLobbyPage() {
{/* Room list */}
diff --git a/src/app/globals.css b/src/app/globals.css index 3bd166d..28b75f5 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -69,6 +69,15 @@ body { -webkit-tap-highlight-color: transparent; } +/* Allow inputs and textareas to receive focus and accept input (body has user-select: none) */ +input, +textarea, +[contenteditable="true"] { + -webkit-user-select: text !important; + user-select: text !important; + touch-action: manipulation; /* prevent scroll container from capturing touch on iOS */ +} + .scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 585cbb5..633679c 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -21,7 +21,8 @@ const Input = forwardRef( ({ size = "md", variant = "default", className = "", ...rest }, ref) => ( ),