"use client"; import { useState, useEffect } from "react"; import { api, MoodType, MoodBucketPreset } from "@/lib/api"; import { useAudioControls } from "@/lib/audio-controls-context"; import { Track } from "@/lib/audio-state-context"; import { useQueryClient } from "@tanstack/react-query"; import { Play, Loader2, AudioWaveform, X, Smile, Frown, Coffee, Zap, PartyPopper, Brain, CloudRain, Flame, Guitar, } from "lucide-react"; import { toast } from "sonner"; interface MoodMixerProps { isOpen: boolean; onClose: () => void; } // Mood configuration with icons and colors const MOOD_CONFIG: Record< MoodType, { icon: React.ComponentType<{ className?: string }>; color: string; label: string; description: string; } > = { happy: { icon: Smile, color: "from-yellow-500 to-orange-500", label: "Happy", description: "Uplifting & joyful", }, sad: { icon: Frown, color: "from-blue-600 to-indigo-700", label: "Sad", description: "Melancholic & emotional", }, chill: { icon: Coffee, color: "from-teal-500 to-cyan-600", label: "Chill", description: "Relaxed & mellow", }, energetic: { icon: Zap, color: "from-orange-500 to-red-500", label: "Energetic", description: "High energy & pumped", }, party: { icon: PartyPopper, color: "from-pink-500 to-purple-600", label: "Party", description: "Dance & celebrate", }, focus: { icon: Brain, color: "from-emerald-500 to-green-600", label: "Focus", description: "Concentration & flow", }, melancholy: { icon: CloudRain, color: "from-slate-500 to-gray-600", label: "Melancholy", description: "Bittersweet & reflective", }, aggressive: { icon: Flame, color: "from-red-600 to-rose-700", label: "Aggressive", description: "Intense & powerful", }, acoustic: { icon: Guitar, color: "from-amber-600 to-yellow-700", label: "Acoustic", description: "Organic & unplugged", }, }; // Order for display in 3x3 grid const MOOD_ORDER: MoodType[] = [ "happy", "energetic", "party", "chill", "focus", "acoustic", "melancholy", "sad", "aggressive", ]; export function MoodMixer({ isOpen, onClose }: MoodMixerProps) { const { playTracks } = useAudioControls(); const queryClient = useQueryClient(); const [presets, setPresets] = useState([]); const [loading, setLoading] = useState(true); const [generating, setGenerating] = useState(null); const [isVisible, setIsVisible] = useState(false); // Handle visibility animation useEffect(() => { if (isOpen) { setIsVisible(true); loadPresets(); } else { // Delay hiding to allow exit animation const timeout = setTimeout(() => setIsVisible(false), 200); return () => clearTimeout(timeout); } }, [isOpen]); const loadPresets = async () => { try { const data = await api.getMoodBucketPresets(); setPresets(data); } catch (error) { console.error("Failed to load mood presets:", error); toast.error("Failed to load mood presets"); } finally { setLoading(false); } }; const generateMix = async (mood: MoodType) => { const config = MOOD_CONFIG[mood]; setGenerating(mood); try { // Get the mix from pre-computed bucket (instant!) const mix = await api.getMoodBucketMix(mood); if (mix.tracks && mix.tracks.length > 0) { const tracks: Track[] = mix.tracks.map((t) => ({ id: t.id, title: t.title, artist: { name: t.album?.artist?.name || "Unknown Artist", id: t.album?.artist?.id, }, album: { title: t.album?.title || "Unknown Album", coverArt: t.album?.coverUrl, id: t.albumId, }, duration: t.duration, })); // Start playback playTracks(tracks, 0); // Save as user's active mood mix await api.saveMoodBucketMix(mood); toast.success(`${config.label} Mix`, { description: `Playing ${tracks.length} tracks`, }); // Force immediate refetch of mixes on home page // Using refetchQueries instead of invalidateQueries for immediate update await queryClient.refetchQueries({ queryKey: ["mixes"] }); // Also dispatch events for any other listeners window.dispatchEvent(new CustomEvent("mix-generated")); window.dispatchEvent(new CustomEvent("mixes-updated")); onClose(); } else { toast.error("Not enough tracks for this mood", { description: "Try analyzing more music or choose a different mood", }); } } catch (error: unknown) { console.error("Failed to generate mood mix:", error); const errorMessage = error instanceof Error ? error.message : "Failed to generate mix"; toast.error(errorMessage); } finally { setGenerating(null); } }; // Get track count for a mood const getTrackCount = (mood: MoodType): number => { // MoodBucketPreset uses 'id' as the mood identifier const preset = presets.find((p) => p.id === mood); return preset?.trackCount || 0; }; if (!isVisible && !isOpen) return null; return (
e.stopPropagation()} > {/* Header */}

Mood Mixer

Pick your vibe

{/* Content */}
{loading ? (
) : ( /* 3x3 Mood Grid */
{MOOD_ORDER.map((mood) => { const config = MOOD_CONFIG[mood]; const Icon = config.icon; const trackCount = getTrackCount(mood); const isDisabled = trackCount < 5; const isGenerating = generating === mood; return ( ); })}
)} {/* Help text */}

Moods are based on audio analysis of your library

); }