Turn timer + auto-play, disconnect/reconnect, cosmetics, queue & paid plan
- Turn timer (20s) for play/trump; system auto-plays a smart move on timeout - Disconnect handling (mock): wait-for-return countdown, system covers turns - Cosmetics: titles, card-back styles, custom profile-image upload, badges; pickers in Profile; shop sells card styles; reward modal shows new titles - Paid plan (pro): free players queue when server busy, pro skips; upgrade flow - OnlineService extended (upgradePlan, richer profile patch); mock implements queue + plans; gamification adds TITLES + CARD_STYLES Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Crown, Loader2 } from "lucide-react";
|
||||
import { ScreenShell } from "@/components/online/ScreenHeader";
|
||||
import { useGameStore } from "@/lib/game-store";
|
||||
import { useOnlineStore } from "@/lib/online-store";
|
||||
import { useSessionStore } from "@/lib/session-store";
|
||||
import { useUIStore } from "@/lib/ui-store";
|
||||
import { useI18n } from "@/lib/i18n";
|
||||
import { getService } from "@/lib/online/service";
|
||||
@@ -15,10 +16,12 @@ export function MatchmakingScreen() {
|
||||
const mm = useOnlineStore((s) => s.matchmaking);
|
||||
const cancelMatchmaking = useOnlineStore((s) => s.cancelMatchmaking);
|
||||
const newOnlineMatch = useGameStore((s) => s.newOnlineMatch);
|
||||
const upgradePlan = useSessionStore((s) => s.upgradePlan);
|
||||
const goGame = useUIStore((s) => s.goGame);
|
||||
const go = useUIStore((s) => s.go);
|
||||
|
||||
const ready = mm.phase === "ready";
|
||||
const queued = mm.phase === "queued";
|
||||
const slots = [0, 1, 2, 3];
|
||||
|
||||
const cancel = async () => {
|
||||
@@ -42,6 +45,39 @@ export function MatchmakingScreen() {
|
||||
goGame("home");
|
||||
};
|
||||
|
||||
if (queued) {
|
||||
return (
|
||||
<ScreenShell>
|
||||
<div className="flex flex-col items-center justify-center min-h-[80dvh] text-center">
|
||||
<div className="text-5xl mb-4">⏳</div>
|
||||
<h1 className="gold-text text-2xl font-black">{t("queue.title")}</h1>
|
||||
<p className="text-cream/60 text-sm mt-1">{t("queue.busy")}</p>
|
||||
<div className="my-6 text-7xl font-black gold-text tabular-nums">
|
||||
{mm.queuePosition ?? 0}
|
||||
</div>
|
||||
<p className="text-cream/70">{t("queue.position", { n: mm.queuePosition ?? 0 })}</p>
|
||||
|
||||
<div className="mt-9 flex flex-col items-center gap-3 w-full max-w-xs">
|
||||
<button
|
||||
onClick={() => upgradePlan()}
|
||||
className="btn-gold w-full rounded-xl py-3 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Crown className="size-4" />
|
||||
{t("queue.upgrade")}
|
||||
</button>
|
||||
<span className="text-[11px] text-cream/45">{t("queue.skip")}</span>
|
||||
<button
|
||||
onClick={cancel}
|
||||
className="glass rounded-xl px-6 py-2.5 text-cream/70 hover:text-cream"
|
||||
>
|
||||
{t("mm.cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScreenShell>
|
||||
<div className="flex flex-col items-center justify-center min-h-[80dvh] text-center">
|
||||
|
||||
Reference in New Issue
Block a user