fix: sub/you tabs now use backend API, sidebar always visible on desktop, channel subscribe button wired up
This commit is contained in:
parent
7358a92bd8
commit
7e05778840
5 changed files with 88 additions and 38 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import VideoCard from '../../components/VideoCard';
|
||||
import ChannelSubscribeButton from '../../components/ChannelSubscribeButton';
|
||||
import { notFound } from 'next/navigation';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
|
|
@ -93,9 +94,7 @@ export default async function ChannelPage({
|
|||
<span style={{ opacity: 0.5 }}>•</span>
|
||||
<span>{videos.length} videos</span>
|
||||
</div>
|
||||
<button className="channel-subscribe-btn">
|
||||
Subscribe
|
||||
</button>
|
||||
<ChannelSubscribeButton channelId={info.id} channelName={info.title} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
51
frontend/app/components/ChannelSubscribeButton.tsx
Normal file
51
frontend/app/components/ChannelSubscribeButton.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function ChannelSubscribeButton({ channelId, channelName }: { channelId: string; channelName: string }) {
|
||||
const [subscribed, setSubscribed] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/subscribe?channel_id=${encodeURIComponent(channelId)}`)
|
||||
.then(r => r.json())
|
||||
.then(data => setSubscribed(data.subscribed))
|
||||
.catch(() => setSubscribed(false));
|
||||
}, [channelId]);
|
||||
|
||||
const handleSubscribe = async () => {
|
||||
if (loading || !channelId) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
if (subscribed) {
|
||||
const res = await fetch(`/api/subscribe?channel_id=${encodeURIComponent(channelId)}`, { method: 'DELETE' });
|
||||
if (res.ok) setSubscribed(false);
|
||||
} else {
|
||||
const res = await fetch('/api/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
channel_id: channelId,
|
||||
channel_name: channelName || channelId,
|
||||
channel_avatar: channelName ? channelName[0].toUpperCase() : '?',
|
||||
}),
|
||||
});
|
||||
if (res.ok) setSubscribed(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Subscribe error:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`channel-subscribe-btn ${subscribed ? 'subscribed' : ''}`}
|
||||
onClick={handleSubscribe}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? '...' : subscribed ? 'Subscribed' : 'Subscribe'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@ export default function Header() {
|
|||
{/* Left */}
|
||||
<div className="yt-header-left">
|
||||
<button className="yt-icon-btn hamburger-btn" onClick={() => {
|
||||
toggleSidebar();
|
||||
toggleMobileMenu();
|
||||
}} title="Menu">
|
||||
<IoMenuOutline size={22} />
|
||||
|
|
|
|||
|
|
@ -16,23 +16,10 @@ interface SidebarContextType {
|
|||
const SidebarContext = createContext<SidebarContextType | undefined>(undefined);
|
||||
|
||||
export function SidebarProvider({ children }: { children: ReactNode }) {
|
||||
// Sidebar is collapsed by default on desktop
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
// Sidebar is open by default on desktop (hidden on mobile via CSS)
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
// Load saved preference from localStorage
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('sidebarOpen');
|
||||
if (saved !== null) {
|
||||
setIsSidebarOpen(saved === 'true');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Save preference to localStorage
|
||||
useEffect(() => {
|
||||
localStorage.setItem('sidebarOpen', isSidebarOpen.toString());
|
||||
}, [isSidebarOpen]);
|
||||
|
||||
const toggleSidebar = () => setIsSidebarOpen(prev => !prev);
|
||||
const openSidebar = () => setIsSidebarOpen(true);
|
||||
const closeSidebar = () => setIsSidebarOpen(false);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useSearchParams, useRouter } from 'next/navigation';
|
|||
import YouTubePlayer from './YouTubePlayer';
|
||||
import { getVideoDetailsClient, getRelatedVideosClient, getCommentsClient, searchVideosClient } from '../clientActions';
|
||||
import { VideoData } from '../constants';
|
||||
import { isSubscribed, toggleSubscription, addToHistory, isVideoSaved, toggleSaveVideo } from '../storage';
|
||||
import { isVideoSaved, toggleSaveVideo } from '../storage';
|
||||
import LoadingSpinner from '../components/LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
|
|
@ -39,33 +39,44 @@ function VideoInfo({ video }: { video: any }) {
|
|||
const [isSaved, setIsSaved] = useState(false);
|
||||
const [subscribing, setSubscribing] = useState(false);
|
||||
|
||||
// Check subscription and save status on mount
|
||||
// Check subscription status via API and save status on mount
|
||||
useEffect(() => {
|
||||
if (video?.channelId) {
|
||||
setSubscribed(isSubscribed(video.channelId));
|
||||
fetch(`/api/subscribe?channel_id=${encodeURIComponent(video.channelId)}`)
|
||||
.then(r => r.json())
|
||||
.then(data => setSubscribed(data.subscribed))
|
||||
.catch(() => setSubscribed(false));
|
||||
}
|
||||
if (video?.id) {
|
||||
setIsSaved(isVideoSaved(video.id));
|
||||
}
|
||||
}, [video?.channelId, video?.id]);
|
||||
|
||||
const handleSubscribe = useCallback(() => {
|
||||
const handleSubscribe = useCallback(async () => {
|
||||
if (!video?.channelId || subscribing) return;
|
||||
|
||||
setSubscribing(true);
|
||||
try {
|
||||
const nowSubscribed = toggleSubscription({
|
||||
channelId: video.channelId,
|
||||
channelName: video.channelTitle,
|
||||
channelAvatar: '',
|
||||
});
|
||||
setSubscribed(nowSubscribed);
|
||||
if (subscribed) {
|
||||
const res = await fetch(`/api/subscribe?channel_id=${encodeURIComponent(video.channelId)}`, { method: 'DELETE' });
|
||||
if (res.ok) setSubscribed(false);
|
||||
} else {
|
||||
const res = await fetch('/api/subscribe', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
channel_id: video.channelId,
|
||||
channel_name: video.channelTitle || video.channelId,
|
||||
channel_avatar: '',
|
||||
}),
|
||||
});
|
||||
if (res.ok) setSubscribed(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Subscribe error:', error);
|
||||
} finally {
|
||||
setSubscribing(false);
|
||||
}
|
||||
}, [video?.channelId, video?.channelTitle, subscribing]);
|
||||
}, [video?.channelId, video?.channelTitle, subscribed, subscribing]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (!video?.id) return;
|
||||
|
|
@ -636,14 +647,17 @@ export default function ClientWatchPage() {
|
|||
}
|
||||
setVideoInfo(video);
|
||||
|
||||
// Add to watch history (localStorage)
|
||||
// Add to watch history via API
|
||||
if (video) {
|
||||
addToHistory({
|
||||
videoId: videoId,
|
||||
title: video.title,
|
||||
thumbnail: video.thumbnail,
|
||||
channelTitle: video.channelTitle,
|
||||
});
|
||||
fetch('/api/history', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
video_id: videoId,
|
||||
title: video.title,
|
||||
thumbnail: video.thumbnail,
|
||||
}),
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Get related videos - use channel name and video title for better results
|
||||
|
|
|
|||
Loading…
Reference in a new issue