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:
parent
355f494d38
commit
d2f32b1e14
BIN
client/public/uploads/oberkrainer-geschichte-thumb.webp
Normal file
BIN
client/public/uploads/oberkrainer-geschichte-thumb.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
client/public/uploads/oberkrainer-geschichte.jpg
Normal file
BIN
client/public/uploads/oberkrainer-geschichte.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
BIN
client/public/uploads/oberkrainer-geschichte.webp
Normal file
BIN
client/public/uploads/oberkrainer-geschichte.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@ -7,6 +7,8 @@ import NotFound from "@/pages/not-found";
|
||||
import Home from "@/pages/home";
|
||||
import ArticlePage from "@/pages/article";
|
||||
import CategoryPage from "@/pages/category";
|
||||
import VideosPage from "@/pages/videos";
|
||||
import VideoPage from "@/pages/video";
|
||||
|
||||
function Router() {
|
||||
return (
|
||||
@ -14,6 +16,8 @@ function Router() {
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/article/:slug" component={ArticlePage} />
|
||||
<Route path="/category/:category" component={CategoryPage} />
|
||||
<Route path="/videos" component={VideosPage} />
|
||||
<Route path="/video/:guid" component={VideoPage} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
104
client/src/pages/video.tsx
Normal file
104
client/src/pages/video.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user