add recent vods and clips to home page
All checks were successful
CI / Maven unit tests (push) Successful in 17s
CI / Build and push images (push) Has been skipped
CI / Deploy to server (push) Has been skipped

This commit is contained in:
2026-08-02 15:17:06 +01:00
parent 0b1ea8e089
commit 11809a7da8
5 changed files with 115 additions and 16 deletions

View File

@@ -75,9 +75,14 @@ const Sidebar = ({ className }: props) => {
alt={"Profile Picture"}
/>
<label className={"font-body text-text-strong text-sm"}>
<div className={"flex flex-col"}>
<p className={"font-body text-text-strong text-sm"}>
{user.name}
</label>
</p>
<p className={"text-[0.625rem] font-body text-muted wrap-break-word"}>
Signed in with Google
</p>
</div>
<button
className={"font-body text-muted text-xs hover:bg-hover p-1 pt-1.5 rounded"}

View File

@@ -0,0 +1,14 @@
import React from "react";
type Props = React.ButtonHTMLAttributes<HTMLButtonElement>;
const DarkButton: React.FC<Props> = ({ className = "", ...props }) => {
return (
<button
className={`bg-accent text-on-dark font-body rounded-lg hover:bg-accent-hover transition-colors duration-150 h-10 px-4 ${className}`}
{...props}
/>
);
};
export default DarkButton;

View File

@@ -1,7 +1,7 @@
import clsx from "clsx";
import { formatTime, stringToDate, dateToTimeAgo } from "../../utils/utils.ts";
import { Link } from "react-router-dom";
import { useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { isThumbnailAvailable } from "../../utils/api/client.ts";
import { MoreVertical, Pencil, Trash2 } from "lucide-react";

View File

@@ -24,6 +24,7 @@
--color-hover: #E7E0D2;
--color-hairline: #F0EBE2;
--color-live-red: #B5432E;
--color-on-dark: #FBF8F2;
}
:root {

View File

@@ -1,11 +1,16 @@
import {useAuth} from "../utils/AuthContext.tsx";
import NoAccessPage from "../components/NoAccessPage.tsx";
import {Link} from "react-router-dom";
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 {StreamStatus} from "../utils/types.ts";
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 {getMarkers} from "../utils/api/markers.ts";
import {getVods} from "../utils/api/vods.ts";
import {getClips} from "../utils/api/clips.ts";
import DarkButton from "../components/buttons/DarkButton.tsx";
type TimeOfDay = "morning" | "afternoon" | "evening" | "night";
@@ -71,19 +76,50 @@ const Home = () => {
const { user } = useAuth();
const [ streamStatus, setStreamStatus ] = useState<StreamStatus | null>(null)
const [ greeting ] = useState<string>(getGreetingSubtext(new Date()));
const [ elapsed, setElapsed ] = useState('00:00:00');
const [ markers, setMarkers ] = useState<Marker[]>([])
const [ clips, setClips ] = useState<Clip[]>([]);
const [ vods, setVods ] = useState<Vod[]>([]);
const [ greeting ] = useState<string>(getGreetingSubtext(new Date()));
const navigate = useNavigate();
// Fetch Markers & Stream information
useEffect(() => {
getCurrentStream()
.then(streamStatus => {
setStreamStatus(streamStatus)
// ---- FOR TESTING ONLY, REMOVE AT THE END -----
streamStatus.isStreaming = true;
streamStatus.id = 49;
streamStatus.startDate = "2026-07-30T14:23:01.123456Z"
const stream: StreamStatus = {
isStreaming : true,
id : 49,
startDate : "2026-07-30T18:23:01.123456Z"
}
setStreamStatus(stream);
})
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);
})
// 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);
})
}, []);
@@ -114,7 +150,7 @@ const Home = () => {
return (
<div className={"w-10/12 m-auto pt-10"}>
<div className={"flex flex-row justify-between items-start pb-10"}>
<div className={"flex flex-row justify-between items-start pb-8"}>
<div>
<p className={"font-data text-faint-mono text-sm tracking-widest"}>{formatted.toLocaleUpperCase()}</p>
<h1 className={"font-heading font text-5xl text-text-strong tracking-[-0.015em]"}>{greeting}</h1>
@@ -126,7 +162,7 @@ const Home = () => {
</div>
{streamStatus?.isStreaming &&
<Card className={"bg-live-card p-8"}>
<Card className={"bg-live-card p-8 mb-8"}>
<div className={"flex flex-row items-center gap-2"}>
<IsLivePulse />
<p className={"text-xs font-data text-live-red"}>LIVE NOW</p>
@@ -134,21 +170,64 @@ const Home = () => {
<div className={"p-1"}></div>
<h2 className={"font-heading text-2xl"}>
<h2 className={"font-heading text-3xl"}>
Session {String(streamStatus.id).padStart(5, '0')} {' '}
recording since {getTimeFromISOString(streamStatus.startDate)}.</h2>
<div className={"flex flex-row items-center gap-2 pt-4"}>
<div className={"flex flex-row items-center gap-10 pt-4"}>
<div className={"flex flex-col"}>
<h2 className={"font-data text-xl"}>{elapsed}</h2>
<p className={"text-xs text-muted"}>elapsed</p>
</div>
<div className={"flex flex-col"}>
<h2 className={"font-data text-xl"}>{markers.length}</h2>
<p className={"text-xs text-muted"}>markers dropped</p>
</div>
</div>
<div className={"mt-4"}>
<DarkButton>+ Drop Marker</DarkButton>
</div>
</Card>
}
<div className={"pt-10"}>
<div className={"flex flex-col"}>
<h2 className={"font-heading text-2xl"}>Recent VoDs</h2>
<div className={"grid grid-cols-3 gap-4"}>
{vods.map((vod) => (
<VideoCard
id={vod.id}
title={vod.title}
duration={vod.duration}
createdAt={vod.createdAt}
mediaApiPath="/api/v1/vods"
playerPath="/vod"
itemLabel="vod"
onEdit={() => navigate(`/vods/${vod.id}/edit`)}
/>
))}
</div>
</div>
<div className={"flex flex-col mt-4"}>
<h2 className={"font-heading text-2xl"}>Recent Clips</h2>
<div className={"grid grid-cols-4 gap-4"}>
{clips.map((clip) => (
<VideoCard
id={clip.id}
title={clip.title}
duration={clip.duration}
createdAt={clip.createdAt}
mediaApiPath="/api/v1/clips"
playerPath="/clip"
itemLabel="clip"
onEdit={() => navigate(`/clip/${clip.id}/edit`)}
/>
))}
</div>
</div>
</div>