"use client";
import { Input } from "@/components/ui/input";
import { useState, useMemo, useEffect } from "react";
import { Separator } from "@/components/ui/separator";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
PlayIcon,
PauseIcon,
HeartIcon,
PlusIcon,
ListFilter,
} from "lucide-react";
import { useSoundsStore } from "@/stores/sounds-store";
import { useSoundSearch } from "@/hooks/use-sound-search";
import type { SoundEffect, SavedSound } from "@/types/sounds";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuCheckboxItem,
} from "@/components/ui/dropdown-menu";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { cn } from "@/utils/ui";
import { useInfiniteScroll } from "@/hooks/use-infinite-scroll";
export function SoundsView() {
return (
Sound effects
Songs
Saved
);
}
function SoundEffectsView() {
const {
topSoundEffects,
isLoading,
searchQuery,
setSearchQuery,
scrollPosition,
setScrollPosition,
loadSavedSounds,
isSoundSaved,
toggleSavedSound,
showCommercialOnly,
toggleCommercialFilter,
hasLoaded,
setTopSoundEffects,
setLoading,
setError,
setHasLoaded,
setCurrentPage,
setHasNextPage,
setTotalCount,
} = useSoundsStore();
const {
results: searchResults,
isLoading: isSearching,
loadMore,
hasNextPage,
isLoadingMore,
} = useSoundSearch({ query: searchQuery, commercialOnly: showCommercialOnly });
// Audio playback state
const [playingId, setPlayingId] = useState(null);
const [audioElement, setAudioElement] = useState(
null
);
const { scrollAreaRef, handleScroll } = useInfiniteScroll({
onLoadMore: loadMore,
hasMore: hasNextPage,
isLoading: isLoadingMore || isSearching,
});
useEffect(() => {
loadSavedSounds();
if (!hasLoaded) {
let ignore = false;
const fetchTopSounds = async () => {
try {
if (!ignore) {
setLoading({ loading: true });
setError({ error: null });
}
const response = await fetch(
"/api/sounds/search?page_size=50&sort=downloads"
);
if (!ignore) {
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
const data = await response.json();
setTopSoundEffects({ sounds: data.results });
setHasLoaded({ loaded: true });
setCurrentPage({ page: 1 });
setHasNextPage({ hasNext: !!data.next });
setTotalCount({ count: data.count });
}
} catch (error) {
if (!ignore) {
console.error("Failed to fetch top sounds:", error);
setError({
error:
error instanceof Error ? error.message : "Failed to load sounds",
});
}
} finally {
if (!ignore) {
setLoading({ loading: false });
}
}
};
const timeoutId = setTimeout(fetchTopSounds, 100);
return () => {
clearTimeout(timeoutId);
ignore = true;
};
}
if (scrollAreaRef.current && scrollPosition > 0) {
const timeoutId = setTimeout(() => {
scrollAreaRef.current?.scrollTo({ top: scrollPosition });
}, 100);
return () => clearTimeout(timeoutId);
}
}, [
hasLoaded,
setTopSoundEffects,
setLoading,
setError,
setHasLoaded,
setCurrentPage,
setHasNextPage,
setTotalCount,
]);
const handleScrollWithPosition = (event: React.UIEvent) => {
const { scrollTop } = event.currentTarget;
setScrollPosition({ position: scrollTop });
handleScroll(event);
};
const displayedSounds = useMemo(() => {
const sounds = searchQuery ? searchResults : topSoundEffects;
return sounds;
}, [searchQuery, searchResults, topSoundEffects]);
const playSound = (sound: SoundEffect) => {
if (playingId === sound.id) {
audioElement?.pause();
setPlayingId(null);
return;
}
// Stop previous sound
audioElement?.pause();
if (sound.previewUrl) {
const audio = new Audio(sound.previewUrl);
audio.addEventListener("ended", () => {
setPlayingId(null);
});
audio.addEventListener("error", (e) => {
setPlayingId(null);
});
audio.play().catch((error) => {
setPlayingId(null);
});
setAudioElement(audio);
setPlayingId(sound.id);
}
};
return (
{isLoading && !searchQuery && (
Loading sounds...
)}
{isSearching && searchQuery && (
Searching...
)}
{displayedSounds.map((sound) => (
playSound(sound)}
isSaved={isSoundSaved({ soundId: sound.id })}
onToggleSaved={() => toggleSavedSound({ soundEffect: sound })}
/>
))}
{!isLoading && !isSearching && displayedSounds.length === 0 && (
{searchQuery ? "No sounds found" : "No sounds available"}
)}
{isLoadingMore && (
Loading more sounds...
)}
);
}
function SavedSoundsView() {
const {
savedSounds,
isLoadingSavedSounds,
savedSoundsError,
loadSavedSounds,
isSoundSaved,
toggleSavedSound,
clearSavedSounds,
} = useSoundsStore();
// Audio playback state
const [playingId, setPlayingId] = useState(null);
const [audioElement, setAudioElement] = useState(
null
);
// Clear confirmation dialog state
const [showClearDialog, setShowClearDialog] = useState(false);
// Load saved sounds when tab becomes active
useEffect(() => {
loadSavedSounds();
}, [loadSavedSounds]);
const playSound = (sound: SavedSound) => {
if (playingId === sound.id) {
audioElement?.pause();
setPlayingId(null);
return;
}
// Stop previous sound
audioElement?.pause();
if (sound.previewUrl) {
const audio = new Audio(sound.previewUrl);
audio.addEventListener("ended", () => {
setPlayingId(null);
});
audio.addEventListener("error", (e) => {
setPlayingId(null);
});
audio.play().catch((error) => {
setPlayingId(null);
});
setAudioElement(audio);
setPlayingId(sound.id);
}
};
// Convert SavedSound to SoundEffect for compatibility with AudioItem
const convertToSoundEffect = (savedSound: SavedSound): SoundEffect => ({
id: savedSound.id,
name: savedSound.name,
description: "",
url: "",
previewUrl: savedSound.previewUrl,
downloadUrl: savedSound.downloadUrl,
duration: savedSound.duration,
filesize: 0,
type: "audio",
channels: 0,
bitrate: 0,
bitdepth: 0,
samplerate: 0,
username: savedSound.username,
tags: savedSound.tags,
license: savedSound.license,
created: savedSound.savedAt,
downloads: 0,
rating: 0,
ratingCount: 0,
});
if (isLoadingSavedSounds) {
return (
);
}
if (savedSoundsError) {
return (
Error: {savedSoundsError}
);
}
if (savedSounds.length === 0) {
return (
No saved sounds
Click the heart icon on any sound to save it here
);
}
return (
{savedSounds.length} saved{" "}
{savedSounds.length === 1 ? "sound" : "sounds"}
{savedSounds.map((sound) => (
playSound(sound)}
isSaved={isSoundSaved({ soundId: sound.id })}
onToggleSaved={() =>
toggleSavedSound({ soundEffect: convertToSoundEffect(sound) })
}
/>
))}
);
}
function SongsView() {
return Songs
;
}
interface AudioItemProps {
sound: SoundEffect;
isPlaying: boolean;
onPlay: () => void;
isSaved: boolean;
onToggleSaved: () => void;
}
function AudioItem({
sound,
isPlaying,
onPlay,
isSaved,
onToggleSaved,
}: AudioItemProps) {
const { addSoundToTimeline } = useSoundsStore();
const handleClick = () => {
onPlay();
};
const handleSaveClick = (e: React.MouseEvent) => {
e.stopPropagation();
onToggleSaved();
};
const handleAddToTimeline = async (e: React.MouseEvent) => {
e.stopPropagation();
await addSoundToTimeline({ sound });
};
return (
{sound.name}
{sound.username}
);
}