Add video playback functionality and integrate external video content

Implement video playback routes and pages, integrating with an external video API to display video content with details such as title, duration, and upload date.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 413891e8-d784-4bea-b9f5-91a5a68316b4
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ffa8d9a0-d7a9-404b-ba12-191309252a52
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/413891e8-d784-4bea-b9f5-91a5a68316b4/igVW4lQ
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sebastjanartic 2026-02-28 17:46:33 +00:00
parent 355f494d38
commit d2f32b1e14
5 changed files with 108 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -7,6 +7,8 @@ import NotFound from "@/pages/not-found";
import Home from "@/pages/home"; import Home from "@/pages/home";
import ArticlePage from "@/pages/article"; import ArticlePage from "@/pages/article";
import CategoryPage from "@/pages/category"; import CategoryPage from "@/pages/category";
import VideosPage from "@/pages/videos";
import VideoPage from "@/pages/video";
function Router() { function Router() {
return ( return (
@ -14,6 +16,8 @@ function Router() {
<Route path="/" component={Home} /> <Route path="/" component={Home} />
<Route path="/article/:slug" component={ArticlePage} /> <Route path="/article/:slug" component={ArticlePage} />
<Route path="/category/:category" component={CategoryPage} /> <Route path="/category/:category" component={CategoryPage} />
<Route path="/videos" component={VideosPage} />
<Route path="/video/:guid" component={VideoPage} />
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
); );

104
client/src/pages/video.tsx Normal file
View File

@ -0,0 +1,104 @@
import { useQuery } from "@tanstack/react-query";
import { useParams, Link } from "wouter";
import { format } from "date-fns";
import { de } from "date-fns/locale";
import { ArrowLeft, Clock } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import Header from "@/components/header";
import Footer from "@/components/footer";
import { useEffect } from "react";
interface BunnyVideo {
guid: string;
title: string;
length: number;
dateUploaded: string;
thumbnail: string;
embedUrl: string;
}
function formatDuration(seconds: number): string {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}:${s.toString().padStart(2, "0")}`;
}
export default function VideoPage() {
const { guid } = useParams<{ guid: string }>();
const { data: video, isLoading } = useQuery<BunnyVideo>({
queryKey: ["/api/videos", guid],
});
useEffect(() => {
window.scrollTo(0, 0);
}, [guid]);
if (isLoading) {
return (
<div className="min-h-screen bg-background">
<Header />
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Skeleton className="h-8 w-32 mb-6" />
<Skeleton className="w-full aspect-video rounded-md mb-6" />
<Skeleton className="h-8 w-2/3 mb-4" />
<Skeleton className="h-4 w-1/3" />
</div>
</div>
);
}
if (!video) {
return (
<div className="min-h-screen bg-background">
<Header />
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-16 text-center">
<p className="text-muted-foreground" data-testid="text-video-not-found">Video nicht gefunden.</p>
<Link href="/videos">
<Button className="mt-4" data-testid="button-back-videos">Zurück zu Videos</Button>
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-background">
<Header />
<main className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<Link href="/videos">
<button className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors mb-6 text-sm" data-testid="button-back">
<ArrowLeft className="w-4 h-4" />
Zurück
</button>
</Link>
<div className="w-full aspect-video rounded-md overflow-hidden mb-6" data-testid="video-player">
<iframe
src={video.embedUrl}
className="w-full h-full border-0"
allow="accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;"
allowFullScreen
loading="lazy"
/>
</div>
<h1 className="text-2xl md:text-3xl font-bold text-foreground mb-3" data-testid="text-video-title">
{video.title}
</h1>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span className="flex items-center gap-1">
<Clock className="w-4 h-4" />
{formatDuration(video.length)}
</span>
<span>
{format(new Date(video.dateUploaded), "d. MMMM yyyy", { locale: de })}
</span>
</div>
</main>
<Footer />
</div>
);
}