Files
KVideo/lib/hooks/usePremiumContent.ts
T
2026-01-13 16:04:13 +08:00

131 lines
4.6 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from 'react';
import { useInfiniteScroll } from '@/lib/hooks/useInfiniteScroll';
import { settingsStore } from '@/lib/store/settings-store';
interface PremiumVideo {
vod_id: string | number;
vod_name: string;
vod_pic?: string;
vod_remarks?: string;
type_name?: string;
source: string;
}
const PAGE_LIMIT = 20;
export function usePremiumContent(categoryValue: string) {
const [videos, setVideos] = useState<PremiumVideo[]>([]);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1);
// Track source count to detect meaningful updates
const sourceCountRef = useRef(0);
const loadVideos = useCallback(async (pageNum: number, append = false) => {
if (loading) return;
setLoading(true);
try {
// Get sources from settings
const settings = settingsStore.getSettings();
// Resolve all relevant sources (premium sources + subscriptions that might be premium)
// For simplicity, we send all enabled premium sources.
const premiumSources = [
...settings.premiumSources,
// Check if any subscription sources are marked as premium
...settings.subscriptions.filter(s => (s as any).group === 'premium')
].filter(s => (s as any).enabled !== false);
if (premiumSources.length === 0) {
setLoading(false);
return;
}
// Should we include normal subscriptions too if categoryValue requests them?
// The API handles filtering by categoryValue map.
// If categoryValue is empty (Recommend), we use all enabled premium sources.
const response = await fetch('/api/premium/category', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sources: premiumSources,
category: categoryValue,
page: pageNum.toString(),
limit: PAGE_LIMIT.toString()
})
});
if (!response.ok) throw new Error('Failed to fetch');
const data = await response.json();
const newVideos = data.videos || [];
setVideos(prev => append ? [...prev, ...newVideos] : newVideos);
setHasMore(newVideos.length === PAGE_LIMIT);
} catch (error) {
console.error('Failed to load videos:', error);
setHasMore(false);
} finally {
setLoading(false);
}
}, [loading, categoryValue]);
// Initial load and category change
useEffect(() => {
setPage(1);
setVideos([]);
setHasMore(true);
// Initial check for sources
const settings = settingsStore.getSettings();
const sourcesCount = settings.premiumSources.length + settings.subscriptions.length;
sourceCountRef.current = sourcesCount;
loadVideos(1, false);
}, [categoryValue]); // eslint-disable-line react-hooks/exhaustive-deps
// Subscribe to settings changes to handle async source loading
useEffect(() => {
const handleSettingsUpdate = () => {
const settings = settingsStore.getSettings();
const premiumSources = [
...settings.premiumSources,
...settings.subscriptions.filter(s => (s as any).group === 'premium')
].filter(s => (s as any).enabled !== false);
const currentSourceCount = premiumSources.length;
// If we have 0 videos and suddenly gain sources, we should retry loading
// OR if the number of sources significantly changed (e.g. from 0 to N)
if (videos.length === 0 && currentSourceCount > 0 && !loading) {
// Determine if we should reload.
// Mostly needed when initial load failed due to no sources.
loadVideos(1, false);
}
};
const unsubscribe = settingsStore.subscribe(handleSettingsUpdate);
return () => unsubscribe();
}, [loadVideos, videos.length, loading]);
const { prefetchRef, loadMoreRef } = useInfiniteScroll({
hasMore,
loading,
page,
onLoadMore: (nextPage) => {
setPage(nextPage);
loadVideos(nextPage, true);
},
});
return {
videos,
loading,
hasMore,
prefetchRef,
loadMoreRef,
};
}