diff --git a/app/favorites/page.tsx b/app/favorites/page.tsx
index b335e5d..2402e97 100644
--- a/app/favorites/page.tsx
+++ b/app/favorites/page.tsx
@@ -1,73 +1,15 @@
'use client';
-import { Suspense, useState, useMemo } from 'react';
-import { useRouter } from 'next/navigation';
-import { Navbar } from '@/components/layout/Navbar';
-import { FavoritesPageHeader } from '@/components/favorites/FavoritesPageHeader';
-import { FavoritesGrid } from '@/components/favorites/FavoritesGrid';
-import { FavoritesSidebar } from '@/components/favorites/FavoritesSidebar';
-import { WatchHistorySidebar } from '@/components/history/WatchHistorySidebar';
-import { ConfirmDialog } from '@/components/ui/ConfirmDialog';
-import { useFavoritesStore } from '@/lib/store/favorites-store';
-
-function FavoritesPage() {
- const router = useRouter();
- const { favorites, clearFavorites } = useFavoritesStore();
- const [sortBy, setSortBy] = useState<'date' | 'title'>('date');
- const [isClearDialogOpen, setIsClearDialogOpen] = useState(false);
-
- const sortedFavorites = useMemo(() => {
- if (sortBy === 'title') {
- return [...favorites].sort((a, b) =>
- a.title.localeCompare(b.title, 'zh-CN')
- );
- }
- return favorites; // already newest-first from store
- }, [favorites, sortBy]);
-
- return (
-
- router.push('/')} />
-
-
- setIsClearDialogOpen(true)}
- />
-
-
-
-
-
-
-
- {
- clearFavorites();
- setIsClearDialogOpen(false);
- }}
- onCancel={() => setIsClearDialogOpen(false)}
- dangerous
- />
-
- );
-}
+import { Suspense } from 'react';
+import {
+ FavoritesPageContent,
+ FavoritesPageFallback,
+} from '@/components/favorites/FavoritesPageContent';
export default function Favorites() {
return (
-
-
-
- }>
-
+ }>
+
);
}
diff --git a/app/premium/favorites/page.tsx b/app/premium/favorites/page.tsx
index 212f74d..758acbf 100644
--- a/app/premium/favorites/page.tsx
+++ b/app/premium/favorites/page.tsx
@@ -1,73 +1,15 @@
'use client';
-import { Suspense, useState, useMemo } from 'react';
-import { useRouter } from 'next/navigation';
-import { Navbar } from '@/components/layout/Navbar';
-import { FavoritesPageHeader } from '@/components/favorites/FavoritesPageHeader';
-import { FavoritesGrid } from '@/components/favorites/FavoritesGrid';
-import { FavoritesSidebar } from '@/components/favorites/FavoritesSidebar';
-import { WatchHistorySidebar } from '@/components/history/WatchHistorySidebar';
-import { ConfirmDialog } from '@/components/ui/ConfirmDialog';
-import { usePremiumFavoritesStore } from '@/lib/store/favorites-store';
-
-function PremiumFavoritesPage() {
- const router = useRouter();
- const { favorites, clearFavorites } = usePremiumFavoritesStore();
- const [sortBy, setSortBy] = useState<'date' | 'title'>('date');
- const [isClearDialogOpen, setIsClearDialogOpen] = useState(false);
-
- const sortedFavorites = useMemo(() => {
- if (sortBy === 'title') {
- return [...favorites].sort((a, b) =>
- a.title.localeCompare(b.title, 'zh-CN')
- );
- }
- return favorites;
- }, [favorites, sortBy]);
-
- return (
-
- router.push('/premium')} isPremiumMode />
-
-
- setIsClearDialogOpen(true)}
- />
-
-
-
-
-
-
-
- {
- clearFavorites();
- setIsClearDialogOpen(false);
- }}
- onCancel={() => setIsClearDialogOpen(false)}
- dangerous
- />
-
- );
-}
+import { Suspense } from 'react';
+import {
+ FavoritesPageContent,
+ FavoritesPageFallback,
+} from '@/components/favorites/FavoritesPageContent';
export default function PremiumFavorites() {
return (
-
-
-
- }>
-
+ }>
+
);
}
diff --git a/components/favorites/FavoritesGrid.tsx b/components/favorites/FavoritesGrid.tsx
index 46e7037..6ca3ec9 100644
--- a/components/favorites/FavoritesGrid.tsx
+++ b/components/favorites/FavoritesGrid.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useState, useRef, useCallback, useEffect, memo } from 'react';
+import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react';
import { VideoCard } from '@/components/search/VideoCard';
import { FavoritesEmptyState } from './FavoritesEmptyState';
import type { FavoriteItem, Video } from '@/lib/types';
@@ -20,16 +20,20 @@ export const FavoritesGrid = memo(function FavoritesGrid({
const observerRef = useRef(null);
// Convert FavoriteItem to Video format
- const videos: Video[] = favorites.map((favorite) => ({
- vod_id: favorite.videoId,
- vod_name: favorite.title,
- vod_pic: favorite.poster,
- vod_remarks: favorite.remarks,
- vod_year: favorite.year,
- type_name: favorite.type,
- source: favorite.source,
- sourceName: favorite.sourceName,
- }));
+ const videos: Video[] = useMemo(
+ () =>
+ favorites.map((favorite) => ({
+ vod_id: favorite.videoId,
+ vod_name: favorite.title,
+ vod_pic: favorite.poster,
+ vod_remarks: favorite.remarks,
+ vod_year: favorite.year,
+ type_name: favorite.type,
+ source: favorite.source,
+ sourceName: favorite.sourceName,
+ })),
+ [favorites]
+ );
// Setup intersection observer for infinite scroll
useEffect(() => {
diff --git a/components/favorites/FavoritesPageContent.tsx b/components/favorites/FavoritesPageContent.tsx
new file mode 100644
index 0000000..33fcbe0
--- /dev/null
+++ b/components/favorites/FavoritesPageContent.tsx
@@ -0,0 +1,93 @@
+'use client';
+
+import { useMemo, useState } from 'react';
+import { useRouter } from 'next/navigation';
+import { Navbar } from '@/components/layout/Navbar';
+import { FavoritesPageHeader } from '@/components/favorites/FavoritesPageHeader';
+import { FavoritesGrid } from '@/components/favorites/FavoritesGrid';
+import { FavoritesSidebar } from '@/components/favorites/FavoritesSidebar';
+import { WatchHistorySidebar } from '@/components/history/WatchHistorySidebar';
+import { ConfirmDialog } from '@/components/ui/ConfirmDialog';
+import { useFavorites } from '@/lib/store/favorites-store';
+
+interface FavoritesPageContentProps {
+ isPremium?: boolean;
+}
+
+export function FavoritesPageContent({
+ isPremium = false,
+}: FavoritesPageContentProps) {
+ const router = useRouter();
+ const { favorites, clearFavorites } = useFavorites(isPremium);
+ const [sortBy, setSortBy] = useState<'date' | 'title'>('date');
+ const [isClearDialogOpen, setIsClearDialogOpen] = useState(false);
+
+ const sortedFavorites = useMemo(() => {
+ if (sortBy === 'title') {
+ return [...favorites].sort((a, b) => a.title.localeCompare(b.title, 'zh-CN'));
+ }
+
+ return favorites;
+ }, [favorites, sortBy]);
+
+ return (
+
+ router.push(isPremium ? '/premium' : '/')}
+ isPremiumMode={isPremium}
+ />
+
+
+ setIsClearDialogOpen(true)}
+ />
+
+
+
+
+
+
+
+ {
+ clearFavorites();
+ setIsClearDialogOpen(false);
+ }}
+ onCancel={() => setIsClearDialogOpen(false)}
+ dangerous
+ />
+
+ );
+}
+
+interface FavoritesPageFallbackProps {
+ isPremium?: boolean;
+}
+
+export function FavoritesPageFallback({
+ isPremium = false,
+}: FavoritesPageFallbackProps) {
+ return (
+
+ );
+}
diff --git a/components/favorites/FavoritesPageHeader.tsx b/components/favorites/FavoritesPageHeader.tsx
index b4fd4fa..0d039d1 100644
--- a/components/favorites/FavoritesPageHeader.tsx
+++ b/components/favorites/FavoritesPageHeader.tsx
@@ -73,7 +73,7 @@ export function FavoritesPageHeader({
onClick={onClearAll}
className="px-4 py-2 rounded-[var(--radius-full)] bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)] transition-all text-sm flex items-center gap-2"
>
-
+
清空收藏
)}
diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx
index 5d6a982..7c119a8 100644
--- a/components/layout/Navbar.tsx
+++ b/components/layout/Navbar.tsx
@@ -109,6 +109,7 @@ export function Navbar({ onReset, isPremiumMode = false }: NavbarProps) {
href={favoritesHref}
className="w-8 h-8 sm:w-10 sm:h-10 flex items-center justify-center rounded-[var(--radius-full)] bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)] transition-all duration-200 cursor-pointer"
aria-label="我的收藏"
+ data-focusable
>