mirror of https://github.com/djteang/OrangeTV.git
Hide optional frontend entries
This commit is contained in:
parent
12d88d3991
commit
6d1ada87b8
|
|
@ -199,6 +199,45 @@ export const Card = Object.assign(
|
|||
}
|
||||
);
|
||||
|
||||
export const Link = ({
|
||||
children,
|
||||
href,
|
||||
...props
|
||||
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
export const Tooltip = Object.assign(
|
||||
({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div {...props}>{children}</div>
|
||||
),
|
||||
{
|
||||
Trigger: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div {...props}>{children}</div>
|
||||
),
|
||||
Content: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div {...props}>{children}</div>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
export const Badge = Object.assign(
|
||||
({ children, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
||||
<span {...props}>{children}</span>
|
||||
),
|
||||
{
|
||||
Label: ({ children, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
|
||||
<span {...props}>{children}</span>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
export const Separator = (props: React.HTMLAttributes<HTMLHRElement>) => (
|
||||
<hr {...props} />
|
||||
);
|
||||
|
||||
const ModalRoot = ({
|
||||
children,
|
||||
state,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ import {
|
|||
} from '@heroui/react';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
BangumiCalendarData,
|
||||
GetBangumiCalendarData,
|
||||
} from '@/lib/bangumi.client';
|
||||
// 客户端收藏 API
|
||||
import {
|
||||
clearAllFavorites,
|
||||
|
|
@ -38,9 +34,6 @@ function HomeClient() {
|
|||
const [hotMovies, setHotMovies] = useState<DoubanItem[]>([]);
|
||||
const [hotTvShows, setHotTvShows] = useState<DoubanItem[]>([]);
|
||||
const [hotVarietyShows, setHotVarietyShows] = useState<DoubanItem[]>([]);
|
||||
const [bangumiCalendarData, setBangumiCalendarData] = useState<
|
||||
BangumiCalendarData[]
|
||||
>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { announcement } = useSite();
|
||||
|
||||
|
|
@ -79,7 +72,7 @@ function HomeClient() {
|
|||
setLoading(true);
|
||||
|
||||
// 并行获取热门电影、热门剧集和热门综艺
|
||||
const [moviesData, tvShowsData, varietyShowsData, bangumiCalendarData] =
|
||||
const [moviesData, tvShowsData, varietyShowsData] =
|
||||
await Promise.all([
|
||||
getDoubanCategories({
|
||||
kind: 'movie',
|
||||
|
|
@ -88,7 +81,6 @@ function HomeClient() {
|
|||
}),
|
||||
getDoubanCategories({ kind: 'tv', category: 'tv', type: 'tv' }),
|
||||
getDoubanCategories({ kind: 'tv', category: 'show', type: 'show' }),
|
||||
GetBangumiCalendarData(),
|
||||
]);
|
||||
|
||||
if (moviesData.code === 200) {
|
||||
|
|
@ -102,8 +94,6 @@ function HomeClient() {
|
|||
if (varietyShowsData.code === 200) {
|
||||
setHotVarietyShows(varietyShowsData.list);
|
||||
}
|
||||
|
||||
setBangumiCalendarData(bangumiCalendarData);
|
||||
} catch (error) {
|
||||
console.error('获取推荐数据失败:', error);
|
||||
} finally {
|
||||
|
|
@ -322,75 +312,6 @@ function HomeClient() {
|
|||
</ScrollableRow>
|
||||
</Card>
|
||||
|
||||
{/* 每日新番放送 */}
|
||||
<Card>
|
||||
<Card.Header className='flex-row items-end justify-between gap-4'>
|
||||
<div>
|
||||
<Card.Description>Bangumi</Card.Description>
|
||||
<Card.Title>新番放送</Card.Title>
|
||||
</div>
|
||||
<HeroLink href='/douban?type=anime'>查看更多</HeroLink>
|
||||
</Card.Header>
|
||||
<ScrollableRow>
|
||||
{loading
|
||||
? // 加载状态显示灰色占位数据
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
>
|
||||
<Skeleton className='aspect-[2/3] w-full' />
|
||||
<Skeleton className='mt-3 h-4' />
|
||||
</div>
|
||||
))
|
||||
: // 展示当前日期的番剧
|
||||
(() => {
|
||||
// 获取当前日期对应的星期
|
||||
const today = new Date();
|
||||
const weekdays = [
|
||||
'Sun',
|
||||
'Mon',
|
||||
'Tue',
|
||||
'Wed',
|
||||
'Thu',
|
||||
'Fri',
|
||||
'Sat',
|
||||
];
|
||||
const currentWeekday = weekdays[today.getDay()];
|
||||
|
||||
// 找到当前星期对应的番剧数据
|
||||
const todayAnimes =
|
||||
bangumiCalendarData.find(
|
||||
(item) => item.weekday.en === currentWeekday
|
||||
)?.items || [];
|
||||
|
||||
return todayAnimes.map((anime, index) => (
|
||||
<div
|
||||
key={`${anime.id || 0}-${index}`}
|
||||
className='min-w-[96px] w-24 sm:min-w-[180px] sm:w-44'
|
||||
>
|
||||
<VideoCard
|
||||
from='douban'
|
||||
title={anime.name_cn || anime.name || '未知标题'}
|
||||
poster={
|
||||
anime.images?.large ||
|
||||
anime.images?.common ||
|
||||
anime.images?.medium ||
|
||||
anime.images?.small ||
|
||||
anime.images?.grid ||
|
||||
'' // 空字符串,让 VideoCard 组件处理图片加载失败
|
||||
}
|
||||
douban_id={anime.id || 0}
|
||||
rate={anime.rating?.score?.toFixed(1) || ''}
|
||||
year={anime.air_date?.split('-')?.[0] || ''}
|
||||
isBangumi={true}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</ScrollableRow>
|
||||
</Card>
|
||||
|
||||
{/* 热门综艺 */}
|
||||
<Card>
|
||||
<Card.Header className='flex-row items-end justify-between gap-4'>
|
||||
|
|
|
|||
|
|
@ -1037,7 +1037,7 @@ function SearchPageClient() {
|
|||
value={searchQuery}
|
||||
onChange={handleInputChange}
|
||||
onFocus={handleInputFocus}
|
||||
placeholder='搜索电影、电视剧、短剧...'
|
||||
placeholder='搜索电影、电视剧...'
|
||||
autoComplete="off"
|
||||
fullWidth
|
||||
className='pl-10 pr-12'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
'use client';
|
||||
|
||||
import { Cat, Clover, Film, Home, Play, Radio, Star, Tv } from 'lucide-react';
|
||||
import { Clover, Film, Home, Star, Tv } from 'lucide-react';
|
||||
import { Button, Card, ScrollShadow } from '@heroui/react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
|
@ -33,26 +33,11 @@ const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
|
|||
label: '剧集',
|
||||
href: '/douban?type=tv',
|
||||
},
|
||||
{
|
||||
icon: Play,
|
||||
label: '短剧',
|
||||
href: '/shortdrama',
|
||||
},
|
||||
{
|
||||
icon: Cat,
|
||||
label: '动漫',
|
||||
href: '/douban?type=anime',
|
||||
},
|
||||
{
|
||||
icon: Clover,
|
||||
label: '综艺',
|
||||
href: '/douban?type=show',
|
||||
},
|
||||
{
|
||||
icon: Radio,
|
||||
label: '直播',
|
||||
href: '/live',
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -81,11 +66,6 @@ const MobileBottomNav = ({ activePath }: MobileBottomNavProps) => {
|
|||
return true;
|
||||
}
|
||||
|
||||
// 短剧页面的特殊处理
|
||||
if (href === '/shortdrama' && decodedActive.startsWith('/shortdrama')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 豆瓣页面的类型匹配
|
||||
if (decodedActive.startsWith('/douban') && typeMatch &&
|
||||
decodedActive.includes(`type=${typeMatch}`)) {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@
|
|||
'use client';
|
||||
|
||||
import {
|
||||
Cat,
|
||||
Clover,
|
||||
ExternalLink,
|
||||
Film,
|
||||
Home,
|
||||
Menu,
|
||||
PlayCircle,
|
||||
Radio,
|
||||
Search,
|
||||
Star,
|
||||
Tv,
|
||||
|
|
@ -176,31 +173,16 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
|
|||
label: '电影',
|
||||
href: '/douban?type=movie',
|
||||
},
|
||||
{
|
||||
icon: PlayCircle,
|
||||
label: '短剧',
|
||||
href: '/shortdrama',
|
||||
},
|
||||
{
|
||||
icon: Tv,
|
||||
label: '剧集',
|
||||
href: '/douban?type=tv',
|
||||
},
|
||||
{
|
||||
icon: Cat,
|
||||
label: '动漫',
|
||||
href: '/douban?type=anime',
|
||||
},
|
||||
{
|
||||
icon: Clover,
|
||||
label: '综艺',
|
||||
href: '/douban?type=show',
|
||||
},
|
||||
{
|
||||
icon: Radio,
|
||||
label: '直播',
|
||||
href: '/live',
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -327,8 +309,7 @@ const Sidebar = ({ onToggle, activePath = '/' }: SidebarProps) => {
|
|||
const isActive =
|
||||
decodedActive === decodedItemHref ||
|
||||
(decodedActive.startsWith('/douban') &&
|
||||
decodedActive.includes(`type=${typeMatch}`)) ||
|
||||
(item.href === '/shortdrama' && decodedActive.startsWith('/shortdrama'));
|
||||
decodedActive.includes(`type=${typeMatch}`));
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<div key={item.label}>
|
||||
|
|
|
|||
|
|
@ -1,48 +1,20 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any,react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
'use client';
|
||||
|
||||
import { MessageCircle, Moon, Sun } from 'lucide-react';
|
||||
import { Moon, Sun } from 'lucide-react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { Badge } from '@heroui/react';
|
||||
import { ChatModal } from './ChatModal';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { AppIconButton } from './ui/HeroPrimitives';
|
||||
import { useWebSocket } from '../hooks/useWebSocket';
|
||||
import { WebSocketMessage } from '../lib/types';
|
||||
|
||||
export function ThemeToggle() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [isChatModalOpen, setIsChatModalOpen] = useState(false);
|
||||
const [messageCount, setMessageCount] = useState(0);
|
||||
const [chatCount, setChatCount] = useState(0);
|
||||
const [friendRequestCount, setFriendRequestCount] = useState(0);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const { setTheme, resolvedTheme } = useTheme();
|
||||
const pathname = usePathname();
|
||||
|
||||
// 不再在ThemeToggle中创建独立的WebSocket连接
|
||||
// 改为依赖ChatModal传递的消息计数
|
||||
|
||||
// 直接使用ChatModal传来的消息计数
|
||||
const handleMessageCountFromModal = useCallback((totalCount: number) => {
|
||||
console.log('📊 [ThemeToggle] 收到ChatModal传来的消息计数:', totalCount);
|
||||
setMessageCount(totalCount);
|
||||
}, []);
|
||||
|
||||
// 处理聊天消息计数重置(当用户查看对话时)
|
||||
const handleChatCountReset = useCallback((resetCount: number) => {
|
||||
console.log('💬 [ThemeToggle] 重置聊天计数:', resetCount);
|
||||
// 这些回调函数现在主要用于同步状态,实际计数由ChatModal管理
|
||||
}, []);
|
||||
|
||||
// 处理好友请求计数重置(当用户查看好友请求时)
|
||||
const handleFriendRequestCountReset = useCallback((resetCount: number) => {
|
||||
console.log('👥 [ThemeToggle] 重置好友请求计数:', resetCount);
|
||||
// 这些回调函数现在主要用于同步状态,实际计数由ChatModal管理
|
||||
}, []);
|
||||
|
||||
const setThemeColor = (theme?: string) => {
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (!meta) {
|
||||
|
|
@ -96,28 +68,9 @@ export function ThemeToggle() {
|
|||
});
|
||||
};
|
||||
|
||||
// 检查是否在登录页面
|
||||
const isLoginPage = pathname === '/login';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`flex items-center ${isMobile ? 'space-x-1' : 'space-x-2'}`}>
|
||||
{/* 聊天按钮 - 在登录页面不显示 */}
|
||||
{!isLoginPage && (
|
||||
<AppIconButton
|
||||
onPress={() => setIsChatModalOpen(true)}
|
||||
size={isMobile ? 'sm' : 'md'}
|
||||
aria-label='Open chat'
|
||||
>
|
||||
{messageCount > 0 && (
|
||||
<Badge size='sm' color='accent' variant='primary' className='absolute -right-1 -top-1'>
|
||||
<Badge.Label>{messageCount > 99 ? '99+' : messageCount}</Badge.Label>
|
||||
</Badge>
|
||||
)}
|
||||
<MessageCircle className='h-5 w-5' />
|
||||
</AppIconButton>
|
||||
)}
|
||||
|
||||
{/* 主题切换按钮 */}
|
||||
<AppIconButton
|
||||
onPress={toggleTheme}
|
||||
|
|
@ -131,17 +84,6 @@ export function ThemeToggle() {
|
|||
)}
|
||||
</AppIconButton>
|
||||
</div>
|
||||
|
||||
{/* 聊天模态框 - 在登录页面不渲染 */}
|
||||
{!isLoginPage && (
|
||||
<ChatModal
|
||||
isOpen={isChatModalOpen}
|
||||
onClose={() => setIsChatModalOpen(false)}
|
||||
onMessageCountChange={handleMessageCountFromModal}
|
||||
onChatCountReset={handleChatCountReset}
|
||||
onFriendRequestCountReset={handleFriendRequestCountReset}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import MobileBottomNav from '../MobileBottomNav';
|
||||
import Sidebar from '../Sidebar';
|
||||
import { ThemeToggle } from '../ThemeToggle';
|
||||
|
||||
const push = jest.fn();
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
usePathname: () => '/',
|
||||
useRouter: () => ({ push }),
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
}));
|
||||
|
||||
jest.mock('next-themes', () => ({
|
||||
useTheme: () => ({
|
||||
resolvedTheme: 'dark',
|
||||
setTheme: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../ChatModal', () => ({
|
||||
ChatModal: () => <div role='dialog'>聊天</div>,
|
||||
}));
|
||||
|
||||
describe('hidden front-end options', () => {
|
||||
beforeEach(() => {
|
||||
push.mockClear();
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('does not render manga, short drama, or live in desktop navigation', () => {
|
||||
render(<Sidebar activePath='/' />);
|
||||
|
||||
expect(screen.queryByRole('button', { name: '动漫' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '短剧' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '直播' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '电影' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '剧集' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '综艺' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render manga, short drama, or live in mobile navigation', () => {
|
||||
render(<MobileBottomNav activePath='/' />);
|
||||
|
||||
expect(screen.queryByRole('button', { name: '动漫' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '短剧' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '直播' })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '电影' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '剧集' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '综艺' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render the chat entry point or chat modal', async () => {
|
||||
render(<ThemeToggle />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', { name: 'Toggle theme' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Open chat' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('dialog', { name: '聊天' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue