From 43390116b722a2fd8ceb1f23c21befbe9bd6b22b Mon Sep 17 00:00:00 2001
From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com>
Date: Thu, 28 Aug 2025 18:27:23 +0000
Subject: [PATCH] Add mobile menu and display video categories and tags
Integrates a mobile-friendly menu with search functionality and displays video categories and tags fetched from Bunny.net metadata, enhancing content discoverability and user experience on all devices.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 2eb1084e-b728-4449-9231-f1665924c8d5
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2eb1084e-b728-4449-9231-f1665924c8d5/wsHCZ4W
---
client/src/components/search-header.tsx | 83 ++++++++++++++++++++++++-
client/src/components/video-card.tsx | 23 +++++++
client/src/pages/VideoPage.tsx | 64 ++++++++++++++++++-
server/bunny.ts | 29 ++++++++-
4 files changed, 193 insertions(+), 6 deletions(-)
diff --git a/client/src/components/search-header.tsx b/client/src/components/search-header.tsx
index d8dfcc6..ceec279 100644
--- a/client/src/components/search-header.tsx
+++ b/client/src/components/search-header.tsx
@@ -1,5 +1,5 @@
import { useState, useCallback } from "react";
-import { Search, Play, Menu, Grid3X3, List } from "lucide-react";
+import { Search, Play, Menu, Grid3X3, List, X } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@@ -17,6 +17,7 @@ export default function SearchHeader({
currentView
}: SearchHeaderProps) {
const [searchQuery, setSearchQuery] = useState("");
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Debounce function to delay search API calls
const debounce = useCallback((func: Function, delay: number) => {
@@ -82,12 +83,88 @@ export default function SearchHeader({
-
+
+ {/* Mobile Menu Button */}
+ setIsMobileMenuOpen(!isMobileMenuOpen)}
+ data-testid="button-mobile-menu-video"
+ >
+ {isMobileMenuOpen ? : }
+
+
+ {/* Mobile Menu */}
+ {isMobileMenuOpen && (
+
+ )}
@@ -311,8 +351,30 @@ export default function VideoPage() {
{formatViews(currentVideo.views)} views
{formatDuration(currentVideo.duration)}
{formatDate(currentVideo.createdAt)}
+ {currentVideo.category && (
+
+ {currentVideo.category}
+
+ )}
+ {/* Tags Section */}
+ {currentVideo.tags && currentVideo.tags.length > 0 && (
+
+
Tags
+
+ {currentVideo.tags.map((tag, index) => (
+
+ #{tag}
+
+ ))}
+
+
+ )}
+
{currentVideo.description ? (
diff --git a/server/bunny.ts b/server/bunny.ts
index a1effdf..b29696a 100644
--- a/server/bunny.ts
+++ b/server/bunny.ts
@@ -95,6 +95,31 @@ export class BunnyService {
}
}
+ // Extract category from bunnyVideo.category or metaTags
+ let category = bunnyVideo.category || "";
+ if (!category && bunnyVideo.metaTags && bunnyVideo.metaTags.length > 0) {
+ const categoryTag = bunnyVideo.metaTags.find((tag: any) =>
+ tag.property?.toLowerCase().includes('category') ||
+ tag.property?.toLowerCase().includes('genre')
+ );
+ if (categoryTag) {
+ category = categoryTag.value;
+ }
+ }
+
+ // Extract tags from metaTags
+ const tags: string[] = [];
+ if (bunnyVideo.metaTags && bunnyVideo.metaTags.length > 0) {
+ bunnyVideo.metaTags.forEach((tag: any) => {
+ if (tag.property?.toLowerCase().includes('tag') ||
+ tag.property?.toLowerCase().includes('keyword')) {
+ // Split comma-separated tags and clean them up
+ const tagValues = tag.value.split(',').map((t: string) => t.trim()).filter(Boolean);
+ tags.push(...tagValues);
+ }
+ });
+ }
+
return {
id: bunnyVideo.guid,
title: bunnyVideo.title || 'Untitled Video',
@@ -106,8 +131,8 @@ export class BunnyService {
videoUrlIframe: iframeUrl, // iframe fallback
duration: Math.floor(bunnyVideo.length || 0),
views: bunnyVideo.views || 0,
- category: "",
- tags: [],
+ category: category,
+ tags: tags,
isPublic: true,
uploadStatus: "completed",
originalFileName: null,