Build 2602261523: live updates, invite cleanup and nuclear resync

This commit is contained in:
2026-02-26 15:24:10 +13:00
parent 5dfe614d15
commit 50be0b6b57
12 changed files with 939 additions and 230 deletions

View File

@@ -4,6 +4,24 @@ import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
import { authFetch, getApiBase, getToken, clearToken } from './lib/auth'
const normalizeRecentResults = (items: any[]) =>
items
.filter((item: any) => item?.id)
.map((item: any) => {
const id = item.id
const rawTitle = item.title
const placeholder =
typeof rawTitle === 'string' && rawTitle.trim().toLowerCase() === `request ${id}`
return {
id,
title: !rawTitle || placeholder ? `Request #${id}` : rawTitle,
year: item.year,
statusLabel: item.statusLabel,
artwork: item.artwork,
createdAt: item.createdAt ?? null,
}
})
export default function HomePage() {
const router = useRouter()
const [query, setQuery] = useState('')
@@ -33,6 +51,7 @@ export default function HomePage() {
const [servicesError, setServicesError] = useState<string | null>(null)
const [serviceTesting, setServiceTesting] = useState<Record<string, boolean>>({})
const [serviceTestResults, setServiceTestResults] = useState<Record<string, string | null>>({})
const [liveStreamConnected, setLiveStreamConnected] = useState(false)
const submit = (event: React.FormEvent) => {
event.preventDefault()
@@ -137,25 +156,7 @@ export default function HomePage() {
}
const data = await response.json()
if (Array.isArray(data?.results)) {
setRecent(
data.results
.filter((item: any) => item?.id)
.map((item: any) => {
const id = item.id
const rawTitle = item.title
const placeholder =
typeof rawTitle === 'string' &&
rawTitle.trim().toLowerCase() === `request ${id}`
return {
id,
title: !rawTitle || placeholder ? `Request #${id}` : rawTitle,
year: item.year,
statusLabel: item.statusLabel,
artwork: item.artwork,
createdAt: item.createdAt ?? null,
}
})
)
setRecent(normalizeRecentResults(data.results))
}
} catch (error) {
console.error(error)
@@ -196,10 +197,79 @@ export default function HomePage() {
}
}
load()
void load()
if (liveStreamConnected) {
return
}
const timer = setInterval(load, 30000)
return () => clearInterval(timer)
}, [authReady, router])
}, [authReady, liveStreamConnected, router])
useEffect(() => {
if (!authReady) {
setLiveStreamConnected(false)
return
}
const token = getToken()
if (!token) {
setLiveStreamConnected(false)
return
}
const baseUrl = getApiBase()
const streamUrl = `${baseUrl}/events/stream?access_token=${encodeURIComponent(token)}&recent_days=${encodeURIComponent(String(recentDays))}`
let closed = false
const source = new EventSource(streamUrl)
source.onopen = () => {
if (closed) return
setLiveStreamConnected(true)
}
source.onmessage = (event) => {
if (closed) return
setLiveStreamConnected(true)
try {
const payload = JSON.parse(event.data)
if (!payload || typeof payload !== 'object') {
return
}
if (payload.type === 'home_recent') {
if (Array.isArray(payload.results)) {
setRecent(normalizeRecentResults(payload.results))
setRecentError(null)
setRecentLoading(false)
} else if (typeof payload.error === 'string' && payload.error.trim()) {
setRecentError('Recent requests are not available right now.')
setRecentLoading(false)
}
return
}
if (payload.type === 'home_services') {
if (payload.status && typeof payload.status === 'object') {
setServicesStatus(payload.status)
setServicesError(null)
setServicesLoading(false)
} else if (typeof payload.error === 'string' && payload.error.trim()) {
setServicesError('Service status is not available right now.')
setServicesLoading(false)
}
}
} catch (error) {
console.error(error)
}
}
source.onerror = () => {
if (closed) return
setLiveStreamConnected(false)
}
return () => {
closed = true
setLiveStreamConnected(false)
source.close()
}
}, [authReady, recentDays])
const runSearch = async (term: string) => {
try {