fix: 修复 AI 搜索跳转后 async/await 客户端组件错误

将 ProjectCard 和 SubmitProjectCard 从 async 组件重构为普通组件,
通过 props 接收翻译文本,解决在客户端组件中使用 async 组件导致的错误。

- 移除 ProjectCard 和 SubmitProjectCard 的 async 关键字
- 添加 translations prop 接收翻译文本
- 更新所有调用点传递翻译文本

Co-Authored-By: Claude (glm-4.7) <noreply@anthropic.com>
This commit is contained in:
2026-01-27 17:22:21 +08:00
co-authored by Claude
parent b03085292a
commit 6c13fc54d6
5 changed files with 69 additions and 16 deletions
@@ -24,6 +24,12 @@ interface ProjectsPageClientProps {
searching: string searching: string
aiHint: string aiHint: string
traditionalHint: string traditionalHint: string
translations: {
viewDetails: string
submitProjectTitle: string
submitProjectDescription: string
submitNow: string
}
} }
export function ProjectsPageClient({ export function ProjectsPageClient({
@@ -38,6 +44,7 @@ export function ProjectsPageClient({
searching, searching,
aiHint, aiHint,
traditionalHint, traditionalHint,
translations,
}: ProjectsPageClientProps) { }: ProjectsPageClientProps) {
const searchParams = useSearchParams() const searchParams = useSearchParams()
const [aiResults, setAiResults] = useState<AISearchResult[]>([]) const [aiResults, setAiResults] = useState<AISearchResult[]>([])
@@ -150,7 +157,7 @@ export function ProjectsPageClient({
<p className="text-red-700 dark:text-red-400 font-display text-sm">{error}</p> <p className="text-red-700 dark:text-red-400 font-display text-sm">{error}</p>
</div> </div>
)} )}
<AISearchResults results={aiResults} locale={locale} /> <AISearchResults results={aiResults} locale={locale} translations={translations} />
</> </>
) : ( ) : (
children children
+10
View File
@@ -16,6 +16,7 @@ export default async function ProjectsPage({
}: ProjectsPageProps) { }: ProjectsPageProps) {
const t = await getTranslations('home') const t = await getTranslations('home')
const tCommon = await getTranslations('common') const tCommon = await getTranslations('common')
const tProject = await getTranslations('project')
const [resolvedParams, resolvedSearchParams] = await Promise.all([params, searchParams]) const [resolvedParams, resolvedSearchParams] = await Promise.all([params, searchParams])
const { locale } = resolvedParams const { locale } = resolvedParams
@@ -29,6 +30,14 @@ export default async function ProjectsPage({
getAllTags(), // 获取所有标签(用于展开) getAllTags(), // 获取所有标签(用于展开)
]) ])
// 准备翻译文本传递给客户端组件
const translations = {
viewDetails: tCommon('viewDetails'),
submitProjectTitle: tProject('submitProjectTitle'),
submitProjectDescription: tProject('submitProjectDescription'),
submitNow: tProject('submitNow'),
}
return ( return (
<main className="container mx-auto px-4 py-12 md:py-16 max-w-7xl"> <main className="container mx-auto px-4 py-12 md:py-16 max-w-7xl">
{/* Search and Filter Section */} {/* Search and Filter Section */}
@@ -45,6 +54,7 @@ export default async function ProjectsPage({
searching={tCommon('searching')} searching={tCommon('searching')}
aiHint={tCommon('aiHint')} aiHint={tCommon('aiHint')}
traditionalHint={tCommon('traditionalHint')} traditionalHint={tCommon('traditionalHint')}
translations={translations}
> >
{/* Tag Cloud - rendered inside client component for non-AI mode */} {/* Tag Cloud - rendered inside client component for non-AI mode */}
<div className="border-t-2 border-gray-100 dark:border-gray-800 pt-6 mt-8"> <div className="border-t-2 border-gray-100 dark:border-gray-800 pt-6 mt-8">
+25 -9
View File
@@ -1,6 +1,5 @@
import Link from 'next/link' import Link from 'next/link'
import Image from 'next/image' import Image from 'next/image'
import { getTranslations } from 'next-intl/server'
import { GitHubStatsCompact } from './GitHubBadges' import { GitHubStatsCompact } from './GitHubBadges'
import { getGitHubBadgesFromLinks } from '@/lib/github/badges' import { getGitHubBadgesFromLinks } from '@/lib/github/badges'
@@ -27,6 +26,10 @@ interface ProjectCardProps {
} }
locale: string locale: string
featured?: boolean featured?: boolean
// 新增:通过 props 接收翻译文本,支持客户端组件使用
translations?: {
viewDetails: string
}
} }
// Icon mapping for projects based on tags // Icon mapping for projects based on tags
@@ -42,8 +45,9 @@ function getProjectIcon(tags: Array<{ name: string | null }>): string {
return '✨' return '✨'
} }
export async function ProjectCard({ project, locale, featured = false }: ProjectCardProps) { export function ProjectCard({ project, locale, featured = false, translations }: ProjectCardProps) {
const t = await getTranslations('common') // 使用传入的翻译文本,如果没有提供则使用默认值
const viewDetailsText = translations?.viewDetails || 'View Details'
const icon = getProjectIcon(project.tags) const icon = getProjectIcon(project.tags)
const displayName = locale === 'en' && project.nameEn ? project.nameEn : project.name const displayName = locale === 'en' && project.nameEn ? project.nameEn : project.name
const displayDescription = locale === 'en' && project.descriptionEn ? project.descriptionEn : project.description const displayDescription = locale === 'en' && project.descriptionEn ? project.descriptionEn : project.description
@@ -94,7 +98,7 @@ export async function ProjectCard({ project, locale, featured = false }: Project
href={`/${locale}/projects/${project.slug}`} href={`/${locale}/projects/${project.slug}`}
className="inline-flex items-center font-display font-bold text-sm uppercase hover:text-secondary dark:hover:text-primary group" className="inline-flex items-center font-display font-bold text-sm uppercase hover:text-secondary dark:hover:text-primary group"
> >
{t('viewDetails')}{' '} {viewDetailsText}{' '}
<span className="ml-1 transform group-hover:translate-x-1 transition-transform"></span> <span className="ml-1 transform group-hover:translate-x-1 transition-transform"></span>
</Link> </Link>
@@ -121,22 +125,34 @@ export async function ProjectCard({ project, locale, featured = false }: Project
} }
// Special card for "Submit Your Project" CTA // Special card for "Submit Your Project" CTA
export async function SubmitProjectCard({ locale }: { locale: string }) { interface SubmitProjectCardProps {
const t = await getTranslations('project') locale: string
translations?: {
submitProjectTitle: string
submitProjectDescription: string
submitNow: string
}
}
export function SubmitProjectCard({ locale, translations }: SubmitProjectCardProps) {
const submitProjectTitle = translations?.submitProjectTitle || 'Submit Your Project'
const submitProjectDescription = translations?.submitProjectDescription || 'Help us grow the AI ecosystem'
const submitNow = translations?.submitNow || 'Submit Now'
return ( return (
<article className="bg-primary border-2 border-black dark:border-gray-600 p-6 shadow-neo hover:shadow-neo-hover hover:-translate-y-1 transition-all duration-200 flex flex-col h-full justify-center items-center text-center group"> <article className="bg-primary border-2 border-black dark:border-gray-600 p-6 shadow-neo hover:shadow-neo-hover hover:-translate-y-1 transition-all duration-200 flex flex-col h-full justify-center items-center text-center group">
<div className="bg-white dark:bg-black p-4 rounded-full border-2 border-black mb-4 group-hover:scale-110 transition-transform"> <div className="bg-white dark:bg-black p-4 rounded-full border-2 border-black mb-4 group-hover:scale-110 transition-transform">
<span className="text-3xl dark:text-primary">+</span> <span className="text-3xl dark:text-primary">+</span>
</div> </div>
<h3 className="font-display text-xl md:text-2xl font-bold text-black mb-2">{t('submitProjectTitle')}</h3> <h3 className="font-display text-xl md:text-2xl font-bold text-black mb-2">{submitProjectTitle}</h3>
<p className="text-black mb-6 font-sans text-sm px-4"> <p className="text-black mb-6 font-sans text-sm px-4">
{t('submitProjectDescription')} {submitProjectDescription}
</p> </p>
<Link <Link
href="#" href="#"
className="bg-black text-white dark:bg-black dark:text-primary border-2 border-black dark:border-black px-6 py-2 font-display text-sm font-bold uppercase hover:bg-white hover:text-black dark:hover:bg-white dark:hover:text-black transition-colors" className="bg-black text-white dark:bg-black dark:text-primary border-2 border-black dark:border-black px-6 py-2 font-display text-sm font-bold uppercase hover:bg-white hover:text-black dark:hover:bg-white dark:hover:text-black transition-colors"
> >
{t('submitNow')} {submitNow}
</Link> </Link>
</article> </article>
) )
+18 -4
View File
@@ -21,12 +21,13 @@ interface ProjectListProps {
} }
export async function ProjectList({ projects, locale, featured = false }: ProjectListProps) { export async function ProjectList({ projects, locale, featured = false }: ProjectListProps) {
const t = await getTranslations('common') const tCommon = await getTranslations('common')
const tProject = await getTranslations('project')
if (projects.length === 0) { if (projects.length === 0) {
return ( return (
<div className="text-center py-12"> <div className="text-center py-12">
<p className="text-gray-500 dark:text-gray-400 font-display">{t('noProjects')}</p> <p className="text-gray-500 dark:text-gray-400 font-display">{tCommon('noProjects')}</p>
</div> </div>
) )
} }
@@ -34,12 +35,25 @@ export async function ProjectList({ projects, locale, featured = false }: Projec
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{projects.map((project) => ( {projects.map((project) => (
<ProjectCard key={project.id} project={project} locale={locale} featured={featured} /> <ProjectCard
key={project.id}
project={project}
locale={locale}
featured={featured}
translations={{ viewDetails: tCommon('viewDetails') }}
/>
))} ))}
{/* Add "Submit Your Project" CTA card for featured section */} {/* Add "Submit Your Project" CTA card for featured section */}
{featured && ( {featured && (
<SubmitProjectCard locale={locale} /> <SubmitProjectCard
locale={locale}
translations={{
submitProjectTitle: tProject('submitProjectTitle'),
submitProjectDescription: tProject('submitProjectDescription'),
submitNow: tProject('submitNow'),
}}
/>
)} )}
</div> </div>
) )
+8 -2
View File
@@ -12,9 +12,15 @@ interface AISearchResult {
interface AISearchResultsProps { interface AISearchResultsProps {
results: AISearchResult[] results: AISearchResult[]
locale: string locale: string
translations: {
viewDetails: string
submitProjectTitle: string
submitProjectDescription: string
submitNow: string
}
} }
export function AISearchResults({ results, locale }: AISearchResultsProps) { export function AISearchResults({ results, locale, translations }: AISearchResultsProps) {
if (results.length === 0) { if (results.length === 0) {
return ( return (
<div className="text-center py-12 px-4 bg-surface-light dark:bg-surface-dark border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-lg"> <div className="text-center py-12 px-4 bg-surface-light dark:bg-surface-dark border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-lg">
@@ -51,7 +57,7 @@ export function AISearchResults({ results, locale }: AISearchResultsProps) {
{/* 项目卡片 */} {/* 项目卡片 */}
<div className="ml-3"> <div className="ml-3">
<ProjectCard project={project} locale={locale} /> <ProjectCard project={project} locale={locale} translations={translations} />
{/* AI 匹配信息 */} {/* AI 匹配信息 */}
<div className="mt-3 px-4 py-3 bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-400 dark:border-yellow-500 rounded-r"> <div className="mt-3 px-4 py-3 bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-400 dark:border-yellow-500 rounded-r">