fix: center trick pile; add error boundary (surface post-buy crash)
- Trick area: smaller offsets (±50/52) + retuned scale so the played pile sits centered in the felt instead of flung out to the side seats. - ErrorBoundary around screens + overlays: a render error now shows a recoverable in-app message with the cause (and logs componentStack) instead of the browser's blank "page couldn't load" — helps pinpoint the post-purchase crash. Verified: tsc + next build clean; web rebuilt on :1500. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+10
-7
@@ -21,6 +21,7 @@ import { NotificationToaster } from "@/components/online/NotificationToaster";
|
|||||||
import { ResumeGameBar } from "@/components/online/ResumeGameBar";
|
import { ResumeGameBar } from "@/components/online/ResumeGameBar";
|
||||||
import { CelebrationOverlay } from "@/components/online/CelebrationOverlay";
|
import { CelebrationOverlay } from "@/components/online/CelebrationOverlay";
|
||||||
import { MusicToggle } from "@/components/online/MusicToggle";
|
import { MusicToggle } from "@/components/online/MusicToggle";
|
||||||
|
import { ErrorBoundary } from "@/components/ErrorBoundary";
|
||||||
import { PublicProfileModal } from "@/components/online/PublicProfileModal";
|
import { PublicProfileModal } from "@/components/online/PublicProfileModal";
|
||||||
import { CapacitorBack } from "@/components/CapacitorBack";
|
import { CapacitorBack } from "@/components/CapacitorBack";
|
||||||
import { useSessionStore } from "@/lib/session-store";
|
import { useSessionStore } from "@/lib/session-store";
|
||||||
@@ -193,13 +194,15 @@ export default function Page() {
|
|||||||
// reducedMotion="user" makes every Framer Motion animation honor the OS
|
// reducedMotion="user" makes every Framer Motion animation honor the OS
|
||||||
// "reduce motion" accessibility setting (coin rain, confetti, count-ups…).
|
// "reduce motion" accessibility setting (coin rain, confetti, count-ups…).
|
||||||
<MotionConfig reducedMotion="user">
|
<MotionConfig reducedMotion="user">
|
||||||
{renderScreen(screen)}
|
<ErrorBoundary>
|
||||||
<DailyRewardModal />
|
{renderScreen(screen)}
|
||||||
<NotificationToaster />
|
<DailyRewardModal />
|
||||||
<ResumeGameBar />
|
<NotificationToaster />
|
||||||
<CelebrationOverlay />
|
<ResumeGameBar />
|
||||||
<MusicToggle />
|
<CelebrationOverlay />
|
||||||
<PublicProfileModal />
|
<MusicToggle />
|
||||||
|
<PublicProfileModal />
|
||||||
|
</ErrorBoundary>
|
||||||
<CapacitorBack />
|
<CapacitorBack />
|
||||||
{loading && null}
|
{loading && null}
|
||||||
</MotionConfig>
|
</MotionConfig>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catches React render errors so a crash shows a recoverable in-app message
|
||||||
|
* (with the cause) instead of the browser's blank "page couldn't load" screen.
|
||||||
|
*/
|
||||||
|
export class ErrorBoundary extends React.Component<
|
||||||
|
{ children: React.ReactNode },
|
||||||
|
{ error: Error | null }
|
||||||
|
> {
|
||||||
|
state: { error: Error | null } = { error: null };
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||||
|
// Surface the real cause in the console for diagnosis.
|
||||||
|
console.error("[Hokm] render error:", error, info?.componentStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.error) return this.props.children;
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100] grid place-items-center bg-navy-950 p-6 text-center">
|
||||||
|
<div className="glass rounded-3xl p-6 w-full max-w-sm">
|
||||||
|
<div className="text-5xl mb-2">⚠️</div>
|
||||||
|
<h2 className="gold-text text-xl font-black">مشکلی پیش آمد</h2>
|
||||||
|
<p className="text-cream/60 text-sm mt-1">برنامه به مشکل خورد؛ دوباره بارگذاری کنید.</p>
|
||||||
|
<pre className="mt-3 max-h-32 overflow-auto rounded-lg bg-navy-900/70 p-2 text-start text-[10px] leading-relaxed text-rose-300/80 whitespace-pre-wrap break-words">
|
||||||
|
{String(this.state.error?.message || this.state.error)}
|
||||||
|
</pre>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="btn-gold rounded-xl px-5 py-2.5 mt-4 font-bold"
|
||||||
|
>
|
||||||
|
بارگذاری مجدد
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ export function GameTable({
|
|||||||
const exit = onExit ?? reset;
|
const exit = onExit ?? reset;
|
||||||
const vw = useViewportWidth();
|
const vw = useViewportWidth();
|
||||||
// Pull the played-card pile inward on narrow screens so it clears the side stacks.
|
// Pull the played-card pile inward on narrow screens so it clears the side stacks.
|
||||||
const trickScale = vw < 360 ? 0.5 : vw < 460 ? 0.6 : 1;
|
const trickScale = vw < 400 ? 0.82 : 1;
|
||||||
// Smaller played cards on phones so the center pile stays clear of the side seats.
|
// Smaller played cards on phones so the center pile stays clear of the side seats.
|
||||||
const trickCardSize: "sm" | "md" = vw < 480 ? "sm" : "md";
|
const trickCardSize: "sm" | "md" = vw < 480 ? "sm" : "md";
|
||||||
const { phase, players, hakem, trump, turn, currentTrick } = game;
|
const { phase, players, hakem, trump, turn, currentTrick } = game;
|
||||||
@@ -410,11 +410,13 @@ function OpponentHand({
|
|||||||
|
|
||||||
/* ----------------------------- Trick area ----------------------------- */
|
/* ----------------------------- Trick area ----------------------------- */
|
||||||
|
|
||||||
|
// Compact, centered cross — small magnitudes keep the played pile in the middle of
|
||||||
|
// the felt (clear of the side seats/stacks). Each card still nudges toward its player.
|
||||||
const TRICK_OFFSET: Record<Seat, { x: number; y: number }> = {
|
const TRICK_OFFSET: Record<Seat, { x: number; y: number }> = {
|
||||||
0: { x: 0, y: 70 },
|
0: { x: 0, y: 52 },
|
||||||
1: { x: 96, y: 0 },
|
1: { x: 50, y: 0 },
|
||||||
2: { x: 0, y: -70 },
|
2: { x: 0, y: -52 },
|
||||||
3: { x: -96, y: 0 },
|
3: { x: -50, y: 0 },
|
||||||
};
|
};
|
||||||
const TRICK_ENTER: Record<Seat, { x: number; y: number }> = {
|
const TRICK_ENTER: Record<Seat, { x: number; y: number }> = {
|
||||||
0: { x: 0, y: 260 },
|
0: { x: 0, y: 260 },
|
||||||
|
|||||||
Reference in New Issue
Block a user