From 5d519571ca0259bc66e72fe098532932ab697f41 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Tue, 11 Nov 2025 23:10:14 +0000 Subject: [PATCH] Ethos track library - Browse, filter, and search all tracks cgen-e059fffdf88546e4902b3273507b65af --- client/pages/ethos/ArtistProfile.tsx | 263 +++++++++++++++++++++++ client/pages/ethos/TrackLibrary.tsx | 299 +++++++++++++++++++++++++++ 2 files changed, 562 insertions(+) create mode 100644 client/pages/ethos/ArtistProfile.tsx create mode 100644 client/pages/ethos/TrackLibrary.tsx diff --git a/client/pages/ethos/ArtistProfile.tsx b/client/pages/ethos/ArtistProfile.tsx new file mode 100644 index 00000000..85f86395 --- /dev/null +++ b/client/pages/ethos/ArtistProfile.tsx @@ -0,0 +1,263 @@ +import { useEffect, useState } from "react"; +import { useParams, Link } from "react-router-dom"; +import Layout from "@/components/Layout"; +import SEO from "@/components/SEO"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Star, Mail, Music, Zap, Clock } from "lucide-react"; + +interface Artist { + user_id: string; + skills: string[]; + for_hire: boolean; + bio?: string; + portfolio_url?: string; + sample_price_track?: number; + sample_price_sfx?: number; + sample_price_score?: number; + turnaround_days?: number; + verified: boolean; + total_downloads: number; + created_at: string; + user_profiles: { + id: string; + full_name: string; + avatar_url?: string; + email?: string; + }; + tracks: Array<{ + id: string; + title: string; + genre: string[]; + download_count: number; + }>; +} + +export default function ArtistProfile() { + const { userId } = useParams<{ userId: string }>(); + const [artist, setArtist] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchArtist = async () => { + if (!userId) return; + + try { + const res = await fetch(`/api/ethos/artists?id=${userId}`); + const data = await res.json(); + setArtist(data); + } catch (error) { + console.error("Failed to fetch artist:", error); + } finally { + setLoading(false); + } + }; + + fetchArtist(); + }, [userId]); + + if (loading) { + return
Loading artist profile...
; + } + + if (!artist) { + return
Artist not found
; + } + + const memberSince = new Date(artist.created_at).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + }); + + return ( + <> + + +
+ {/* Profile Header */} +
+
+
+ + + + {artist.user_profiles.full_name.charAt(0)} + + + +
+
+

+ {artist.user_profiles.full_name} +

+ {artist.verified && ( + + ✓ Verified Artist + + )} +
+ + {artist.bio && ( +

{artist.bio}

+ )} + +
+
+

Total Downloads

+

+ {artist.total_downloads} +

+
+
+

Tracks Published

+

+ {artist.tracks.length} +

+
+
+

Member Since

+

{memberSince}

+
+
+ + {artist.for_hire && ( + + )} +
+
+ + {/* Skills & Services */} +
+ {artist.skills.length > 0 && ( + + + + + Skills + + + +
+ {artist.skills.map((skill) => ( + + {skill} + + ))} +
+
+
+ )} + + {artist.for_hire && ( + + + + + Services + + + + {artist.sample_price_track && ( +
+ Custom Track + + ${artist.sample_price_track} + +
+ )} + {artist.sample_price_sfx && ( +
+ SFX Pack + + ${artist.sample_price_sfx} + +
+ )} + {artist.sample_price_score && ( +
+ Full Score + + ${artist.sample_price_score} + +
+ )} + {artist.turnaround_days && ( +
+ + {artist.turnaround_days} day turnaround +
+ )} +
+
+ )} +
+
+
+ + {/* Portfolio */} +
+
+

Portfolio

+ + {artist.tracks.length === 0 ? ( + + + No tracks published yet + + + ) : ( +
+ {artist.tracks.map((track) => ( + + +
+

{track.title}

+
+ {track.genre.map((g) => ( + + {g} + + ))} +
+
+
+ + {track.download_count} downloads +
+
+
+ ))} +
+ )} +
+
+
+
+ + ); +} diff --git a/client/pages/ethos/TrackLibrary.tsx b/client/pages/ethos/TrackLibrary.tsx new file mode 100644 index 00000000..b4392189 --- /dev/null +++ b/client/pages/ethos/TrackLibrary.tsx @@ -0,0 +1,299 @@ +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import Layout from "@/components/Layout"; +import SEO from "@/components/SEO"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Music, Download, Radio, Search, Filter } from "lucide-react"; + +interface Track { + id: string; + user_id: string; + title: string; + description?: string; + genre: string[]; + license_type: string; + duration_seconds?: number; + download_count: number; + created_at: string; + user_profiles?: { + id: string; + full_name: string; + avatar_url?: string; + }; +} + +const GENRES = [ + "All Genres", + "Synthwave", + "Orchestral", + "SFX", + "Ambient", + "Electronic", + "Cinematic", + "Jazz", + "Hip-Hop", + "Folk", +]; + +export default function TrackLibrary() { + const [tracks, setTracks] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedGenre, setSelectedGenre] = useState("All Genres"); + const [licenseFilter, setLicenseFilter] = useState("all"); + const [sortBy, setSortBy] = useState("newest"); + + useEffect(() => { + const fetchTracks = async () => { + try { + const params = new URLSearchParams(); + params.append("limit", "100"); + + if (searchQuery) params.append("search", searchQuery); + if (selectedGenre !== "All Genres") params.append("genre", selectedGenre); + if (licenseFilter !== "all") params.append("licenseType", licenseFilter); + + const res = await fetch(`/api/ethos/tracks?${params}`); + const { data } = await res.json(); + + let sorted = [...data]; + if (sortBy === "newest") { + sorted.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + } else if (sortBy === "popular") { + sorted.sort((a, b) => b.download_count - a.download_count); + } + + setTracks(sorted); + } catch (error) { + console.error("Failed to fetch tracks:", error); + } finally { + setLoading(false); + } + }; + + const timer = setTimeout(fetchTracks, 300); + return () => clearTimeout(timer); + }, [searchQuery, selectedGenre, licenseFilter, sortBy]); + + const formatDuration = (seconds?: number) => { + if (!seconds) return "—"; + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toString().padStart(2, "0")}`; + }; + + return ( + <> + + +
+ {/* Hero Section */} +
+
+
+
+ + + Ethos Track Library + +

+ Discover Ethos Music & SFX +

+

+ Browse original music and sound effects created by Ethos Guild artists. + Use freely in your projects or license commercially. +

+
+ + {/* Search & Filters */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-12 bg-slate-800 border-slate-700 h-11" + /> +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+
+
+ + {/* Tracks Grid */} +
+
+ {loading ? ( +
+
+ +
+

Loading tracks...

+
+ ) : tracks.length === 0 ? ( +
+ +

No tracks found. Try adjusting your filters.

+
+ ) : ( +
+ {tracks.map((track) => ( + + +
+
+
+
+

+ {track.title} +

+ {track.user_profiles && ( + + {track.user_profiles.full_name} + + )} +
+ + {track.license_type === "ecosystem" ? "Free" : "Commercial"} + +
+ +
+ {track.genre.map((g) => ( + + {g} + + ))} +
+ + {track.description && ( +

+ {track.description} +

+ )} +
+ +
+
+ + {track.download_count} +
+ {track.duration_seconds && ( +
+ + {formatDuration(track.duration_seconds)} +
+ )} +
+ + +
+
+
+ ))} +
+ )} +
+
+
+
+ + ); +}