diff --git a/client/components/nexus/AudioServicesForHire.tsx b/client/components/nexus/AudioServicesForHire.tsx new file mode 100644 index 00000000..916429ac --- /dev/null +++ b/client/components/nexus/AudioServicesForHire.tsx @@ -0,0 +1,312 @@ +import { useState, useEffect } from "react"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Search, Star, Clock, DollarSign, CheckCircle, Music } from "lucide-react"; +import { useNavigate } from "react-router-dom"; + +interface ArtistService { + id: string; + user_id: string; + full_name: string; + avatar_url?: string; + bio?: string; + skills: string[]; + verified: boolean; + rating: number; + for_hire: boolean; + price_list: { + track_custom?: number; + sfx_pack?: number; + full_score?: number; + day_rate?: number; + contact_for_quote?: boolean; + }; + turnaround_days?: number; +} + +const SERVICE_TYPES = [ + { value: "track_custom", label: "Custom Track" }, + { value: "sfx_pack", label: "SFX Pack" }, + { value: "full_score", label: "Full Game Score" }, + { value: "day_rate", label: "Day Rate Consulting" }, +]; + +export default function AudioServicesForHire() { + const navigate = useNavigate(); + const [artists, setArtists] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedSkill, setSelectedSkill] = useState(null); + const [selectedService, setSelectedService] = useState(null); + const [minRating, setMinRating] = useState(0); + + useEffect(() => { + const fetchArtists = async () => { + try { + setLoading(true); + // Fetch artists who are for_hire + const response = await fetch(`/api/ethos/artists?for_hire=true&limit=20`); + if (response.ok) { + const data = await response.json(); + setArtists(Array.isArray(data) ? data : []); + } + } catch (error) { + console.error("Failed to fetch artists:", error); + } finally { + setLoading(false); + } + }; + + const timer = setTimeout(fetchArtists, 300); + return () => clearTimeout(timer); + }, []); + + // Filter and sort artists + const filteredArtists = artists.filter((artist) => { + const matchesSearch = + !searchQuery || + artist.full_name.toLowerCase().includes(searchQuery.toLowerCase()) || + artist.bio?.toLowerCase().includes(searchQuery.toLowerCase()); + + const matchesSkill = + !selectedSkill || + artist.skills.some((skill) => + skill.toLowerCase().includes(selectedSkill.toLowerCase()) + ); + + const matchesService = + !selectedService || + (artist.price_list as Record)[selectedService] !== null; + + const matchesRating = artist.rating >= minRating; + + return matchesSearch && matchesSkill && matchesService && matchesRating; + }); + + // Get all unique skills from artists + const allSkills = Array.from( + new Set(artists.flatMap((artist) => artist.skills)) + ).sort(); + + return ( +
+ {/* Search & Filters */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-10 bg-slate-800 border-slate-700" + /> +
+ +
+ + + + + + + +
+
+ + {/* Results Count */} +
+ {loading ? "Loading..." : `${filteredArtists.length} artist${filteredArtists.length !== 1 ? "s" : ""} available`} +
+ + {/* Artists Grid */} + {loading ? ( +
Loading artists...
+ ) : filteredArtists.length === 0 ? ( +
+ No artists found matching your criteria. Try adjusting your filters. +
+ ) : ( +
+ {filteredArtists.map((artist) => ( + + +
+ {artist.avatar_url && ( + {artist.full_name} + )} +
+ + {artist.full_name} + {artist.verified && ( + + )} + +
+ + {artist.rating.toFixed(1)} +
+
+
+
+ + {/* Bio */} + {artist.bio && ( +

{artist.bio}

+ )} + + {/* Skills */} +
+

Skills

+
+ {artist.skills.slice(0, 3).map((skill) => ( + + {skill} + + ))} + {artist.skills.length > 3 && ( + + +{artist.skills.length - 3} + + )} +
+
+ + {/* Service Pricing */} +
+

Services

+
+ {artist.price_list.track_custom && ( +
+ Custom Track + + ${artist.price_list.track_custom} + +
+ )} + {artist.price_list.sfx_pack && ( +
+ SFX Pack + + ${artist.price_list.sfx_pack} + +
+ )} + {artist.price_list.full_score && ( +
+ Full Score + + ${artist.price_list.full_score} + +
+ )} + {artist.price_list.day_rate && ( +
+ Day Rate + + ${artist.price_list.day_rate}/day + +
+ )} + {artist.price_list.contact_for_quote && ( +
+ Enterprise rates available - contact for quote +
+ )} +
+
+ + {/* Turnaround */} + {artist.turnaround_days && ( +
+ + {artist.turnaround_days} day turnaround +
+ )} + + {/* CTA Buttons */} +
+ + +
+
+
+ ))} +
+ )} +
+ ); +}