Apply full refactor updates plus pipeline/email UX confirmations
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m33s

This commit is contained in:
Matt
2026-02-14 15:26:42 +01:00
parent e56e143a40
commit b5425e705e
374 changed files with 116737 additions and 111969 deletions

View File

@@ -1,130 +1,130 @@
'use client'
import { useEffect, useRef, useCallback, useState } from 'react'
export interface VoteUpdate {
projectId: string
totalVotes: number
averageScore: number | null
latestVote: { score: number; isAudienceVote: boolean; votedAt: string } | null
timestamp: string
}
export interface AudienceVoteUpdate {
projectId: string
audienceVotes: number
audienceAverage: number | null
timestamp: string
}
export interface SessionStatusUpdate {
status: string
timestamp: string
}
export interface ProjectChangeUpdate {
projectId: string | null
projectIndex: number
timestamp: string
}
interface SSECallbacks {
onVoteUpdate?: (data: VoteUpdate) => void
onAudienceVote?: (data: AudienceVoteUpdate) => void
onSessionStatus?: (data: SessionStatusUpdate) => void
onProjectChange?: (data: ProjectChangeUpdate) => void
onConnected?: () => void
onError?: (error: Event) => void
}
export function useLiveVotingSSE(
sessionId: string | null,
callbacks: SSECallbacks
) {
const [isConnected, setIsConnected] = useState(false)
const eventSourceRef = useRef<EventSource | null>(null)
const callbacksRef = useRef(callbacks)
callbacksRef.current = callbacks
const connect = useCallback(() => {
if (!sessionId) return
// Close any existing connection
if (eventSourceRef.current) {
eventSourceRef.current.close()
}
const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''
const url = `${baseUrl}/api/live-voting/stream?sessionId=${sessionId}`
const es = new EventSource(url)
eventSourceRef.current = es
es.addEventListener('connected', () => {
setIsConnected(true)
callbacksRef.current.onConnected?.()
})
es.addEventListener('vote_update', (event) => {
try {
const data = JSON.parse(event.data) as VoteUpdate
callbacksRef.current.onVoteUpdate?.(data)
} catch {
// Ignore parse errors
}
})
es.addEventListener('audience_vote', (event) => {
try {
const data = JSON.parse(event.data) as AudienceVoteUpdate
callbacksRef.current.onAudienceVote?.(data)
} catch {
// Ignore parse errors
}
})
es.addEventListener('session_status', (event) => {
try {
const data = JSON.parse(event.data) as SessionStatusUpdate
callbacksRef.current.onSessionStatus?.(data)
} catch {
// Ignore parse errors
}
})
es.addEventListener('project_change', (event) => {
try {
const data = JSON.parse(event.data) as ProjectChangeUpdate
callbacksRef.current.onProjectChange?.(data)
} catch {
// Ignore parse errors
}
})
es.onerror = (event) => {
setIsConnected(false)
callbacksRef.current.onError?.(event)
// Auto-reconnect after 3 seconds
setTimeout(() => {
if (eventSourceRef.current === es) {
connect()
}
}, 3000)
}
}, [sessionId])
const disconnect = useCallback(() => {
if (eventSourceRef.current) {
eventSourceRef.current.close()
eventSourceRef.current = null
setIsConnected(false)
}
}, [])
useEffect(() => {
connect()
return () => disconnect()
}, [connect, disconnect])
return { isConnected, reconnect: connect, disconnect }
}
'use client'
import { useEffect, useRef, useCallback, useState } from 'react'
export interface VoteUpdate {
projectId: string
totalVotes: number
averageScore: number | null
latestVote: { score: number; isAudienceVote: boolean; votedAt: string } | null
timestamp: string
}
export interface AudienceVoteUpdate {
projectId: string
audienceVotes: number
audienceAverage: number | null
timestamp: string
}
export interface SessionStatusUpdate {
status: string
timestamp: string
}
export interface ProjectChangeUpdate {
projectId: string | null
projectIndex: number
timestamp: string
}
interface SSECallbacks {
onVoteUpdate?: (data: VoteUpdate) => void
onAudienceVote?: (data: AudienceVoteUpdate) => void
onSessionStatus?: (data: SessionStatusUpdate) => void
onProjectChange?: (data: ProjectChangeUpdate) => void
onConnected?: () => void
onError?: (error: Event) => void
}
export function useLiveVotingSSE(
sessionId: string | null,
callbacks: SSECallbacks
) {
const [isConnected, setIsConnected] = useState(false)
const eventSourceRef = useRef<EventSource | null>(null)
const callbacksRef = useRef(callbacks)
callbacksRef.current = callbacks
const connect = useCallback(() => {
if (!sessionId) return
// Close any existing connection
if (eventSourceRef.current) {
eventSourceRef.current.close()
}
const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''
const url = `${baseUrl}/api/live-voting/stream?sessionId=${sessionId}`
const es = new EventSource(url)
eventSourceRef.current = es
es.addEventListener('connected', () => {
setIsConnected(true)
callbacksRef.current.onConnected?.()
})
es.addEventListener('vote_update', (event) => {
try {
const data = JSON.parse(event.data) as VoteUpdate
callbacksRef.current.onVoteUpdate?.(data)
} catch {
// Ignore parse errors
}
})
es.addEventListener('audience_vote', (event) => {
try {
const data = JSON.parse(event.data) as AudienceVoteUpdate
callbacksRef.current.onAudienceVote?.(data)
} catch {
// Ignore parse errors
}
})
es.addEventListener('session_status', (event) => {
try {
const data = JSON.parse(event.data) as SessionStatusUpdate
callbacksRef.current.onSessionStatus?.(data)
} catch {
// Ignore parse errors
}
})
es.addEventListener('project_change', (event) => {
try {
const data = JSON.parse(event.data) as ProjectChangeUpdate
callbacksRef.current.onProjectChange?.(data)
} catch {
// Ignore parse errors
}
})
es.onerror = (event) => {
setIsConnected(false)
callbacksRef.current.onError?.(event)
// Auto-reconnect after 3 seconds
setTimeout(() => {
if (eventSourceRef.current === es) {
connect()
}
}, 3000)
}
}, [sessionId])
const disconnect = useCallback(() => {
if (eventSourceRef.current) {
eventSourceRef.current.close()
eventSourceRef.current = null
setIsConnected(false)
}
}, [])
useEffect(() => {
connect()
return () => disconnect()
}, [connect, disconnect])
return { isConnected, reconnect: connect, disconnect }
}