Files
pk-lab-xhs/web/components/HomeFeed.tsx
T
codex-bot 5051466692
Verify mini program and deploy preview / verify-deploy (push) Successful in 3m9s
feat: redesign typed bracket poster editor
2026-09-20 16:37:35 +08:00

64 lines
2.4 KiB
TypeScript

'use client';
import { motion } from 'motion/react';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useMemo, useState } from 'react';
import AppLogo from './AppLogo';
import BottomNav from './BottomNav';
import { apiRequest } from '../lib/api';
import type { TestSummary } from '../lib/types';
export default function HomeFeed() {
const [tests, setTests] = useState<TestSummary[]>([]);
const [query, setQuery] = useState('');
const [error, setError] = useState('');
useEffect(() => {
apiRequest<TestSummary[]>('/api/v1/tests').then(setTests).catch((error: Error) => setError(error.message));
}, []);
const visible = useMemo(() => {
const keyword = query.trim().toLowerCase();
return tests.filter(test => `${test.title} ${test.subtitle}`.toLowerCase().includes(keyword));
}, [query, tests]);
return (
<main className="pageShell homePage">
<AppLogo />
<label className="searchBox">
<span aria-hidden>⌕</span>
<input value={query} onChange={event => setQuery(event.target.value)} placeholder="搜索测试" />
</label>
{error ? <ErrorState message={error} /> : null}
{!error && !tests.length ? <div className="emptyState">正在摆好擂台…</div> : null}
<section className="waterfall" aria-label="全部测试">
{visible.map((test, index) => (
<motion.article
key={test.slug}
className="testCard"
initial={{ opacity: 0, y: 18 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.045, duration: 0.32 }}
whileTap={{ scale: 0.975 }}
>
<Link href={`/tests/${test.slug}`}>
<Image src={`/assets/test-covers/${test.slug}.jpg`} width={900} height={1200} sizes="(max-width: 920px) 50vw, 450px" alt={`${test.title}封面`} />
<div className="cardCopy">
<h2>{test.title}</h2>
<p>{test.subtitle}</p>
<div className="cardMeta"><span>{test.candidate_count} 强</span><span>{test.run_count} 人完成</span></div>
</div>
</Link>
</motion.article>
))}
</section>
<BottomNav />
</main>
);
}
function ErrorState({ message }: { message: string }) {
return <div className="emptyState"><strong>暂时没接上服务</strong><p>{message}</p></div>;
}