A new README for the combined project with setup from the Flashpoint Ultimate torrent. New feature docs for streaming, proxy sign-in and game saves; deployment, upgrade and streaming READMEs updated for the single compose file; the project renamed throughout.
12 KiB
Component Overview
This document provides an overview of the component architecture and organization in the Dotcom Arcade frontend.
Component Organization
Components are organized by domain and functionality:
components/
├── activities/ # Activity log components (2 components)
├── auth/ # Authentication components (8 components)
├── common/ # Shared/utility components (14 components)
├── dialogs/ # Modal dialogs (1 component)
├── error/ # Error handling components (8 components)
├── game/ # Game-specific components (1 component)
├── github/ # GitHub integration (1 component)
├── home/ # Home page components (1 component)
├── jobs/ # Job management (4 components)
├── layout/ # Layout structure (9 components)
├── library/ # Game browsing/display (10 components)
├── player/ # Game players (3 components)
├── playlist/ # Playlist management (10 components)
│ └── SharePlaylistDialog includes domain selector for admins
├── roles/ # Role management (3 components)
├── search/ # Search and filtering (5 components)
├── settings/ # Settings tabs (6 components)
├── stats/ # Statistics/charts (5 components)
├── theme/ # Theme controls (1 component)
├── ui/ # Shadcn UI primitives (36 components)
└── users/ # User management (3 components)
Component Categories
Authentication Components
Located in components/auth/
- LoginForm - User login form with validation
- RegisterForm - User registration form
- ProtectedRoute - Route wrapper for authorization
- AuthContext - Authentication context provider
Common Components
Located in components/common/
- Breadcrumbs - Navigation bar with integrated back button and contextual breadcrumb trail
- Now acts as a navigation bar instead of just breadcrumbs
- Integrated back button with vertical divider separator
- Props:
items(breadcrumb trail),showBackButton(default: true) - Visual: rounded bar with
bg-muted/50, pill-style breadcrumb items - Exports types:
BreadcrumbContext,PlayerBreadcrumbContext,BreadcrumbItem BreadcrumbContextmoved fromGameCard.tsxtoBreadcrumbs.tsxfor centralization
- FormattedDate - Reusable date/time formatting component with user preferences
- CardSizeControl - Control for adjusting card size in grid views
- ConfirmDialog - Generic confirmation dialog for destructive actions
- ErrorBoundary - Error boundary wrapper for graceful error handling
- FavoriteButton - Button to add games to favorites
- RemoveFavoriteButton - Button to remove games from favorites
- MaintenanceGuard - Component to display maintenance mode message
- MobileWarningDialog - Warning for mobile users about limited functionality
- NetworkStatusIndicator - Shows online/offline network status
- RoleGuard - Component wrapper for role-based access control
- ViewOptions - Toggle between list and grid view modes
Playlist Components
Located in components/playlist/
- SharePlaylistDialog - Dialog for sharing playlists via URL. Admin users
see a domain selector dropdown to choose which domain to use for the share
link. Non-admin users use the default domain from public settings. Share URLs
are constructed client-side via the
buildShareUrl()utility. - Other playlist management components...
Settings Components
Located in components/settings/
- AppSettingsTab - Application settings including Site Name, Maintenance Mode, Theme, and Domain Settings. The Domain Settings card allows admins to add/delete domains and set a default via radio buttons, with hostname validation.
- Other settings tabs...
Layout Components
Located in components/layout/
- AppShell - Main application layout container
- Header - Top navigation bar with search, theme picker, user menu
- Sidebar - Collapsible side navigation with route-based highlighting
Game Components
Located in components/library/ and components/game/
- GameCard - Card view for individual games
- Keyboard accessible:
role="link",tabIndex={0}, andonKeyDownhandler for Enter/Space keys AddToPlaylistModalnow conditionally renders only when open (prevents 50+ hidden modals in grid)
- Keyboard accessible:
- GameGrid - Responsive grid layout for game cards
- GameList - List view for games
- GameListItem - Individual list item
- GameBrowseLayout - Complete browse layout with filters and pagination
- GameInfoGrid - Detailed game information display
Player Components
Located in components/player/
- GamePlayer - Universal game player wrapper
- Debug logging moved to
useEffectto prevent re-renders on every log - Game launch only proceeds once player is mounted
- Debug logging moved to
- RufflePlayer - Flash game emulator component
- Proper TypeScript type declarations for
window.RufflePlayer(noanycasts) - Supports scale modes: exactfit, noborder, showall (default), noscale
- Proper TypeScript type declarations for
UI Components
Located in components/ui/
The application uses Shadcn UI - a collection of re-usable components built with Radix UI and Tailwind CSS. These are copied into the project and can be customized.
40+ components including:
- Button, Input, Textarea, Select, Checkbox, Switch
- Dialog, Alert Dialog, Sheet, Popover, Dropdown Menu
- Card, Badge, Avatar, Separator, Skeleton
- Table, Data Table, Pagination
- Toast, Sonner (notifications)
- Form components with React Hook Form integration
Component Design Patterns
1. Composition Pattern
Components use composition for flexibility:
<Card>
<CardHeader>
<CardTitle>Game Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>
{/* Content */}
</CardContent>
<CardFooter>
{/* Actions */}
</CardFooter>
</Card>
2. Controlled Components
Components receive state via props and notify parent of changes:
interface FilterPanelProps {
filters: GameFilters;
onFiltersChange: (filters: GameFilters) => void;
}
export function FilterPanel({ filters, onFiltersChange }: FilterPanelProps) {
// Component is controlled by parent
}
3. Render Props
Flexible rendering through render props:
interface GameBrowseLayoutProps {
renderHeader?: () => React.ReactNode;
renderActions?: () => React.ReactNode;
renderFilters?: () => React.ReactNode;
}
4. TypeScript Props
All components are fully typed:
interface GameCardProps {
game: Game;
onAddToPlaylist?: (gameId: string) => void;
onToggleFavorite?: (gameId: string) => void;
isFavorited?: boolean;
showFavoriteOnHoverOnly?: boolean;
}
5. Skeleton Loading States
All data components have corresponding skeleton loaders:
// GameCard.tsx
export function GameCard({ game }: GameCardProps) { /* ... */ }
// GameCardSkeleton.tsx
export function GameCardSkeleton() {
return <Skeleton className="h-full w-full" />;
}
Component Naming Conventions
File Naming
- PascalCase for component files:
GameCard.tsx,UserTable.tsx - camelCase for utility files:
api.ts,utils.ts - kebab-case for UI primitive files:
dropdown-menu.tsx,alert-dialog.tsx
Component Exports
// Named export (preferred for main components)
export function GameCard({ game }: GameCardProps) {
/* ... */
}
// Default export (used for views/pages)
export default function BrowseView() {
/* ... */
}
Component Props Best Practices
Optional vs Required Props
interface GamePlayerProps {
// Required props
title: string;
platform: string;
canPlayInBrowser: boolean;
// Optional props with defaults
allowFullscreen?: boolean; // Default: true
showControls?: boolean; // Default: true
height?: string; // Default: 'calc(100vh - 220px)'
// Optional callbacks
onFullscreenChange?: (isFullscreen: boolean) => void;
}
Default Props Pattern
export function GamePlayer({
title,
platform,
canPlayInBrowser,
allowFullscreen = true,
showControls = true,
height = 'calc(100vh - 220px)',
onFullscreenChange,
}: GamePlayerProps) {
// Implementation
}
State Management in Components
Local Component State
Use useState for UI-only state:
const [isFullscreen, setIsFullscreen] = useState(false);
const [imageError, setImageError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
Global UI State (Zustand)
Access global state via Zustand stores:
const sidebarCollapsed = useUIStore((state) => state.sidebarCollapsed);
const { isAuthenticated, user } = useAuthStore();
const { mode, primaryColor } = useThemeStore();
Server State (TanStack Query)
Use custom hooks wrapping TanStack Query:
const { data: games, isLoading, error } = useGames(filters);
const { data: playlists } = usePlaylists();
Component Lifecycle
Mount Effect Hook
Use custom useMountEffect for mount-only effects:
import { useMountEffect } from '@/hooks/useMountEffect';
function MyComponent() {
useMountEffect(() => {
// Runs once on mount
console.log('Component mounted');
});
}
Cleanup Pattern
Always clean up resources in useEffect:
useEffect(() => {
const handleResize = () => {
// Handle resize
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
Error Boundaries
Top-level error boundary catches React errors:
<ErrorBoundary>
<App />
</ErrorBoundary>
Component-level error handling:
if (error) {
return (
<div className="text-center py-8">
<AlertCircle className="mx-auto text-red-500" />
<p>Failed to load data</p>
</div>
);
}
Accessibility
ARIA Labels
<button
aria-label="Toggle sidebar"
title="Toggle sidebar width"
onClick={toggleSidebar}
>
<Menu size={20} />
</button>
Keyboard Navigation
useEffect(() => {
const handleEscKey = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isFullscreen) {
event.preventDefault();
setIsFullscreen(false);
}
};
window.addEventListener('keydown', handleEscKey);
return () => window.removeEventListener('keydown', handleEscKey);
}, [isFullscreen]);
Focus Management
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
Performance Optimizations
React.memo
Memoize expensive components:
export const GameCard = memo(function GameCard({ game }: GameCardProps) {
// Component implementation
});
useMemo
Memoize expensive computations:
const sortedGames = useMemo(() => {
return games.sort((a, b) => a.title.localeCompare(b.title));
}, [games]);
useCallback
Memoize callback functions:
const handleAddToPlaylist = useCallback(
(gameId: string) => {
// Add to playlist logic
},
[
/* dependencies */
]
);
Lazy Loading
Load images lazily:
<img
src={imageUrl}
alt={game.title}
loading="lazy"
onLoad={() => setImageLoaded(true)}
onError={() => setImageError(true)}
/>
Testing Components (Future)
Component Testing Structure
import { render, screen } from '@testing-library/react';
import { GameCard } from './GameCard';
describe('GameCard', () => {
it('renders game title', () => {
const game = { id: '1', title: 'Test Game', /* ... */ };
render(<GameCard game={game} />);
expect(screen.getByText('Test Game')).toBeInTheDocument();
});
});