Add an artist listing page and improve SEO for various pages

Introduces a new `/kuenstler` page, API endpoint `/api/artists`, and enhances meta tags on `index.html` and for crawler requests on `/kuenstler`. Adds `KuenstlerPage.tsx` and updates `routes.ts` and `index.html`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 401e2ec0-e00d-4f10-9d0e-60f3d479f9a5
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 69ee4fdb-c617-4cdd-b9f6-d3fab268d533
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/60d372ff-2c10-46c7-b01b-10c3435136b0/401e2ec0-e00d-4f10-9d0e-60f3d479f9a5/qFrskyV
This commit is contained in:
sebastjanartic
2026-02-13 17:53:37 +00:00
parent 19acaec48c
commit 590584b893
5 changed files with 304 additions and 2 deletions
+162
View File
@@ -0,0 +1,162 @@
import { useQuery } from '@tanstack/react-query';
import { Link, useLocation } from 'wouter';
import { ArrowLeft, Music, Users, Search, Menu, X } from 'lucide-react';
import { useState, useEffect } from 'react';
import HeaderAd from '@/components/HeaderAd';
interface Artist {
name: string;
videoCount: number;
}
export default function KuenstlerPage() {
const [searchQuery, setSearchQuery] = useState("");
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [, setLocation] = useLocation();
useEffect(() => {
document.title = 'Alle Künstler & Interpreten | Folx TV - Video';
const metaDescription = document.querySelector('meta[name="description"]');
if (metaDescription) {
metaDescription.setAttribute('content', 'Entdecken Sie alle Volksmusik- und Schlager-Künstler auf Folx TV. Nummer 1 in Europa für Volksmusik und Schlager.');
}
}, []);
const { data, isLoading } = useQuery<{ artists: Artist[], total: number }>({
queryKey: ['/api/artists'],
});
const artists = data?.artists || [];
const filteredArtists = searchQuery
? artists.filter(a => a.name.toLowerCase().includes(searchQuery.toLowerCase()))
: artists;
const letters = Array.from(new Set(filteredArtists.map(a => a.name.charAt(0).toUpperCase()))).sort((a, b) => a.localeCompare(b, 'de'));
if (isLoading) {
return (
<div className="min-h-screen bg-gradient-to-b from-gray-900 to-black text-white flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-500"></div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-gray-900 to-black text-white">
<header className="bg-black/80 backdrop-blur-md border-b border-purple-500/30 sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/">
<span className="text-2xl font-bold bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent cursor-pointer">
Folx TV
</span>
</Link>
</div>
<nav className="hidden md:flex items-center gap-6">
<Link href="/" className="text-gray-300 hover:text-white transition-colors">Startseite</Link>
<Link href="/folx-stadl" className="text-gray-300 hover:text-white transition-colors">FOLX STADL</Link>
<Link href="/geschichte-lied" className="text-gray-300 hover:text-white transition-colors">Geschichte des Liedes</Link>
<Link href="/gipfelstammtisch" className="text-gray-300 hover:text-white transition-colors">Gipfelstammtisch</Link>
<Link href="/kuenstler" className="text-purple-400 font-semibold">Künstler</Link>
<Link href="/live" className="text-red-400 hover:text-red-300 transition-colors flex items-center gap-1">
<span className="w-2 h-2 bg-red-500 rounded-full animate-pulse"></span> LIVE
</Link>
</nav>
<button className="md:hidden text-white" onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}>
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
</button>
</div>
{isMobileMenuOpen && (
<nav className="md:hidden bg-black/95 border-t border-purple-500/20 py-4 px-4 space-y-3">
<Link href="/" className="block text-gray-300 hover:text-white py-2">Startseite</Link>
<Link href="/folx-stadl" className="block text-gray-300 hover:text-white py-2">FOLX STADL</Link>
<Link href="/geschichte-lied" className="block text-gray-300 hover:text-white py-2">Geschichte des Liedes</Link>
<Link href="/gipfelstammtisch" className="block text-gray-300 hover:text-white py-2">Gipfelstammtisch</Link>
<Link href="/kuenstler" className="block text-purple-400 font-semibold py-2">Künstler</Link>
<Link href="/live" className="block text-red-400 py-2">LIVE</Link>
</nav>
)}
</header>
<HeaderAd />
<main className="max-w-7xl mx-auto px-4 py-8">
<div className="flex items-center gap-3 mb-6">
<Link href="/">
<ArrowLeft className="text-gray-400 hover:text-white cursor-pointer" size={24} />
</Link>
<Users className="text-purple-400" size={28} />
<h1 className="text-3xl font-bold">Alle Künstler & Interpreten</h1>
<span className="bg-purple-500/20 text-purple-300 px-3 py-1 rounded-full text-sm">
{artists.length} Künstler
</span>
</div>
<div className="relative mb-8 max-w-md">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
<input
type="text"
placeholder="Künstler suchen..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:border-purple-500"
/>
</div>
<div className="flex flex-wrap gap-2 mb-8">
{letters.map(letter => (
<a
key={letter}
href={`#letter-${letter}`}
className="w-9 h-9 flex items-center justify-center bg-gray-800 hover:bg-purple-600 rounded-lg text-sm font-bold transition-colors"
>
{letter}
</a>
))}
</div>
{letters.map(letter => {
const letterArtists = filteredArtists.filter(a => a.name.charAt(0).toUpperCase() === letter);
return (
<div key={letter} id={`letter-${letter}`} className="mb-8">
<h2 className="text-2xl font-bold text-purple-400 mb-4 border-b border-purple-500/30 pb-2">{letter}</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{letterArtists.map(artist => (
<Link
key={artist.name}
href={`/?search=${encodeURIComponent(artist.name)}`}
className="flex items-center gap-3 p-3 bg-gray-800/40 hover:bg-gray-700/60 rounded-lg transition-colors group"
>
<div className="w-10 h-10 bg-purple-500/20 rounded-full flex items-center justify-center group-hover:bg-purple-500/40 transition-colors">
<Music size={18} className="text-purple-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-white font-medium truncate">{artist.name}</p>
<p className="text-gray-400 text-sm">{artist.videoCount} Video{artist.videoCount > 1 ? 's' : ''}</p>
</div>
</Link>
))}
</div>
</div>
);
})}
{filteredArtists.length === 0 && (
<div className="text-center py-16 text-gray-400">
<Users size={48} className="mx-auto mb-4 opacity-50" />
<p className="text-lg">Keine Künstler gefunden für "{searchQuery}"</p>
</div>
)}
</main>
<footer className="bg-black/80 border-t border-gray-800 py-6 mt-12">
<div className="max-w-7xl mx-auto px-4 text-center text-gray-500 text-sm">
<p>Folx TV - Nummer 1 in Europa für Volksmusik und Schlager</p>
</div>
</footer>
</div>
);
}
+7
View File
@@ -56,6 +56,13 @@ export default function Home() {
if (metaDescription) {
metaDescription.setAttribute('content', 'Folx TV - Nummer 1 in Europa für Volksmusik und Schlager. Video Streaming Plattform mit Musik, Unterhaltung und Live-Streaming.');
}
let metaKeywords = document.querySelector('meta[name="keywords"]');
if (!metaKeywords) {
metaKeywords = document.createElement('meta');
metaKeywords.setAttribute('name', 'keywords');
document.head.appendChild(metaKeywords);
}
metaKeywords.setAttribute('content', 'Volksmusik, Schlager, Folx TV, Kastelruther Spatzen, Monika Martin, Oswald Sattler, Angela Wiedl, Die Grubertaler, Michael Hirte, Sašo Avsenik, Jonny Hill, Rosanna Rocci, Linda Feller, Julia Buchner, Melanie Payer, Marlena Martinelli, Musikvideo, Streaming');
}, []);
// Update videos when new data comes in