analytics and embed meta update!!!

This commit is contained in:
legop3
2026-07-31 22:12:04 -04:00
parent 1aecab66e7
commit 8c5d98bed6
45 changed files with 395 additions and 699 deletions
@@ -1,10 +1,9 @@
// Barcode Games Panel
// Purpose: Shows barcode game selection, current game status, and player points in the Activities tab.
// Scope: Keeps the driver-side UI compact but informative; game-specific rules stay in server game modules.
import { useEffect, useMemo, useRef, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import useBarcodeGameState from '../../barcodeGames/useBarcodeGameState.js';
import CardFrame from '../CardFrame/index.jsx';
import { trackAnalyticsEvent } from '../../analytics/index.js';
import { useSessionSelector } from '../../context/SessionContext.jsx';
import { isFeatureEnabled } from '../../lib/features.js';
@@ -261,10 +260,8 @@ export default function BarcodeGamesPanel() {
}
function BarcodeGamesPanelContent() {
const { state, connectionState, voteForGame } = useBarcodeGameState();
const { state, voteForGame } = useBarcodeGameState();
const [pendingGameId, setPendingGameId] = useState(null);
const activeSignatureRef = useRef('');
const participantCountRef = useRef(0);
const activeGame = state.activeGame;
const display = activeGame?.display || {};
// Voting should stop once the shared game system has moved past selection.
@@ -278,77 +275,10 @@ function BarcodeGamesPanelContent() {
const participants = Array.isArray(state.participants) ? state.participants : [];
const activeTheme = getGameTheme(activeGame?.themeColor);
useEffect(() => {
if (!connectionState.stale || !connectionState.lastReceivedAt) return;
/*
Stale barcode-game state is worth tracking because it points to a real
interaction problem: users can be looking at old game choices or scores
even though the rest of the page appears loaded.
*/
trackAnalyticsEvent('barcode_game_state_stale', {
connected: connectionState.connected,
phase: state.phase || 'unknown',
});
}, [connectionState.connected, connectionState.lastReceivedAt, connectionState.stale, state.phase]);
useEffect(() => {
const signature = `${activeGame?.id || 'none'}:${activeGame?.status || state.phase || 'unknown'}`;
if (activeSignatureRef.current === signature) return;
activeSignatureRef.current = signature;
/*
Game lifecycle events are derived from server state instead of button
clicks so they still report games started by another user or by a scanner
workflow outside this panel.
*/
trackAnalyticsEvent('barcode_game_active_change', {
gameId: activeGame?.id || '',
status: activeGame?.status || '',
phase: state.phase || 'unknown',
});
if (activeGame?.status === 'ending' || state.phase === 'ending') {
trackAnalyticsEvent('barcode_game_round_end', {
gameId: activeGame?.id || '',
participantCount: participants.length,
});
}
}, [activeGame?.id, activeGame?.status, participants.length, state.phase]);
useEffect(() => {
if (participants.length <= participantCountRef.current) {
participantCountRef.current = participants.length;
return;
}
participantCountRef.current = participants.length;
trackAnalyticsEvent('barcode_game_player_join', {
gameId: activeGame?.id || '',
participantCount: participants.length,
});
}, [activeGame?.id, participants.length]);
const handleVote = async (gameId) => {
setPendingGameId(gameId);
trackAnalyticsEvent('barcode_game_vote', {
gameId,
phase: state.phase || 'unknown',
status: 'started',
});
try {
await voteForGame(gameId);
trackAnalyticsEvent('barcode_game_vote', {
gameId,
phase: state.phase || 'unknown',
status: 'accepted',
});
} catch (error) {
trackAnalyticsEvent('barcode_game_vote', {
gameId,
phase: state.phase || 'unknown',
status: 'failed',
reason: error?.message || 'unknown',
});
throw error;
} finally {
setPendingGameId(null);
}