Remove categories and sorting options from the video search filters

Removes category/sort selection from UI and `/api/videos`, updates `getVideos`/`getVideoCount` in `storage.ts`, and removes `/api/categories` route.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aa92e7e2-ec62-4c92-b21b-02ef78a664c2
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/aa92e7e2-ec62-4c92-b21b-02ef78a664c2/Cz5fe6k
This commit is contained in:
sebastjanartic 2025-08-04 20:18:18 +00:00
parent 58dc46ef4b
commit 645fa51028
4 changed files with 44 additions and 123 deletions

View File

@ -6,19 +6,13 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
interface SearchHeaderProps { interface SearchHeaderProps {
onSearch: (query: string) => void; onSearch: (query: string) => void;
onCategoryChange: (category: string) => void;
onSortChange: (sort: string) => void;
onViewChange: (view: "grid" | "list") => void; onViewChange: (view: "grid" | "list") => void;
categories: string[];
currentView: "grid" | "list"; currentView: "grid" | "list";
} }
export default function SearchHeader({ export default function SearchHeader({
onSearch, onSearch,
onCategoryChange,
onSortChange,
onViewChange, onViewChange,
categories,
currentView currentView
}: SearchHeaderProps) { }: SearchHeaderProps) {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
@ -79,61 +73,33 @@ export default function SearchHeader({
<p className="text-bunny-muted">Streaming from Bunny.net CDN</p> <p className="text-bunny-muted">Streaming from Bunny.net CDN</p>
</div> </div>
<div className="flex flex-wrap gap-3"> <div className="flex bg-bunny-gray rounded-lg p-1">
<Select onValueChange={onCategoryChange} data-testid="select-category"> <Button
<SelectTrigger className="bg-bunny-gray border border-gray-600 rounded-lg px-4 py-2 text-sm focus:outline-none focus:border-bunny-blue w-40"> variant={currentView === "grid" ? "default" : "ghost"}
<SelectValue placeholder="All Categories" /> size="sm"
</SelectTrigger> onClick={() => onViewChange("grid")}
<SelectContent> className={`px-3 py-1 rounded text-sm ${
<SelectItem value="All Categories">All Categories</SelectItem> currentView === "grid"
{categories.map((category) => ( ? "bg-bunny-blue text-white"
<SelectItem key={category} value={category}> : "text-bunny-muted hover:text-white"
{category} }`}
</SelectItem> data-testid="button-grid-view"
))} >
</SelectContent> <Grid3X3 className="w-4 h-4" />
</Select> </Button>
<Button
<Select onValueChange={onSortChange} data-testid="select-sort"> variant={currentView === "list" ? "default" : "ghost"}
<SelectTrigger className="bg-bunny-gray border border-gray-600 rounded-lg px-4 py-2 text-sm focus:outline-none focus:border-bunny-blue w-32"> size="sm"
<SelectValue placeholder="Latest" /> onClick={() => onViewChange("list")}
</SelectTrigger> className={`px-3 py-1 rounded text-sm ${
<SelectContent> currentView === "list"
<SelectItem value="latest">Latest</SelectItem> ? "bg-bunny-blue text-white"
<SelectItem value="views">Most Viewed</SelectItem> : "text-bunny-muted hover:text-white"
<SelectItem value="duration">Duration</SelectItem> }`}
<SelectItem value="title">A-Z</SelectItem> data-testid="button-list-view"
</SelectContent> >
</Select> <List className="w-4 h-4" />
</Button>
<div className="flex bg-bunny-gray rounded-lg p-1">
<Button
variant={currentView === "grid" ? "default" : "ghost"}
size="sm"
onClick={() => onViewChange("grid")}
className={`px-3 py-1 rounded text-sm ${
currentView === "grid"
? "bg-bunny-blue text-white"
: "text-bunny-muted hover:text-white"
}`}
data-testid="button-grid-view"
>
<Grid3X3 className="w-4 h-4" />
</Button>
<Button
variant={currentView === "list" ? "default" : "ghost"}
size="sm"
onClick={() => onViewChange("list")}
className={`px-3 py-1 rounded text-sm ${
currentView === "list"
? "bg-bunny-blue text-white"
: "text-bunny-muted hover:text-white"
}`}
data-testid="button-list-view"
>
<List className="w-4 h-4" />
</Button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,25 +12,16 @@ interface VideosResponse {
export default function Home() { export default function Home() {
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] = useState("All Categories");
const [sortBy, setSortBy] = useState("latest");
const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const [allVideos, setAllVideos] = useState<Video[]>([]); const [allVideos, setAllVideos] = useState<Video[]>([]);
// Fetch categories
const { data: categories = [] } = useQuery<string[]>({
queryKey: ["/api/categories"],
});
// Fetch videos // Fetch videos
const { data: videosResponse, isLoading, refetch } = useQuery<VideosResponse>({ const { data: videosResponse, isLoading, refetch } = useQuery<VideosResponse>({
queryKey: ["/api/videos", { queryKey: ["/api/videos", {
limit: 20, limit: 20,
offset, offset,
search: searchQuery || undefined, search: searchQuery || undefined
category: selectedCategory !== "All Categories" ? selectedCategory : undefined,
sort: sortBy
}], }],
queryFn: async ({ queryKey }) => { queryFn: async ({ queryKey }) => {
const [, params] = queryKey as [string, any]; const [, params] = queryKey as [string, any];
@ -61,42 +52,27 @@ export default function Home() {
} }
}, [videosResponse, offset]); }, [videosResponse, offset]);
// Reset videos when search/filter changes // Reset videos when search changes
const handleSearch = (query: string) => { const handleSearch = (query: string) => {
setSearchQuery(query); setSearchQuery(query);
setOffset(0); setOffset(0);
setAllVideos([]); setAllVideos([]);
}; };
const handleCategoryChange = (category: string) => {
setSelectedCategory(category);
setOffset(0);
setAllVideos([]);
};
const handleSortChange = (sort: string) => {
setSortBy(sort);
setOffset(0);
setAllVideos([]);
};
const handleLoadMore = () => { const handleLoadMore = () => {
setOffset(prev => prev + 20); setOffset(prev => prev + 20);
}; };
// Force refetch when filters change // Force refetch when search changes
useEffect(() => { useEffect(() => {
refetch(); refetch();
}, [searchQuery, selectedCategory, sortBy, offset, refetch]); }, [searchQuery, offset, refetch]);
return ( return (
<div className="min-h-screen bg-bunny-dark"> <div className="min-h-screen bg-bunny-dark">
<SearchHeader <SearchHeader
onSearch={handleSearch} onSearch={handleSearch}
onCategoryChange={handleCategoryChange}
onSortChange={handleSortChange}
onViewChange={setViewMode} onViewChange={setViewMode}
categories={categories}
currentView={viewMode} currentView={viewMode}
/> />

View File

@ -10,10 +10,9 @@ export async function registerRoutes(app: Express): Promise<Server> {
const limit = parseInt(req.query.limit as string) || 20; const limit = parseInt(req.query.limit as string) || 20;
const offset = parseInt(req.query.offset as string) || 0; const offset = parseInt(req.query.offset as string) || 0;
const search = req.query.search as string; const search = req.query.search as string;
const category = req.query.category as string;
const videos = await storage.getVideos(limit, offset, search, category); const videos = await storage.getVideos(limit, offset, search);
const total = await storage.getVideoCount(search, category); const total = await storage.getVideoCount(search);
res.json({ res.json({
videos, videos,
@ -48,17 +47,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
}); });
// Get video categories
app.get("/api/categories", async (req, res) => {
try {
const videos = await storage.getVideos(1000);
const categories = Array.from(new Set(videos.map(v => v.category).filter(Boolean)));
res.json(categories);
} catch (error) {
console.error("Error fetching categories:", error);
res.status(500).json({ message: "Failed to fetch categories" });
}
});
const httpServer = createServer(app); const httpServer = createServer(app);
return httpServer; return httpServer;

View File

@ -3,11 +3,11 @@ import { randomUUID } from "crypto";
import { BunnyService } from "./bunny"; import { BunnyService } from "./bunny";
export interface IStorage { export interface IStorage {
getVideos(limit?: number, offset?: number, search?: string, category?: string): Promise<Video[]>; getVideos(limit?: number, offset?: number, search?: string): Promise<Video[]>;
getVideo(id: string): Promise<Video | undefined>; getVideo(id: string): Promise<Video | undefined>;
createVideo(video: InsertVideo): Promise<Video>; createVideo(video: InsertVideo): Promise<Video>;
updateVideoViews(id: string): Promise<void>; updateVideoViews(id: string): Promise<void>;
getVideoCount(search?: string, category?: string): Promise<number>; getVideoCount(search?: string): Promise<number>;
} }
export class MemStorage implements IStorage { export class MemStorage implements IStorage {
@ -92,7 +92,7 @@ export class MemStorage implements IStorage {
}); });
} }
async getVideos(limit = 20, offset = 0, search?: string, category?: string): Promise<Video[]> { async getVideos(limit = 20, offset = 0, search?: string): Promise<Video[]> {
let videos = Array.from(this.videos.values()); let videos = Array.from(this.videos.values());
// Filter by search // Filter by search
@ -104,11 +104,6 @@ export class MemStorage implements IStorage {
); );
} }
// Filter by category
if (category && category !== "All Categories") {
videos = videos.filter(video => video.category === category);
}
// Sort by created date (newest first) // Sort by created date (newest first)
videos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); videos.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
@ -141,8 +136,8 @@ export class MemStorage implements IStorage {
} }
} }
async getVideoCount(search?: string, category?: string): Promise<number> { async getVideoCount(search?: string): Promise<number> {
const videos = await this.getVideos(1000, 0, search, category); const videos = await this.getVideos(1000, 0, search);
return videos.length; return videos.length;
} }
} }
@ -156,11 +151,11 @@ class BunnyStorage implements IStorage {
this.bunnyService = new BunnyService(); this.bunnyService = new BunnyService();
} }
async getVideos(limit = 20, offset = 0, search?: string, category?: string): Promise<Video[]> { async getVideos(limit = 20, offset = 0, search?: string): Promise<Video[]> {
try { try {
// For search/filtering, we need to get more videos than the requested limit // For search filtering, we need to get more videos than the requested limit
// because we'll filter client-side and then slice to the requested amount // because we'll filter client-side and then slice to the requested amount
const fetchLimit = (search || category) ? 100 : limit; const fetchLimit = search ? 100 : limit;
const page = Math.floor(offset / fetchLimit) + 1; const page = Math.floor(offset / fetchLimit) + 1;
// Get videos from Bunny API // Get videos from Bunny API
@ -178,11 +173,6 @@ class BunnyStorage implements IStorage {
); );
} }
// Filter by category if specified
if (category && category !== "All Categories") {
filteredVideos = filteredVideos.filter(video => video.category === category);
}
// Apply cached view counts // Apply cached view counts
filteredVideos.forEach(video => { filteredVideos.forEach(video => {
if (this.viewsCache.has(video.id)) { if (this.viewsCache.has(video.id)) {
@ -228,10 +218,10 @@ class BunnyStorage implements IStorage {
this.viewsCache.set(id, currentViews + 1); this.viewsCache.set(id, currentViews + 1);
} }
async getVideoCount(search?: string, category?: string): Promise<number> { async getVideoCount(search?: string): Promise<number> {
try { try {
// For accurate count with client-side filtering, we need to get all videos and filter them // For accurate count with client-side filtering, we need to get all videos and filter them
const allVideos = await this.getVideos(1000, 0, search, category); const allVideos = await this.getVideos(1000, 0, search);
return allVideos.length; return allVideos.length;
} catch (error) { } catch (error) {
console.error('Error getting video count from Bunny:', error); console.error('Error getting video count from Bunny:', error);