import {useAuth} from "../utils/AuthContext.tsx"; import Landing from "./Landing.tsx"; import {Link, useNavigate} from "react-router-dom"; import Card from "../components/Card.tsx"; import {useEffect, useState} from "react"; import IsLivePulse from "../components/IsLivePulse.tsx"; import type {Clip, Marker, StreamStatus, Vod} from "../utils/types.ts"; import {getCurrentStream} from "../utils/api/stream.ts"; import VideoCard from "../components/video/VideoCard.tsx"; import {createMarker, getMarkers} from "../utils/api/markers.ts"; import clsx from "clsx"; import {getVods} from "../utils/api/vods.ts"; import {getClips} from "../utils/api/clips.ts"; import Button from "../components/buttons/Button.tsx"; type TimeOfDay = "morning" | "afternoon" | "evening" | "night"; function getTimeOfDay(date: Date = new Date()): TimeOfDay { const hour = date.getHours(); if (hour >= 5 && hour < 12) return "morning"; if (hour >= 12 && hour < 17) return "afternoon"; if (hour >= 17 && hour < 22) return "evening"; return "night"; } function getGreetingSubtext(date: Date = new Date()): string { const timeOfDay = getTimeOfDay(date); const messages: Record = { morning: [ "Good morning.", "Coffee first, then clips.", "Here's what's waiting for you.", ], afternoon: [ "Good afternoon.", "A quiet moment for your clips.", "Catching up?", ], evening: [ "Good evening.", "Settle in.", "Time to unwind.", ], night: [ "Still up?", "Quiet night for it.", "Good night, when you're ready.", ] } const options = messages[timeOfDay]; return options[Math.floor(Math.random() * options.length)]; } function getTimeFromISOString(date: string | null): string { if (date == null) { return "--:--"; } const d = new Date(date); return new Intl.DateTimeFormat('en-GB', { hour: '2-digit', minute: '2-digit', hour12: false }).format(d); } type SectionHeaderProps = { title: string; to: string; count: number }; const SectionHeader = ({ title, to, count }: SectionHeaderProps) => (

{title}

{count > 0 && ( All {count} → )}
); const Home = () => { const now = new Date(); const formatted: string = new Intl.DateTimeFormat('en-IE', { weekday: 'long', day: 'numeric', month: 'long', }).format(now); const { user } = useAuth(); const [ streamStatus, setStreamStatus ] = useState(null) const [ elapsed, setElapsed ] = useState('00:00:00'); const [ markers, setMarkers ] = useState([]) const [ clips, setClips ] = useState([]); const [ vods, setVods ] = useState([]); const [ counts, setCounts ] = useState({ vods: 0, clips: 0 }); const [ markerBusy, setMarkerBusy ] = useState(false); const [ markerNote, setMarkerNote ] = useState(null); const isStreaming = streamStatus?.isStreaming ?? false; const [ greeting ] = useState(getGreetingSubtext(new Date())); const navigate = useNavigate(); // Fetch Markers & Stream information useEffect(() => { getCurrentStream() .then(setStreamStatus) getMarkers() .then(data => { setMarkers(data); }) // Get Top 4 Clips getClips() .then(data => { const topSix = [...data] .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, 4); setClips(topSix); setCounts(prev => ({ ...prev, clips: data.length })); }) // Get top 3 vods getVods() .then(data => { const topSix = [...data] .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, 3); setVods(topSix); setCounts(prev => ({ ...prev, vods: data.length })); }) }, []); const handleDropMarker = async () => { if (markerBusy) return; setMarkerBusy(true); try { await createMarker(`Marker at ${elapsed}`); setMarkers(await getMarkers()); setMarkerNote(`dropped at ${elapsed}`); } catch (err) { setMarkerNote(err instanceof Error ? err.message : "Couldn't drop that marker"); } finally { setMarkerBusy(false); } }; useEffect(() => { if (streamStatus?.startDate == null) { setElapsed('00:00:00'); return; } const startMs = new Date(streamStatus?.startDate).getTime(); const tick = () => { const diffSec = Math.floor((Date.now() - startMs) / 1000); const hh = String(Math.floor(diffSec / 3600)).padStart(2, '0'); const mm = String(Math.floor((diffSec % 3600) / 60)).padStart(2, '0'); const ss = String(diffSec % 60).padStart(2, '0'); setElapsed(`${hh}:${mm}:${ss}`); }; tick(); const interval = setInterval(tick, 1000); return () => clearInterval(interval); }, [streamStatus?.startDate]); if (user == null) { return () } return (

{formatted.toLocaleUpperCase()}

{greeting}

Open Stream Panel
{/* Live status — always present, so the page has the same shape either way. */}
{isStreaming ? : }

{isStreaming ? "LIVE NOW" : "OFF AIR"}

{isStreaming ? ( <>

Session {String(streamStatus?.id).padStart(5, '0')} {' '} recording since {getTimeFromISOString(streamStatus?.startDate ?? null)}.

{elapsed}

elapsed

{markers.length}

markers dropped

{markerNote &&

{markerNote}

}
) : ( <>

Nothing is streaming right now.

Point OBS at the ingest URL with your stream key and this block turns over to elapsed time and markers.

{counts.vods}

vods saved

{counts.clips}

clips cut

)}
{vods.map((vod) => ( navigate(`/vods/${vod.id}/edit`)} /> ))}
{clips.map((clip) => ( navigate(`/clip/${clip.id}/edit`)} /> ))}
); }; export default Home;