# frozen_string_literal: true # Rails 8.0+ Hotwire Architecture Example # Demonstrates ViewComponent, Turbo Streams, and Stimulus patterns # for game admin dashboards with ethical engagement features require 'bundler/inline' gem 'rails', '~> 8.0' gem 'view_component', '~> 3.0' gem 'redis', '~> 5.0' # ============================================================================= # CORE APPLICATION CONFIGURATION # ============================================================================= module GameAdmin # Rails 8 application demonstrating Hotwire architecture patterns # # This module provides: # - Component-based UI architecture via ViewComponent # - Real-time updates via Turbo Streams # - Interactive behaviors via Stimulus controllers # - Ethical engagement features (transparent odds, rest-state mechanics) # # @example Mount in config/routes.rb # mount GameAdmin::Application, at: '/admin' class Application include Rails.application.config end # ============================================================================= # VIEW COMPONENT BASE # ============================================================================= # Base ViewComponent with accessibility and ethical engagement patterns # # All admin dashboard components inherit from this base class # # @security Brakeman scans for permit! usage (Mass Assignment protection) # @test RSpec unit tests for component rendering class AdminComponent < ViewComponent::Base # Default accessibility attributes def aria_label @aria_label || self.class.name.downcase.demodulize end # Ethical engagement: display transparent odds information # # @param drop_table [Hash] loot table with odds # @return [String] formatted odds display def render_loot_odts(drop_table) content_tag :div, class: 'loot-odds', aria: { label: 'Drop rates and expected value' } do concat content_tag(:p, 'Transparent Drop Rates') concat render_drop_rates(drop_table) end end # Rest-State Mechanics: offline regeneration indicator # # Shows player's offline resource accumulation # # @param player_id [Integer] player identifier # @return [String] rest-state display def render_rest_state(player_id) offline_resources = ::PlayerOfflineResources.find_by(player_id: player_id) return '' unless offline_resources content_tag :div, class: 'rest-state', data: { stimulus_target: 'restState', player_id: player_id } do concat content_tag(:p, "Offline Regeneration") concat content_tag(:span, offline_resources.resources_gained.to_s) end end private # Render individual drop rates with expected value def render_drop_rates(drop_table) items = drop_table.items.map do |item| { name: item.name, rate: item.drop_rate, expected_value: item.market_value * item.drop_rate } end items.sort_by { |i| -i.expected_value }.map do |item| content_tag(:div, class: 'drop-rate-row') do concat content_tag(:span, item.name, class: 'item-name') concat content_tag(:span, "#{item.rate}%", class: 'drop-rate') concat content_tag(:span, "EV: #{item.expected_value}", class: 'expected-value') end end.join end end # ============================================================================= # TURBO STREAMS SERVICE # ============================================================================= # Real-time dashboard updates via Turbo Streams # # Provides WebSocket-based live updates for: # - Player analytics # - Economy monitoring # - Moderation alerts # # @dependency Redis pub/sub for stream broadcasting module TurboStreams class DashboardBroadcaster def initialize(channel: 'dashboard') @channel = channel @redis = Redis.new end # Broadcast player metric update # # @param metric [String] metric name # @param value [Float] current value # @param timestamp [Time] update time def broadcast_metric(metric:, value:, timestamp: Time.current) payload = { metric: metric, value: value, timestamp: timestamp.iso8601 } @redis.publish(@channel, ActiveSupport::JSON.encode(payload)) end # Broadcast economy faucet/sink event # # @param event_type [String] :faucet or :sink # @param amount [Integer] resource amount # @param source [String] event source def broadcast_economy_event(event_type:, amount:, source:) payload = { event_type: event_type, amount: amount, source: source, timestamp: Time.current.iso8601 } @redis.publish('economy', ActiveSupport::JSON.encode(payload)) end # Broadcast moderation alert # # @param alert_type [String] alert classification # @param player_id [Integer] flagged player # @param details [Hash] alert context def broadcast_alert(alert_type:, player_id:, details:) payload = { alert_type: alert_type, player_id: player_id, details: details, timestamp: Time.current.iso8601 } @redis.publish('alerts', ActiveSupport::JSON.encode(payload)) end end end # ============================================================================= # STIMulus CONTROLLER BASE # ============================================================================= # Stimulus controller for interactive admin dashboard # # Handles: # - Real-time chart updates # - Filter/sort operations # - Accessibility keyboard shortcuts # # @example HTML usage #