All files / web/src/hooks useObserverCoPlayProfile.ts

76.08% Statements 70/92
66.66% Branches 6/9
60% Functions 3/5
76.08% Lines 70/92

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 931x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 13x 13x 13x 13x 13x 13x 1x           1x 65x 65x 65x 65x 1x       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 26x 26x 26x 13x           26x 26x 26x             26x 26x 26x 26x       26x 26x 26x 26x 26x 26x 26x 26x 26x 26x 26x  
'use client'
 
import { useCallback, useMemo, useSyncExternalStore } from 'react'
 
/**
 * Observer co-play profile for joining practice game breaks as a participant.
 * Stored in localStorage so it persists across page reloads.
 */
export interface ObserverCoPlayProfile {
  name: string
  emoji: string
  color: string
  isReady: boolean
}
 
const STORAGE_KEY = 'observer-coplay-profile'
 
// ============================================================================
// External store for cross-component reactivity
// ============================================================================
 
let listeners: Array<() => void> = []
 
function subscribe(listener: () => void) {
  listeners = [...listeners, listener]
  return () => {
    listeners = listeners.filter((l) => l !== listener)
  }
}
 
function emitChange() {
  for (const listener of listeners) {
    listener()
  }
}
 
function getSnapshot(): string | null {
  if (typeof window === 'undefined') return null
  return localStorage.getItem(STORAGE_KEY)
}
 
function getServerSnapshot(): string | null {
  return null
}
 
// ============================================================================
// Hook
// ============================================================================
 
/**
 * Manage the observer's co-play profile for game break participation.
 *
 * Profile is stored in localStorage and shared across components
 * via useSyncExternalStore for instant reactivity.
 */
export function useObserverCoPlayProfile() {
  const raw = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
 
  const profile = useMemo<ObserverCoPlayProfile | null>(() => {
    if (!raw) return null
    try {
      return JSON.parse(raw) as ObserverCoPlayProfile
    } catch {
      return null
    }
  }, [raw])
 
  const setProfile = useCallback((update: ObserverCoPlayProfile | null) => {
    if (update) {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(update))
    } else {
      localStorage.removeItem(STORAGE_KEY)
    }
    emitChange()
  }, [])
 
  const updateProfile = useCallback(
    (partial: Partial<ObserverCoPlayProfile>) => {
      const current = profile ?? { name: '', emoji: '😊', color: '#6366f1', isReady: false }
      setProfile({ ...current, ...partial })
    },
    [profile, setProfile]
  )
 
  return {
    profile,
    isReady: profile?.isReady ?? false,
    setProfile,
    updateProfile,
    clearProfile: useCallback(() => setProfile(null), [setProfile]),
  }
}