"use client"; import { FormEvent, useCallback, useEffect, useState } from "react"; type TweakMeta = { key: string; label: string; hint: string; kind: "number" | "boolean" | "string"; unit?: string; }; type ViaplayTweaks = Record; type ViaplayAccount = { id: string; label: string; email: string; hasSession: boolean; hasAccessToken: boolean; hasProfileId: boolean; hasPassword: boolean; enabled: boolean; eventCount: number; healthStatus: "ok" | "dead" | "unknown"; healthCheckedAt: string | null; healthError: string | null; subscriptionSummary: string | null; partnerName: string | null; overviewCheckedAt: string | null; }; type ViaplaySettings = { configured: boolean; enabled: boolean; accounts: ViaplayAccount[]; accountCount: number; eventCount: number; eventOwnerCount: number; lastSyncAt: string | null; lastSyncSummary: Record | null; lastPreLiveAt: string | null; lastPreLiveSummary: Record | null; wvdPath: string; wvdExists: boolean; tweaks: ViaplayTweaks; tweakDefaults: ViaplayTweaks; tweakMeta: TweakMeta[]; }; export type ViaplayOverview = { configured: boolean; enabled: boolean; accountCount: number; eventCount: number; lastSyncAt: string | null; }; export function ViaplayPanel({ onChanged }: { onChanged?: (o: ViaplayOverview) => void }) { const [settings, setSettings] = useState(null); const [enabled, setEnabled] = useState(true); const [tweaks, setTweaks] = useState({}); const [showTweaks, setShowTweaks] = useState(false); const [showCookies, setShowCookies] = useState(false); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(null); const [err, setErr] = useState(null); const [newLabel, setNewLabel] = useState(""); const [newEmail, setNewEmail] = useState(""); const [newSession, setNewSession] = useState(""); const [newAccessToken, setNewAccessToken] = useState(""); const [newProfileId, setNewProfileId] = useState(""); const applySettings = useCallback( (d: ViaplaySettings) => { setSettings(d); setEnabled(d.enabled !== false); setTweaks(d.tweaks ?? d.tweakDefaults ?? {}); onChanged?.({ configured: d.configured, enabled: d.enabled !== false, accountCount: d.accountCount, eventCount: d.eventCount, lastSyncAt: d.lastSyncAt, }); }, [onChanged] ); const load = useCallback(async () => { const r = await fetch("/api/v1/admin/scripts/viaplay", { credentials: "include" }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Viaplay-instellingen laden mislukt"); applySettings(d); }, [applySettings]); useEffect(() => { void load().catch((e) => setErr(String(e.message ?? e))); }, [load]); async function saveSettings(e: FormEvent) { e.preventDefault(); setBusy(true); setErr(null); setMsg(null); try { const r = await fetch("/api/v1/admin/scripts/viaplay", { method: "PUT", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled, tweaks }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Opslaan mislukt"); applySettings(d); setMsg("Viaplay-instellingen opgeslagen"); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } async function addAccount(e: FormEvent) { e.preventDefault(); setBusy(true); setErr(null); setMsg(null); try { const r = await fetch("/api/v1/admin/scripts/viaplay/accounts", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ label: newLabel.trim() || undefined, email: newEmail.trim(), session: newSession.trim() || undefined, accessToken: newAccessToken.trim() || undefined, profileId: newProfileId.trim() || undefined, enabled: true, }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Account toevoegen mislukt"); const emailKey = newEmail.trim().toLowerCase(); applySettings(d); setNewLabel(""); setNewEmail(""); setNewSession(""); setNewAccessToken(""); setNewProfileId(""); const added = (d.accounts as ViaplayAccount[] | undefined)?.find( (a) => a.email === emailKey ); const sub = added?.subscriptionSummary; setMsg( sub ? `Account toegevoegd · abonnement: ${sub}` : "Account toegevoegd — abonnement nog niet zichtbaar, druk Check" ); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } async function toggleAccount(account: ViaplayAccount) { setBusy(true); setErr(null); try { const r = await fetch(`/api/v1/admin/scripts/viaplay/accounts/${account.id}`, { method: "PATCH", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled: !account.enabled }), }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Account bijwerken mislukt"); applySettings(d); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } async function removeAccount(account: ViaplayAccount) { if (!confirm(`Account “${account.label || account.email}” verwijderen?`)) return; setBusy(true); setErr(null); setMsg(null); try { const r = await fetch(`/api/v1/admin/scripts/viaplay/accounts/${account.id}`, { method: "DELETE", credentials: "include", }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Verwijderen mislukt"); applySettings(d); setMsg("Account verwijderd"); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } async function probeAccounts(accountId?: string) { setBusy(true); setErr(null); setMsg(null); try { const url = accountId ? `/api/v1/admin/scripts/viaplay/accounts/${accountId}/probe` : "/api/v1/admin/scripts/viaplay/accounts/probe"; const r = await fetch(url, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(accountId ? { accountId } : {}), }); const d = await r.json().catch(() => ({})); if (!r.ok) { throw new Error( d?.error?.message || d?.message || `Statuscheck mislukt (${r.status})` ); } applySettings(d); const list: ViaplayAccount[] = d.accounts ?? []; const ok = list.filter((a) => a.healthStatus === "ok").length; const dead = list.filter((a) => a.healthStatus === "dead").length; const sample = list.find((a) => a.id === accountId) || list.find((a) => a.subscriptionSummary); const sub = sample?.subscriptionSummary ? ` · ${sample.subscriptionSummary}` : ""; setMsg(`Accountstatus: ${ok} actief · ${dead} probleem${sub}`); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } async function runSync() { setBusy(true); setErr(null); setMsg(null); try { const r = await fetch("/api/v1/admin/scripts/viaplay/sync", { method: "POST", credentials: "include", }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Sync mislukt"); setMsg(`Viaplay-agenda: ${d.events ?? 0} events · ${d.daysOk ?? 0}/${d.daysTried ?? 0} dagen (${d.durationMs ?? "?"} ms)`); await load(); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } async function runPreLive() { setBusy(true); setErr(null); setMsg(null); try { const r = await fetch("/api/v1/admin/scripts/viaplay/prelive", { method: "POST", credentials: "include", }); const d = await r.json(); if (!r.ok) throw new Error(d.error?.message ?? "Pre-live mislukt"); setMsg( `Pre-live: ${d.refreshed ?? 0} refreshed · ${d.failed ?? 0} failed · ${d.checked ?? 0} checked (${d.durationMs ?? "?"} ms)` ); await load(); } catch (e) { setErr(String((e as Error).message ?? e)); } finally { setBusy(false); } } function healthBadge(a: ViaplayAccount) { if (!a.enabled) { return ( uit ); } if (a.healthStatus === "ok") { return ( actief ); } if (a.healthStatus === "dead") { return ( probleem ); } return ( onbekend ); } function resetTweaks() { if (settings?.tweakDefaults) setTweaks({ ...settings.tweakDefaults }); } const accounts = settings?.accounts ?? []; const meta = settings?.tweakMeta ?? []; const summary = settings?.lastSyncSummary; return ( <> {msg && (

{msg}

)} {err && (

{err}

)}

Viaplay

Sportagenda en live streams voor gekoppelde Viaplay-accounts. Voeg een account toe met sessie-token, access token en optioneel profile-id (uit een ingelogde browsersessie). Agenda: max 3 dagen vooruit (vervangt Viaplay uit de externe events-feed). Manifests en licenties cache-first (±5 min vóór start).

Accounts

{accounts.length === 0 ? (

Nog geen accounts — voeg e-mail + sessie-tokens toe.

) : (
    {accounts.map((a) => (
  • {a.label || a.email} {healthBadge(a)} {a.email} {a.eventCount ? ` · ${a.eventCount} events` : ""}
    Abonnement: {a.subscriptionSummary ? ( {a.subscriptionSummary} ) : ( nog niet opgehaald — druk Check )}
    {a.partnerName && a.subscriptionSummary && !a.subscriptionSummary.includes(a.partnerName) ? (
    Partner: {a.partnerName}
    ) : null} {a.healthError ? (
    {a.healthError}
    ) : null}
  • ))}
)}
{showCookies && (