All files / web/src/components GamePreview.tsx

0% Statements 0/130
0% Branches 0/1
0% Functions 0/1
0% Lines 0/130

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131                                                                                                                                                                                                                                                                     
'use client'

import dynamic from 'next/dynamic'
import { Component, useEffect, useMemo, useState } from 'react'
import type { ReactNode } from 'react'
import type { GameComponent, GameProviderComponent } from '@/lib/arcade/game-sdk/types'
import { MockArcadeEnvironment } from './MockArcadeEnvironment'
import { PreviewModeContext } from '@/contexts/PreviewModeContext'
import { ViewportProvider } from '@/contexts/ViewportContext'
import { getMockGameState } from './MockGameStates'

// Re-export for backwards compatibility
export { PreviewModeContext } from '@/contexts/PreviewModeContext'

// Dynamic import breaks webpack's import chain, preventing useRoomData
// from being bundled with useUserPlayers in shared chunks
const GameModeProviderWithHooks = dynamic(
  () => import('@/contexts/GameModeProviderWithHooks').then((m) => m.GameModeProviderWithHooks),
  { ssr: false }
)

interface GamePreviewProps {
  GameComponent: GameComponent
  Provider: GameProviderComponent
  gameName: string
}

/**
 * Error boundary to prevent game errors from crashing the page
 */
class GameErrorBoundary extends Component<
  { children: ReactNode; fallback: ReactNode },
  { hasError: boolean }
> {
  constructor(props: { children: ReactNode; fallback: ReactNode }) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError() {
    return { hasError: true }
  }

  componentDidCatch(error: Error) {
    console.error(`Game preview error (${error.message}):`, error)
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback
    }
    return this.props.children
  }
}

/**
 * Wrapper for displaying games in demo/preview mode
 * Provides mock arcade contexts so games can render
 */
export function GamePreview({ GameComponent, Provider, gameName }: GamePreviewProps) {
  // Don't render on first mount to avoid hydration issues
  const [mounted, setMounted] = useState(false)
  useEffect(() => {
    setMounted(true)
  }, [])

  // Get mock state for this game
  const mockState = useMemo(() => getMockGameState(gameName), [gameName])

  // Preview mode context value
  const previewModeValue = useMemo(
    () => ({
      isPreview: true,
      mockState,
    }),
    [mockState]
  )

  if (!mounted) {
    return null
  }

  return (
    <GameErrorBoundary
      fallback={
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
            height: '100%',
            color: 'rgba(255, 255, 255, 0.4)',
            fontSize: '14px',
            textAlign: 'center',
            padding: '20px',
          }}
        >
          <span style={{ fontSize: '48px', marginBottom: '10px' }}>🎮</span>
          Game Demo
        </div>
      }
    >
      <PreviewModeContext.Provider value={previewModeValue}>
        <MockArcadeEnvironment gameName={gameName}>
          <GameModeProviderWithHooks>
            {/*
              Mock viewport: Provide 1440x900 dimensions to games via ViewportContext
              This prevents layout issues when games check viewport size
            */}
            <ViewportProvider width={1440} height={900}>
              <div
                style={{
                  width: '1440px',
                  height: '900px',
                  position: 'relative',
                  overflow: 'hidden',
                }}
              >
                <Provider>
                  <GameComponent />
                </Provider>
              </div>
            </ViewportProvider>
          </GameModeProviderWithHooks>
        </MockArcadeEnvironment>
      </PreviewModeContext.Provider>
    </GameErrorBoundary>
  )
}