finished many ui changes + polishing dopamine hits
This commit is contained in:
+403
-25
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useNavigate, useLocation, Navigate } from 'react-router-dom';
|
||||
|
||||
const VALID_GRADES = new Set(['G3', 'G6', 'G9']);
|
||||
@@ -9,21 +9,380 @@ function authHeaders() {
|
||||
return token ? { Authorization: `Bearer ${token}` } : {};
|
||||
}
|
||||
|
||||
function FindingScreen({ elapsed, onBack }) {
|
||||
function getNameFromToken() {
|
||||
try {
|
||||
const token = sessionStorage.getItem('ranked_token');
|
||||
if (!token) return 'You';
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
return payload.name ?? 'You';
|
||||
} catch {
|
||||
return 'You';
|
||||
}
|
||||
}
|
||||
|
||||
function getAvatarFromToken() {
|
||||
try {
|
||||
const token = sessionStorage.getItem('ranked_token');
|
||||
if (!token) return null;
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
return payload.picture ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function MatchFoundScreen({ myName, myAvatar, opponentName, opponentAvatar, grade, onDone }) {
|
||||
const [count, setCount] = useState(5);
|
||||
const [phase, setPhase] = useState('intro'); // intro → reveal → countdown
|
||||
|
||||
useEffect(() => {
|
||||
const t1 = setTimeout(() => setPhase('reveal'), 400);
|
||||
const t2 = setTimeout(() => setPhase('countdown'), 1200);
|
||||
return () => { clearTimeout(t1); clearTimeout(t2); };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (phase !== 'countdown') return;
|
||||
if (count <= 0) { onDone(); return; }
|
||||
const id = setTimeout(() => setCount(c => c - 1), 1000);
|
||||
return () => clearTimeout(id);
|
||||
}, [count, phase, onDone]);
|
||||
|
||||
const countColor = count <= 1 ? '#ff3b30' : count <= 2 ? '#ff9500' : count <= 3 ? '#ffcc00' : '#4fc3f7';
|
||||
const countGlow = count <= 1 ? '#ff3b3088' : count <= 2 ? '#ff950088' : count <= 3 ? '#ffcc0088' : '#4fc3f788';
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh', display: 'flex', flexDirection: 'column',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
background: '#07090f',
|
||||
fontFamily: "'system-ui',sans-serif", overflow: 'hidden', position: 'relative',
|
||||
}}>
|
||||
<style>{`
|
||||
@keyframes bg-scan {
|
||||
0% { transform: translateY(-100%); opacity: 0.07; }
|
||||
100% { transform: translateY(100vh); opacity: 0.03; }
|
||||
}
|
||||
@keyframes header-drop {
|
||||
0% { transform: translateY(-40px) scaleX(0.6); opacity: 0; letter-spacing: 0.3em; }
|
||||
60% { transform: translateY(4px) scaleX(1.02); opacity: 1; letter-spacing: 0.18em; }
|
||||
100% { transform: translateY(0) scaleX(1); opacity: 1; letter-spacing: 0.18em; }
|
||||
}
|
||||
@keyframes player-slide-left {
|
||||
0% { transform: translateX(-120px); opacity: 0; }
|
||||
70% { transform: translateX(6px); opacity: 1; }
|
||||
100% { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes player-slide-right {
|
||||
0% { transform: translateX(120px); opacity: 0; }
|
||||
70% { transform: translateX(-6px); opacity: 1; }
|
||||
100% { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes vs-slam {
|
||||
0% { transform: scale(3) rotate(-8deg); opacity: 0; }
|
||||
55% { transform: scale(0.92) rotate(1deg); opacity: 1; }
|
||||
75% { transform: scale(1.06) rotate(-0.5deg); }
|
||||
100% { transform: scale(1) rotate(0deg); opacity: 1; }
|
||||
}
|
||||
@keyframes divider-grow {
|
||||
0% { width: 0; opacity: 0; }
|
||||
100% { width: 100%; opacity: 1; }
|
||||
}
|
||||
@keyframes count-slam {
|
||||
0% { transform: scale(2.2); opacity: 0; }
|
||||
50% { transform: scale(0.88); opacity: 1; }
|
||||
75% { transform: scale(1.08); }
|
||||
100% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
@keyframes ring-pulse {
|
||||
0% { transform: scale(1); opacity: 0.7; }
|
||||
100% { transform: scale(2.2); opacity: 0; }
|
||||
}
|
||||
@keyframes avatar-glow-blue {
|
||||
0%, 100% { box-shadow: 0 0 18px 4px #1a73e8aa, 0 0 0 3px #1a73e8; }
|
||||
50% { box-shadow: 0 0 32px 10px #1a73e8cc, 0 0 0 3px #1a73e8; }
|
||||
}
|
||||
@keyframes avatar-glow-red {
|
||||
0%, 100% { box-shadow: 0 0 18px 4px #ff3b30aa, 0 0 0 3px #ff3b30; }
|
||||
50% { box-shadow: 0 0 32px 10px #ff3b30cc, 0 0 0 3px #ff3b30; }
|
||||
}
|
||||
@keyframes scan-line {
|
||||
0% { top: 0%; }
|
||||
100% { top: 100%; }
|
||||
}
|
||||
.player-left { animation: player-slide-left 0.55s cubic-bezier(.22,1,.36,1) 0.9s both; }
|
||||
.player-right { animation: player-slide-right 0.55s cubic-bezier(.22,1,.36,1) 0.9s both; }
|
||||
.vs-text { animation: vs-slam 0.5s cubic-bezier(.22,1,.36,1) 1.05s both; }
|
||||
.header-text { animation: header-drop 0.6s cubic-bezier(.22,1,.36,1) 0.1s both; }
|
||||
.count-slam { animation: count-slam 0.3s cubic-bezier(.22,1,.36,1) both; }
|
||||
.ring { animation: ring-pulse 0.7s ease-out both; }
|
||||
`}</style>
|
||||
|
||||
{/* Scanline overlay */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'hidden', zIndex: 0,
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute', left: 0, right: 0, height: 2,
|
||||
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent)',
|
||||
animation: 'scan-line 3s linear infinite',
|
||||
}} />
|
||||
{/* subtle grid */}
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0,
|
||||
backgroundImage: 'linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.025) 1px, transparent 1px)',
|
||||
backgroundSize: '40px 40px',
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="header-text" style={{
|
||||
fontSize: 13, fontWeight: 800, letterSpacing: '0.18em',
|
||||
textTransform: 'uppercase', color: '#4fc3f7',
|
||||
marginBottom: 48, zIndex: 1,
|
||||
textShadow: '0 0 20px #4fc3f7aa',
|
||||
}}>
|
||||
Match Found
|
||||
</div>
|
||||
|
||||
{/* Players row */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 48, zIndex: 1 }}>
|
||||
|
||||
{/* You */}
|
||||
<div className="player-left" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{
|
||||
width: 100, height: 100, borderRadius: '50%',
|
||||
background: myAvatar ? 'transparent' : '#0d2a4a',
|
||||
overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
animation: 'avatar-glow-blue 2s ease-in-out infinite',
|
||||
}}>
|
||||
{myAvatar
|
||||
? <img src={myAvatar} alt={myName} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
: <span style={{ fontSize: 38, color: '#4fc3f7', fontWeight: 800 }}>{(myName?.[0] ?? '?').toUpperCase()}</span>
|
||||
}
|
||||
</div>
|
||||
<span style={{ fontSize: 15, fontWeight: 700, color: '#e8f4fd', maxWidth: 130, textAlign: 'center', wordBreak: 'break-word', letterSpacing: '0.01em' }}>{myName}</span>
|
||||
<span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.14em', textTransform: 'uppercase', color: '#1a73e8' }}>You</span>
|
||||
</div>
|
||||
|
||||
{/* VS + countdown */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 20 }}>
|
||||
<span className="vs-text" style={{
|
||||
fontSize: 40, fontWeight: 900, color: '#fff',
|
||||
letterSpacing: '0.05em',
|
||||
textShadow: '0 0 24px rgba(255,255,255,0.25)',
|
||||
}}>VS</span>
|
||||
|
||||
{phase === 'countdown' && (
|
||||
<div style={{ position: 'relative', width: 80, height: 80, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{/* pulse rings */}
|
||||
<div key={`r1-${count}`} className="ring" style={{
|
||||
position: 'absolute', width: 80, height: 80, borderRadius: '50%',
|
||||
border: `2px solid ${countColor}`,
|
||||
}} />
|
||||
<div key={`r2-${count}`} className="ring" style={{
|
||||
position: 'absolute', width: 80, height: 80, borderRadius: '50%',
|
||||
border: `2px solid ${countColor}`,
|
||||
animationDelay: '0.15s',
|
||||
}} />
|
||||
<div key={count} className="count-slam" style={{
|
||||
width: 80, height: 80, borderRadius: '50%',
|
||||
background: `radial-gradient(circle at 38% 38%, ${countColor}33, #0a0a1a)`,
|
||||
border: `2px solid ${countColor}`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 38, fontWeight: 900, color: countColor,
|
||||
boxShadow: `0 0 28px ${countGlow}, inset 0 0 12px ${countColor}22`,
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
}}>
|
||||
{count}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Opponent */}
|
||||
<div className="player-right" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
|
||||
<div style={{
|
||||
width: 100, height: 100, borderRadius: '50%',
|
||||
background: opponentAvatar ? 'transparent' : '#2a0d0d',
|
||||
overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
animation: 'avatar-glow-red 2s ease-in-out infinite',
|
||||
}}>
|
||||
{opponentAvatar
|
||||
? <img src={opponentAvatar} alt={opponentName} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
||||
: <span style={{ fontSize: 38, color: '#ff6b6b', fontWeight: 800 }}>{(opponentName?.[0] ?? '?').toUpperCase()}</span>
|
||||
}
|
||||
</div>
|
||||
<span style={{ fontSize: 15, fontWeight: 700, color: '#ffe8e8', maxWidth: 130, textAlign: 'center', wordBreak: 'break-word', letterSpacing: '0.01em' }}>{opponentName}</span>
|
||||
<span style={{ fontSize: 10, fontWeight: 700, letterSpacing: '0.14em', textTransform: 'uppercase', color: '#ff3b30' }}>Opponent</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider line */}
|
||||
<div style={{
|
||||
marginTop: 52, height: 1, zIndex: 1,
|
||||
background: 'linear-gradient(90deg, transparent, rgba(255,255,255,0.08), transparent)',
|
||||
animation: 'divider-grow 0.6s ease-out 1.4s both',
|
||||
width: 340,
|
||||
}} />
|
||||
|
||||
{/* Grade tag */}
|
||||
<div style={{
|
||||
marginTop: 20, fontSize: 11, fontWeight: 700, letterSpacing: '0.15em',
|
||||
textTransform: 'uppercase', color: 'rgba(255,255,255,0.2)', zIndex: 1,
|
||||
animation: 'header-drop 0.5s ease-out 1.5s both',
|
||||
}}>
|
||||
Ranked · Grade {grade?.replace('G', '') ?? '?'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const QUEUE_DIAMONDS = [
|
||||
{ left: '8%', top: '15%', size: 10, dur: '12s', delay: '0s' },
|
||||
{ left: '22%', top: '68%', size: 7, dur: '15s', delay: '2s' },
|
||||
{ left: '52%', top: '20%', size: 13, dur: '11s', delay: '1s' },
|
||||
{ left: '68%', top: '72%', size: 9, dur: '14s', delay: '3s' },
|
||||
{ left: '84%', top: '35%', size: 11, dur: '13s', delay: '0.5s' },
|
||||
{ left: '40%', top: '82%', size: 8, dur: '10s', delay: '4s' },
|
||||
];
|
||||
|
||||
function FindingScreen({ elapsed, grade, onBack }) {
|
||||
const [dots, setDots] = useState('');
|
||||
useEffect(() => { const t = setInterval(() => setDots(d => d.length >= 3 ? '' : d + '.'), 400); return () => clearInterval(t); }, []);
|
||||
const [playersOnline, setPlayersOnline] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const t = setInterval(() => setDots(d => d.length >= 3 ? '' : d + '.'), 400);
|
||||
return () => clearInterval(t);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCount = () => {
|
||||
fetch(`${API}/online-count`)
|
||||
.then(r => r.json())
|
||||
.then(d => setPlayersOnline(d.online ?? 0))
|
||||
.catch(() => {});
|
||||
};
|
||||
fetchCount();
|
||||
const t = setInterval(fetchCount, 30000);
|
||||
return () => clearInterval(t);
|
||||
}, []);
|
||||
|
||||
const minutes = Math.floor(elapsed / 60);
|
||||
const seconds = elapsed % 60;
|
||||
const gradeNum = grade?.replace('G', '') ?? '?';
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: '#fff', fontFamily: 'system-ui,sans-serif', gap: 24 }}>
|
||||
<div style={{ width: 64, height: 64, borderRadius: '50%', border: '4px solid #e0e0e0', borderTopColor: '#1a73e8', animation: 'spin 0.8s linear infinite' }} />
|
||||
<h2 style={{ fontSize: 22, fontWeight: 600, color: '#333', margin: 0 }}>Searching for opponent{dots}</h2>
|
||||
<p style={{ fontSize: 15, color: '#888', margin: 0 }}>Searching all players...</p>
|
||||
<div style={{ fontSize: 14, color: '#666', fontFamily: 'system-ui,sans-serif', fontWeight: 500, letterSpacing: '0.02em' }}>
|
||||
Time elapsed: {minutes}:{String(seconds).padStart(2, '0')}
|
||||
<div style={{ minHeight: '100vh', position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', fontFamily: 'system-ui,sans-serif', overflow: 'hidden' }}>
|
||||
<style>{`
|
||||
@keyframes spinCW { to { transform: rotate(360deg); } }
|
||||
@keyframes spinCCW { to { transform: rotate(-360deg); } }
|
||||
`}</style>
|
||||
|
||||
{/* Arena background */}
|
||||
<div className="arena-bg">
|
||||
<div className="arena-grain" />
|
||||
<div className="arena-shapes">
|
||||
{QUEUE_DIAMONDS.map((d, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="shape shape--diamond"
|
||||
style={{
|
||||
left: d.left, top: d.top,
|
||||
width: d.size, height: d.size,
|
||||
animationDuration: d.dur, animationDelay: d.delay,
|
||||
opacity: 0.35,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ position: 'relative', zIndex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 24 }}>
|
||||
|
||||
{/* Two counter-rotating rings */}
|
||||
<div style={{ position: 'relative', width: 80, height: 80 }}>
|
||||
<div style={{
|
||||
position: 'absolute', inset: 0, borderRadius: '50%',
|
||||
border: '3.5px solid rgba(245,197,24,0.12)',
|
||||
borderTopColor: '#f5c518', borderRightColor: 'rgba(245,197,24,0.45)',
|
||||
animation: 'spinCW 1.2s linear infinite',
|
||||
}} />
|
||||
<div style={{
|
||||
position: 'absolute', inset: 13, borderRadius: '50%',
|
||||
border: '3px solid rgba(79,195,247,0.12)',
|
||||
borderTopColor: '#4fc3f7', borderLeftColor: 'rgba(79,195,247,0.45)',
|
||||
animation: 'spinCCW 0.85s linear infinite',
|
||||
}} />
|
||||
<div style={{
|
||||
position: 'absolute', top: '50%', left: '50%',
|
||||
transform: 'translate(-50%,-50%)',
|
||||
width: 7, height: 7, borderRadius: '50%',
|
||||
background: '#f5c518',
|
||||
boxShadow: '0 0 10px rgba(245,197,24,0.9)',
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* Grade label */}
|
||||
<p style={{
|
||||
fontSize: 11, fontWeight: 700, letterSpacing: '0.22em',
|
||||
textTransform: 'uppercase', color: 'rgba(245,197,24,0.45)',
|
||||
margin: 0,
|
||||
}}>
|
||||
Grade {gradeNum} · Ranked
|
||||
</p>
|
||||
|
||||
{/* Heading */}
|
||||
<h2 style={{ fontSize: 22, fontWeight: 700, color: '#eceef8', margin: 0, letterSpacing: '0.01em' }}>
|
||||
Searching for opponent{dots}
|
||||
</h2>
|
||||
|
||||
{/* Players online */}
|
||||
<p style={{
|
||||
fontSize: 13, color: 'rgba(245,197,24,0.55)',
|
||||
margin: 0, letterSpacing: '0.04em',
|
||||
}}>
|
||||
{playersOnline !== null ? `${playersOnline.toLocaleString()} ${playersOnline === 1 ? 'player' : 'players'} online` : 'finding players...'}
|
||||
</p>
|
||||
|
||||
{/* Elapsed timer — large gold */}
|
||||
<div style={{
|
||||
fontSize: 48, fontWeight: 700,
|
||||
color: '#f5c518', fontFamily: 'monospace',
|
||||
letterSpacing: '0.06em', lineHeight: 1,
|
||||
textShadow: '0 0 28px rgba(245,197,24,0.35)',
|
||||
}}>
|
||||
{String(minutes).padStart(2, '0')}:{String(seconds).padStart(2, '0')}
|
||||
</div>
|
||||
|
||||
{/* Back button */}
|
||||
<button
|
||||
onClick={onBack}
|
||||
style={{
|
||||
marginTop: 8, padding: '10px 32px',
|
||||
fontSize: 14, fontWeight: 600,
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
color: 'rgba(255,255,255,0.45)',
|
||||
border: '1px solid rgba(255,255,255,0.12)',
|
||||
borderRadius: 999, cursor: 'pointer',
|
||||
fontFamily: 'system-ui,sans-serif',
|
||||
transition: 'background 0.15s, border-color 0.15s, color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.background = 'rgba(255,255,255,0.09)';
|
||||
e.currentTarget.style.borderColor = 'rgba(255,255,255,0.25)';
|
||||
e.currentTarget.style.color = 'rgba(255,255,255,0.75)';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.background = 'rgba(255,255,255,0.05)';
|
||||
e.currentTarget.style.borderColor = 'rgba(255,255,255,0.12)';
|
||||
e.currentTarget.style.color = 'rgba(255,255,255,0.45)';
|
||||
}}
|
||||
>
|
||||
‹ Back to Dashboard
|
||||
</button>
|
||||
</div>
|
||||
<button onClick={onBack} style={{ marginTop: 8, padding: '10px 32px', fontSize: 15, fontWeight: 600, background: '#f5f5f5', color: '#555', border: '1px solid #ccc', borderRadius: 999, cursor: 'pointer', fontFamily: 'system-ui,sans-serif' }}>‹ Back to Dashboard</button>
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,9 +392,10 @@ export default function Queue() {
|
||||
const { state } = useLocation();
|
||||
const grade = state?.grade ?? null;
|
||||
|
||||
const [myName, setMyName] = useState('');
|
||||
const [myAvatar, setMyAvatar] = useState(null);
|
||||
const [myName, setMyName] = useState(() => getNameFromToken());
|
||||
const [myAvatar, setMyAvatar] = useState(() => getAvatarFromToken());
|
||||
const [queueElapsed, setQueueElapsed] = useState(0);
|
||||
const [matchData, setMatchData] = useState(null);
|
||||
|
||||
const pollRef = useRef(null);
|
||||
const joinedRef = useRef(false);
|
||||
@@ -43,11 +403,12 @@ export default function Queue() {
|
||||
const leaveTimerRef = useRef(null);
|
||||
const leavingRef = useRef(false);
|
||||
|
||||
// Elapsed timer
|
||||
// Elapsed timer — stop once matched
|
||||
useEffect(() => {
|
||||
if (matchData) return;
|
||||
const id = setInterval(() => setQueueElapsed(e => e + 1), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
}, [matchData]);
|
||||
|
||||
// Fetch own user info
|
||||
useEffect(() => {
|
||||
@@ -115,7 +476,7 @@ export default function Queue() {
|
||||
if (!data || leavingRef.current) return;
|
||||
if (data.status === 'matched') {
|
||||
clearInterval(pollRef.current);
|
||||
navigateToGame(data);
|
||||
showMatchup(data);
|
||||
} else if (data.status === 'not_in_queue') {
|
||||
clearInterval(pollRef.current);
|
||||
fetch(`${API}/queue/join`, {
|
||||
@@ -130,7 +491,7 @@ export default function Queue() {
|
||||
.then(d => {
|
||||
if (!d || leavingRef.current) return;
|
||||
if (d.status === 'waiting') pollQueue();
|
||||
else if (d.status === 'matched') navigateToGame(d);
|
||||
else if (d.status === 'matched') showMatchup(d);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
@@ -144,29 +505,46 @@ export default function Queue() {
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!leavingRef.current && data.status === 'matched') {
|
||||
navigateToGame(data);
|
||||
showMatchup(data);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function navigateToGame(data) {
|
||||
function showMatchup(data) {
|
||||
clearInterval(pollRef.current);
|
||||
completedRef.current = true;
|
||||
setMatchData(data);
|
||||
}
|
||||
|
||||
const goToGame = useCallback(() => {
|
||||
navigate('/ranked-game', {
|
||||
state: {
|
||||
gameId: data.gameId,
|
||||
questions: data.questions,
|
||||
opponentName: data.opponent.username ?? data.opponent.displayName,
|
||||
opponentAvatar: data.opponent.avatarUrl,
|
||||
gameId: matchData.gameId,
|
||||
questions: matchData.questions,
|
||||
opponentName: matchData.opponent.username ?? matchData.opponent.displayName,
|
||||
opponentAvatar: matchData.opponent.avatarUrl,
|
||||
myName,
|
||||
myAvatar,
|
||||
grade,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [matchData, myName, myAvatar, grade, navigate]);
|
||||
|
||||
if (!VALID_GRADES.has(grade)) return <Navigate to="/dashboard" replace />;
|
||||
|
||||
return <FindingScreen elapsed={queueElapsed} onBack={() => navigate('/dashboard')} />;
|
||||
if (matchData) {
|
||||
return (
|
||||
<MatchFoundScreen
|
||||
myName={myName}
|
||||
myAvatar={myAvatar}
|
||||
opponentName={matchData.opponent.username ?? matchData.opponent.displayName}
|
||||
opponentAvatar={matchData.opponent.avatarUrl}
|
||||
grade={grade}
|
||||
onDone={goToGame}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <FindingScreen elapsed={queueElapsed} grade={grade} onBack={() => navigate('/dashboard')} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user