From f4f5597a80054845ddb203b0c12869908b1749a1 Mon Sep 17 00:00:00 2001 From: kuekhaoyang Date: Tue, 7 Apr 2026 22:05:39 +0800 Subject: [PATCH] fix: polish favorites page implementation --- app/favorites/page.tsx | 72 ++------------ app/premium/favorites/page.tsx | 72 ++------------ components/favorites/FavoritesGrid.tsx | 26 +++--- components/favorites/FavoritesPageContent.tsx | 93 +++++++++++++++++++ components/favorites/FavoritesPageHeader.tsx | 2 +- components/layout/Navbar.tsx | 1 + 6 files changed, 124 insertions(+), 142 deletions(-) create mode 100644 components/favorites/FavoritesPageContent.tsx 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 >