73 lines
3.7 KiB
TypeScript
73 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { BookSource } from '@/lib/book.types';
|
|
|
|
function BooksHomeSkeleton() {
|
|
return (
|
|
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-3 animate-pulse'>
|
|
{Array.from({ length: 6 }).map((_, index) => (
|
|
<div key={index} className='rounded-3xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-950'>
|
|
<div className='h-5 w-32 rounded bg-gray-200 dark:bg-gray-800' />
|
|
<div className='mt-3 flex gap-2'>
|
|
<div className='h-6 w-16 rounded-full bg-gray-200 dark:bg-gray-800' />
|
|
<div className='h-6 w-16 rounded-full bg-gray-200 dark:bg-gray-800' />
|
|
</div>
|
|
<div className='mt-4 flex gap-2'>
|
|
<div className='h-10 w-24 rounded-2xl bg-gray-200 dark:bg-gray-800' />
|
|
<div className='h-10 w-24 rounded-2xl bg-gray-200 dark:bg-gray-800' />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function BooksHomePage() {
|
|
const [sources, setSources] = useState<BookSource[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined' && !(window as Window & { RUNTIME_CONFIG?: { BOOKS_ENABLED?: boolean } }).RUNTIME_CONFIG?.BOOKS_ENABLED) {
|
|
window.location.href = '/';
|
|
return;
|
|
}
|
|
fetch('/api/books/sources')
|
|
.then((res) => res.json())
|
|
.then((data) => setSources(data.sources || []))
|
|
.catch((err) => setError(err.message || '加载书源失败'))
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
return (
|
|
<div className='space-y-6'>
|
|
<section className='rounded-3xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-950'>
|
|
<h1 className='text-lg font-semibold'>电子书源</h1>
|
|
</section>
|
|
|
|
{loading ? <BooksHomeSkeleton /> : null}
|
|
{error ? <div className='text-sm text-red-500'>{error}</div> : null}
|
|
|
|
<div className='grid gap-4 md:grid-cols-2 xl:grid-cols-3'>
|
|
{sources.map((source) => (
|
|
<div key={source.id} className='rounded-3xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-950'>
|
|
<div className='text-base font-semibold'>{source.name}</div>
|
|
<div className='mt-1 text-xs text-gray-400'>{source.type === 'legado' ? 'Legado' : 'OPDS'}</div>
|
|
<div className='mt-2 flex flex-wrap gap-2 text-xs'>
|
|
<span className={`rounded-full px-2 py-1 ${source.capabilities?.catalogSupported ? 'bg-sky-100 text-sky-700 dark:bg-sky-950/50 dark:text-sky-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-900 dark:text-gray-400'}`}>分类{source.capabilities?.catalogSupported ? '可用' : '不可用'}</span>
|
|
<span className={`rounded-full px-2 py-1 ${source.capabilities?.searchSupported ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-950/50 dark:text-emerald-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-900 dark:text-gray-400'}`}>搜索{source.capabilities?.searchSupported ? '可用' : '不可用'}</span>
|
|
</div>
|
|
<div className='mt-4 flex flex-wrap gap-2'>
|
|
{source.capabilities?.catalogSupported && <Link href={`/books/catalog?sourceId=${encodeURIComponent(source.id)}`} className='rounded-2xl bg-sky-600 px-4 py-2 text-sm text-white'>浏览目录</Link>}
|
|
{source.capabilities?.searchSupported && <Link href={`/books/search?sourceId=${encodeURIComponent(source.id)}`} className='rounded-2xl border border-gray-200 px-4 py-2 text-sm dark:border-gray-700'>搜索书籍</Link>}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|