mirror of
https://github.com/yusufipk/YTChatHub.git
synced 2026-09-11 10:56:17 +00:00
feat: add YouTube live poll support
Add backend support for YouTube live polls including: - Poll event handling and state tracking - New `/poll/current` endpoint for current poll data - Server-sent events stream at `/poll/stream` for real-time updates - Poll emitter for broadcasting poll changes to clients
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import type { ChatMessage } from '@shared/chat';
|
||||
import type { ChatMessage, Poll } from '@shared/chat';
|
||||
import { useTimezone } from '../../lib/TimezoneContext';
|
||||
import { formatTimestamp } from '../../lib/timezone';
|
||||
import { proxyImageUrl } from '../../lib/imageProxy';
|
||||
@@ -46,6 +46,7 @@ let globalConnectionListeners: Set<(payload: any) => void> = new Set();
|
||||
export default function DashboardPage() {
|
||||
const { messages, refresh, error: pollError } = useChatMessages();
|
||||
const { selection, status: overlayStatus } = useOverlaySelection();
|
||||
const { poll: currentPoll } = usePoll();
|
||||
const { connected, liveId, connect, disconnect, connecting } = useConnection();
|
||||
const [confirmUrl, setConfirmUrl] = useState<string | null>(null);
|
||||
const [superChatPulse, setSuperChatPulse] = useState(false);
|
||||
@@ -198,6 +199,12 @@ export default function DashboardPage() {
|
||||
{connected && (
|
||||
<>
|
||||
<header className="dashboard__tabs">
|
||||
{currentPoll && (
|
||||
<div className="poll-indicator" title="Active poll on YouTube">
|
||||
<span className="poll-indicator__icon">📊</span>
|
||||
<span className="poll-indicator__text">Active Poll</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="tab">
|
||||
Messages <span className="tab__count">{regularMessages.length}</span>
|
||||
</div>
|
||||
@@ -313,6 +320,7 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -455,6 +463,38 @@ function useConnection() {
|
||||
return { connected, liveId, connect, disconnect, connecting };
|
||||
}
|
||||
|
||||
function usePoll() {
|
||||
const [poll, setPoll] = useState<Poll | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Create SSE connection for polls
|
||||
const eventSource = new EventSource(`${BACKEND_URL}/poll/stream`);
|
||||
|
||||
eventSource.addEventListener('poll', ((event: MessageEvent) => {
|
||||
try {
|
||||
const payload = JSON.parse(event.data);
|
||||
setPoll(payload.poll);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse poll payload', error);
|
||||
}
|
||||
}) as EventListener);
|
||||
|
||||
eventSource.addEventListener('heartbeat', () => {
|
||||
// Heartbeat - connection is alive
|
||||
});
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
console.error('[Dashboard] Poll SSE connection error', error);
|
||||
};
|
||||
|
||||
return () => {
|
||||
eventSource.close();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { poll };
|
||||
}
|
||||
|
||||
type ConnectionControlProps = {
|
||||
connected: boolean;
|
||||
liveId: string | null;
|
||||
|
||||
@@ -1357,3 +1357,36 @@ main {
|
||||
background: rgba(248, 113, 113, 0.2);
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
/* Poll indicator */
|
||||
.poll-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.875rem;
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #c4b5fd;
|
||||
animation: pollPulse 2s ease-in-out infinite;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.poll-indicator__icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.poll-indicator__text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes pollPulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 rgba(139, 92, 246, 0);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user