diff --git a/frontend/index.html b/frontend/index.html index 4452528..82b523b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,17 +2,12 @@ - - - - + + + + + + MyTube - Download & Watch YouTube Videos diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..9ed0f10 Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon-96x96.png b/frontend/public/favicon-96x96.png new file mode 100644 index 0000000..4b37cc3 Binary files /dev/null and b/frontend/public/favicon-96x96.png differ diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico index de77243..47efbc6 100644 Binary files a/frontend/public/favicon.ico and b/frontend/public/favicon.ico differ diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..bb18c52 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/favicon/apple-touch-icon.png b/frontend/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000..9ed0f10 Binary files /dev/null and b/frontend/public/favicon/apple-touch-icon.png differ diff --git a/frontend/public/favicon/favicon-96x96.png b/frontend/public/favicon/favicon-96x96.png new file mode 100644 index 0000000..4b37cc3 Binary files /dev/null and b/frontend/public/favicon/favicon-96x96.png differ diff --git a/frontend/public/favicon/favicon.ico b/frontend/public/favicon/favicon.ico new file mode 100644 index 0000000..47efbc6 Binary files /dev/null and b/frontend/public/favicon/favicon.ico differ diff --git a/frontend/public/favicon/favicon.svg b/frontend/public/favicon/favicon.svg new file mode 100644 index 0000000..bb18c52 --- /dev/null +++ b/frontend/public/favicon/favicon.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/favicon/site.webmanifest b/frontend/public/favicon/site.webmanifest new file mode 100644 index 0000000..7292fbf --- /dev/null +++ b/frontend/public/favicon/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "MyTube", + "short_name": "MyTube", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/frontend/public/favicon/web-app-manifest-192x192.png b/frontend/public/favicon/web-app-manifest-192x192.png new file mode 100644 index 0000000..b3a3855 Binary files /dev/null and b/frontend/public/favicon/web-app-manifest-192x192.png differ diff --git a/frontend/public/favicon/web-app-manifest-512x512.png b/frontend/public/favicon/web-app-manifest-512x512.png new file mode 100644 index 0000000..706fb3a Binary files /dev/null and b/frontend/public/favicon/web-app-manifest-512x512.png differ diff --git a/frontend/public/mytube-icon.svg b/frontend/public/mytube-icon.svg deleted file mode 100644 index 0785dd5..0000000 --- a/frontend/public/mytube-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/public/site.webmanifest b/frontend/public/site.webmanifest new file mode 100644 index 0000000..7292fbf --- /dev/null +++ b/frontend/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "MyTube", + "short_name": "MyTube", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/frontend/public/web-app-manifest-192x192.png b/frontend/public/web-app-manifest-192x192.png new file mode 100644 index 0000000..b3a3855 Binary files /dev/null and b/frontend/public/web-app-manifest-192x192.png differ diff --git a/frontend/public/web-app-manifest-512x512.png b/frontend/public/web-app-manifest-512x512.png new file mode 100644 index 0000000..706fb3a Binary files /dev/null and b/frontend/public/web-app-manifest-512x512.png differ diff --git a/frontend/src/App.css b/frontend/src/App.css index c507479..8e77441 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -74,6 +74,14 @@ body { color: var(--primary-color); text-decoration: none; letter-spacing: -0.5px; + display: flex; + align-items: center; + gap: 10px; +} + +.logo-icon { + height: 40px; + width: auto; } /* Header Form */ @@ -1997,4 +2005,150 @@ body { display: inline-block; margin-left: 3px; font-weight: bold; +} + +/* Manage Page Styles */ +.manage-page { + padding: 2rem; + max-width: 1200px; + margin: 0 auto; + color: var(--text-primary); +} + +.manage-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.manage-header h1 { + font-size: 2rem; + color: var(--text-primary); + margin: 0; +} + +.back-link { + color: var(--primary-color); + text-decoration: none; + font-weight: 500; + transition: color 0.2s; +} + +.back-link:hover { + color: var(--primary-hover); + text-decoration: underline; +} + +.manage-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + background: var(--bg-secondary); + padding: 1rem; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.manage-search { + flex: 1; + max-width: 400px; + padding: 0.75rem 1rem; + border-radius: 6px; + border: 1px solid var(--border-color); + background: var(--bg-primary); + color: var(--text-primary); + font-size: 1rem; +} + +.video-count { + color: var(--text-secondary); + font-size: 0.9rem; +} + +.manage-table { + width: 100%; + border-collapse: collapse; + background: var(--bg-secondary); + border-radius: 8px; + overflow: hidden; +} + +.manage-table th, +.manage-table td { + padding: 1rem; + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +.manage-table th { + background: rgba(0, 0, 0, 0.2); + font-weight: 600; + color: var(--text-secondary); + text-transform: uppercase; + font-size: 0.85rem; + letter-spacing: 0.5px; +} + +.manage-table tr:last-child td { + border-bottom: none; +} + +.manage-table tr:hover { + background: rgba(255, 255, 255, 0.02); +} + +.manage-thumbnail { + width: 80px; + height: 60px; + object-fit: cover; + border-radius: 4px; +} + +.col-thumbnail { + width: 100px; +} + +.col-title { + font-weight: 500; +} + +.col-author { + color: var(--text-secondary); + width: 200px; +} + +.col-actions { + width: 100px; + text-align: right; +} + +.delete-btn-small { + background: rgba(255, 62, 62, 0.1); + color: #ff3e3e; + border: 1px solid rgba(255, 62, 62, 0.2); + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.85rem; + transition: all 0.2s; +} + +.delete-btn-small:hover:not(:disabled) { + background: rgba(255, 62, 62, 0.2); + border-color: #ff3e3e; +} + +.delete-btn-small:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.no-videos-found { + text-align: center; + padding: 3rem; + color: var(--text-secondary); + background: var(--bg-secondary); + border-radius: 8px; } \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 91ce1a9..0309277 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,6 +7,7 @@ import Header from './components/Header'; import AuthorVideos from './pages/AuthorVideos'; import CollectionPage from './pages/CollectionPage'; import Home from './pages/Home'; +import ManagePage from './pages/ManagePage'; import SearchResults from './pages/SearchResults'; import VideoPlayer from './pages/VideoPlayer'; @@ -681,6 +682,15 @@ function App() { /> } /> + + } + /> diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg new file mode 100644 index 0000000..092bf01 --- /dev/null +++ b/frontend/src/assets/logo.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/AuthorsList.jsx b/frontend/src/components/AuthorsList.jsx index 2d7088e..96c81a1 100644 --- a/frontend/src/components/AuthorsList.jsx +++ b/frontend/src/components/AuthorsList.jsx @@ -11,7 +11,7 @@ const AuthorsList = ({ videos }) => { const uniqueAuthors = [...new Set(videos.map(video => video.author))] .filter(author => author) // Filter out null/undefined authors .sort((a, b) => a.localeCompare(b)); // Sort alphabetically - + setAuthors(uniqueAuthors); } else { setAuthors([]); @@ -40,7 +40,7 @@ const AuthorsList = ({ videos }) => { +
+ setIsOpen(false)} + style={{ fontWeight: 'bold', color: 'var(--primary-color)' }} + > + Manage Videos + +
); diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 66f9356..f3a449e 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -1,7 +1,9 @@ import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; +import logo from '../assets/logo.svg'; const Header = ({ onSubmit, onSearch, activeDownloads = [] }) => { + // ... existing state ... const [videoUrl, setVideoUrl] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(''); @@ -16,6 +18,7 @@ const Header = ({ onSubmit, onSearch, activeDownloads = [] }) => { }, [activeDownloads]); const handleSubmit = async (e) => { + // ... existing submit handler ... e.preventDefault(); if (!videoUrl.trim()) { @@ -76,8 +79,8 @@ const Header = ({ onSubmit, onSearch, activeDownloads = [] }) => {
- My - Tube + MyTube Logo + MyTube
diff --git a/frontend/src/pages/ManagePage.jsx b/frontend/src/pages/ManagePage.jsx new file mode 100644 index 0000000..1e0444d --- /dev/null +++ b/frontend/src/pages/ManagePage.jsx @@ -0,0 +1,96 @@ +import { useState } from 'react'; +import { Link } from 'react-router-dom'; + +const BACKEND_URL = import.meta.env.VITE_BACKEND_URL; + +const ManagePage = ({ videos, onDeleteVideo }) => { + const [searchTerm, setSearchTerm] = useState(''); + const [deletingId, setDeletingId] = useState(null); + + const filteredVideos = videos.filter(video => + video.title.toLowerCase().includes(searchTerm.toLowerCase()) || + video.author.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const handleDelete = async (id) => { + if (window.confirm('Are you sure you want to delete this video?')) { + setDeletingId(id); + await onDeleteVideo(id); + setDeletingId(null); + } + }; + + const getThumbnailSrc = (video) => { + if (video.thumbnailPath) { + return `${BACKEND_URL}${video.thumbnailPath}`; + } + return video.thumbnailUrl || 'https://via.placeholder.com/120x90?text=No+Thumbnail'; + }; + + return ( +
+
+

Manage Videos

+ ← Back to Home +
+ +
+ setSearchTerm(e.target.value)} + className="manage-search" + /> +
+ {filteredVideos.length} videos found +
+
+ +
+ {filteredVideos.length > 0 ? ( + + + + + + + + + + + {filteredVideos.map(video => ( + + + + + + + ))} + +
ThumbnailTitleAuthorActions
+ {video.title} + {video.title}{video.author} + +
+ ) : ( +
+ No videos found matching your search. +
+ )} +
+
+ ); +}; + +export default ManagePage; diff --git a/frontend/src/pages/VideoPlayer.jsx b/frontend/src/pages/VideoPlayer.jsx index fb67ec6..af7bc6d 100644 --- a/frontend/src/pages/VideoPlayer.jsx +++ b/frontend/src/pages/VideoPlayer.jsx @@ -217,7 +217,7 @@ const VideoPlayer = ({ videos, onDeleteVideo, collections, onAddToCollection, on className="action-btn btn-secondary" onClick={handleAddToCollection} > - + Save + + Add to Collection