Tijdelijke debug-tooling voor M8-acceptance op Vercel preview. - app/api/debug/realtime-stream/route.ts — geen auth, geen filtering; dropt elke pg_notify-event op scrum4me_changes rauw door als SSE - app/debug-realtime/page.tsx — open zonder login op de root, toont binnenkomende events in een simpele <table> Doel: isoleren of de SSE + Postgres LISTEN-pipe op Vercel überhaupt events laat zien, los van iron-session, productfilter of solo-store. Als ook deze niets binnen krijgt: probleem zit in pg connection of Vercel function lifecycle. Als deze wel events toont: probleem zit hoger in de stack (filter, store, hook). VERWIJDEREN voordat de PR uit draft gaat. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
3.1 KiB
TypeScript
104 lines
3.1 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef, useState } from 'react'
|
|
|
|
interface Row {
|
|
receivedAt: string
|
|
type: 'ready' | 'message' | 'error'
|
|
raw: string
|
|
}
|
|
|
|
const MAX_ROWS = 200
|
|
|
|
export function DebugRealtimeClient() {
|
|
const [rows, setRows] = useState<Row[]>([])
|
|
const [status, setStatus] = useState<'connecting' | 'open' | 'closed' | 'error'>('connecting')
|
|
const sourceRef = useRef<EventSource | null>(null)
|
|
|
|
useEffect(() => {
|
|
const source = new EventSource('/api/debug/realtime-stream')
|
|
sourceRef.current = source
|
|
|
|
const append = (row: Row) => {
|
|
setRows((prev) => [row, ...prev].slice(0, MAX_ROWS))
|
|
}
|
|
|
|
source.addEventListener('ready', (e) => {
|
|
setStatus('open')
|
|
const data = (e as MessageEvent).data ?? ''
|
|
append({ receivedAt: new Date().toISOString(), type: 'ready', raw: data })
|
|
})
|
|
|
|
source.addEventListener('error', (e) => {
|
|
setStatus('error')
|
|
const data = (e as MessageEvent).data ?? '(no data)'
|
|
append({ receivedAt: new Date().toISOString(), type: 'error', raw: data })
|
|
})
|
|
|
|
source.onmessage = (e) => {
|
|
append({ receivedAt: new Date().toISOString(), type: 'message', raw: e.data ?? '' })
|
|
}
|
|
|
|
source.onerror = () => {
|
|
setStatus('error')
|
|
}
|
|
|
|
return () => {
|
|
source.close()
|
|
sourceRef.current = null
|
|
}
|
|
}, [])
|
|
|
|
function statusColor() {
|
|
switch (status) {
|
|
case 'open':
|
|
return 'green'
|
|
case 'error':
|
|
return 'red'
|
|
case 'closed':
|
|
return 'gray'
|
|
default:
|
|
return 'orange'
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div style={{ marginTop: 16 }}>
|
|
<div style={{ marginBottom: 12 }}>
|
|
Status:{' '}
|
|
<span style={{ color: statusColor(), fontWeight: 'bold' }}>{status}</span> · totaal{' '}
|
|
{rows.length} entries
|
|
</div>
|
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
|
|
<thead>
|
|
<tr style={{ background: '#f0f0f0', textAlign: 'left' }}>
|
|
<th style={{ padding: 6, border: '1px solid #ddd', width: 220 }}>received_at</th>
|
|
<th style={{ padding: 6, border: '1px solid #ddd', width: 100 }}>type</th>
|
|
<th style={{ padding: 6, border: '1px solid #ddd' }}>payload</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={3} style={{ padding: 12, textAlign: 'center', color: '#888' }}>
|
|
Wachten op events… trigger een mutatie via UI of script.
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
rows.map((row, idx) => (
|
|
<tr key={`${row.receivedAt}-${idx}`}>
|
|
<td style={{ padding: 6, border: '1px solid #ddd', whiteSpace: 'nowrap' }}>
|
|
{row.receivedAt}
|
|
</td>
|
|
<td style={{ padding: 6, border: '1px solid #ddd' }}>{row.type}</td>
|
|
<td style={{ padding: 6, border: '1px solid #ddd', wordBreak: 'break-all' }}>
|
|
<code>{row.raw}</code>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|