Match intro "players joining" loading screen + i18n fix; checkpoint
- MatchIntroOverlay: UNO-style pre-game reveal — the 4 seats animate into the table (with "?" placeholders until each player's data streams in for live matches), a 3-2-1-GO countdown, then the table shows. Wired via game-store matchIntroPending/consumeIntro, rendered online-only in GameScreen. - Fix: intro.found / intro.getReady / intro.go existed only in the Persian dict; added the English strings (would have shown raw keys to EN users). - Checkpoint of the in-progress UI/social batch (CoinsPill, shop titles section, friend-request rate limit, etc.) — all green. Verified: tsc + next build + scripts/sim.ts + dotnet build server/Hokm.slnx all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { GameTable } from "@/components/GameTable";
|
||||
import { PostMatchRewardsModal } from "@/components/online/PostMatchRewardsModal";
|
||||
import { MatchIntroOverlay } from "@/components/online/MatchIntroOverlay";
|
||||
import { useGameStore } from "@/lib/game-store";
|
||||
import { useSessionStore } from "@/lib/session-store";
|
||||
import { useUIStore } from "@/lib/ui-store";
|
||||
@@ -24,6 +25,8 @@ export function GameScreen() {
|
||||
const forfeited = useGameStore((s) => s.forfeited);
|
||||
const forfeitRequest = useGameStore((s) => s.forfeitRequest);
|
||||
const respondForfeit = useGameStore((s) => s.respondForfeit);
|
||||
const introPending = useGameStore((s) => s.matchIntroPending);
|
||||
const consumeIntro = useGameStore((s) => s.consumeIntro);
|
||||
const returnTo = useUIStore((s) => s.returnTo);
|
||||
const go = useUIStore((s) => s.go);
|
||||
const refreshProfile = useSessionStore((s) => s.refreshProfile);
|
||||
@@ -139,6 +142,13 @@ export function GameScreen() {
|
||||
onForfeit={canForfeit ? () => useGameStore.getState().forfeit() : undefined}
|
||||
/>
|
||||
|
||||
{/* UNO-style "players joining the table" intro (online matches, once) */}
|
||||
<AnimatePresence>
|
||||
{introPending && mode === "online" && (
|
||||
<MatchIntroOverlay onDone={consumeIntro} />
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* teammate asked to forfeit — confirm or decline */}
|
||||
{forfeitRequest && (
|
||||
<motion.div
|
||||
|
||||
@@ -4,6 +4,7 @@ import { motion } from "framer-motion";
|
||||
import { Coins, Lock, Trophy, Users } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
|
||||
import { CoinsPill } from "@/components/online/CoinsPill";
|
||||
import { MATCH_LEAGUES, leagueById } from "@/lib/online/gamification";
|
||||
import { useOnlineStore } from "@/lib/online-store";
|
||||
import { useSessionStore } from "@/lib/session-store";
|
||||
@@ -43,15 +44,7 @@ export function OnlineLobbyScreen() {
|
||||
|
||||
return (
|
||||
<ScreenShell>
|
||||
<ScreenHeader
|
||||
title={t("lobby.title")}
|
||||
right={
|
||||
<span className="glass rounded-full px-3 py-1.5 text-xs font-bold text-gold-300 flex items-center gap-1">
|
||||
<Coins className="size-3.5 text-gold-400" />
|
||||
{coins.toLocaleString()}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<ScreenHeader title={t("lobby.title")} right={<CoinsPill />} />
|
||||
|
||||
{/* league pick (only for ranked) */}
|
||||
<div className="glass rounded-2xl p-4 mb-4">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { Check, ChevronLeft, Coins, Crown, Eye, EyeOff, Lock, Music, Pencil, Star, Upload, Users, Volume2 } from "lucide-react";
|
||||
import { Check, ChevronLeft, Crown, Eye, EyeOff, Lock, Music, Pencil, Star, Upload, Users, Volume2 } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
|
||||
import { RankBadge } from "@/components/online/RankBadge";
|
||||
import { CoinsPill } from "@/components/online/CoinsPill";
|
||||
import { XpBar } from "@/components/online/XpBar";
|
||||
import { Avatar } from "@/components/online/Avatar";
|
||||
import { useSessionStore } from "@/lib/session-store";
|
||||
@@ -138,10 +139,7 @@ export function ProfileScreen() {
|
||||
|
||||
<div className="mt-2 flex items-center justify-center gap-2">
|
||||
<RankBadge rating={profile.rating} showRating />
|
||||
<span className="glass rounded-full px-2.5 py-1 text-xs font-bold text-gold-300 flex items-center gap-1">
|
||||
<Coins className="size-3.5 text-gold-400" />
|
||||
{profile.coins.toLocaleString()}
|
||||
</span>
|
||||
<CoinsPill />
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Check, Coins, Sparkles, X } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ScreenHeader, ScreenShell } from "@/components/online/ScreenHeader";
|
||||
import { Sticker } from "@/components/online/Sticker";
|
||||
import { CoinsPill } from "@/components/online/CoinsPill";
|
||||
import { useSessionStore } from "@/lib/session-store";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
import { getService } from "@/lib/online/service";
|
||||
@@ -146,15 +147,7 @@ export function ShopScreen() {
|
||||
|
||||
return (
|
||||
<ScreenShell>
|
||||
<ScreenHeader
|
||||
title={t("shop.title")}
|
||||
right={
|
||||
<span className="glass rounded-full px-3 py-1.5 text-xs font-bold text-gold-300 flex items-center gap-1">
|
||||
<Coins className="size-3.5 text-gold-400" />
|
||||
{profile.coins.toLocaleString()}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<ScreenHeader title={t("shop.title")} right={<CoinsPill />} />
|
||||
|
||||
{msg && (
|
||||
<div className="mb-3 text-center text-rose-300 text-sm glass rounded-xl py-2">{msg}</div>
|
||||
|
||||
Reference in New Issue
Block a user