157 lines
6.4 KiB
TypeScript
157 lines
6.4 KiB
TypeScript
import Link from 'next/link'
|
|
import Image from 'next/image'
|
|
import { getGitHubBadgesFromLinks } from '@/lib/github/badges'
|
|
import { getLocalizedTagName } from '@/lib/i18n/tag-display'
|
|
|
|
interface ProjectCardProps {
|
|
project: {
|
|
id: string
|
|
name: string
|
|
nameEn?: string | null
|
|
slug: string
|
|
description: string
|
|
descriptionEn?: string | null
|
|
tags: Array<{
|
|
id: string
|
|
name: string
|
|
nameEn?: string | null
|
|
slug: string
|
|
}>
|
|
links?: Array<{
|
|
id: string
|
|
type: string
|
|
url: string
|
|
title?: string | null
|
|
}>
|
|
}
|
|
locale: string
|
|
featured?: boolean
|
|
// 新增:通过 props 接收翻译文本,支持客户端组件使用
|
|
translations?: {
|
|
viewDetails: string
|
|
}
|
|
}
|
|
|
|
// Icon mapping for projects based on tags
|
|
function getProjectIcon(tags: Array<{ name: string | null }>): string {
|
|
const tagNames = tags
|
|
.map(t => t.name?.toLowerCase())
|
|
.filter((name): name is string => Boolean(name))
|
|
if (tagNames.some(t => t.includes('automation') || t.includes('workflow'))) return '⚙️'
|
|
if (tagNames.some(t => t.includes('image') || t.includes('art'))) return '🎨'
|
|
if (tagNames.some(t => t.includes('chat') || t.includes('assistant'))) return '💬'
|
|
if (tagNames.some(t => t.includes('data') || t.includes('analysis'))) return '📊'
|
|
if (tagNames.some(t => t.includes('llm') || t.includes('language'))) return '🧠'
|
|
return '✨'
|
|
}
|
|
|
|
export function ProjectCard({ project, locale, featured = false, translations }: ProjectCardProps) {
|
|
// 使用传入的翻译文本,如果没有提供则使用默认值
|
|
const viewDetailsText = translations?.viewDetails || (locale === 'zh' ? '查看详情' : 'View Details')
|
|
const icon = getProjectIcon(project.tags)
|
|
const displayName = locale === 'en' && project.nameEn ? project.nameEn : project.name
|
|
const displayDescription = locale === 'en' && project.descriptionEn ? project.descriptionEn : project.description
|
|
|
|
// Generate GitHub badge URLs
|
|
const badges = project.links ? getGitHubBadgesFromLinks(project.links) : { stars: null }
|
|
|
|
return (
|
|
<article
|
|
className={`bg-white dark:bg-surface-dark border-2 border-black dark:border-gray-600 p-6 ${
|
|
featured ? 'shadow-neo hover:shadow-neo-hover hover:-translate-y-1' : 'shadow-neo-sm'
|
|
} transition-all duration-200 flex flex-col h-full group`}
|
|
>
|
|
{/* Card Header */}
|
|
<div className="flex justify-between items-start mb-4">
|
|
<h3 className="font-display text-xl md:text-2xl font-bold">{displayName}</h3>
|
|
<span className="text-2xl group-hover:rotate-12 transition-transform">{icon}</span>
|
|
</div>
|
|
|
|
{/* Card Description */}
|
|
<p className="text-gray-600 dark:text-gray-300 mb-6 flex-grow font-sans leading-relaxed text-sm md:text-base line-clamp-3">
|
|
{displayDescription}
|
|
</p>
|
|
|
|
{/* Card Footer */}
|
|
<div className="mt-auto">
|
|
{/* Tags */}
|
|
<div className="flex flex-wrap gap-2 mb-4">
|
|
{project.tags.slice(0, 3).map((tag) => (
|
|
<span
|
|
key={tag.id}
|
|
className="bg-gray-100 dark:bg-gray-800 px-2 py-1 text-[10px] uppercase font-display font-bold border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-300"
|
|
>
|
|
{getLocalizedTagName(tag, locale)}
|
|
</span>
|
|
))}
|
|
</div>
|
|
|
|
{/* Footer with View Details and Stars */}
|
|
<div className="flex items-center justify-between mt-auto">
|
|
{/* View Details Link */}
|
|
<Link
|
|
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"
|
|
>
|
|
{viewDetailsText}{' '}
|
|
<span className="ml-1 transform group-hover:translate-x-1 transition-transform">→</span>
|
|
</Link>
|
|
|
|
{/* GitHub Stars Badge */}
|
|
{badges.stars && (
|
|
<div className="flex items-center gap-1 text-sm text-gray-600 dark:text-gray-400">
|
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
|
<path d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25z"/>
|
|
</svg>
|
|
<Image
|
|
src={badges.stars}
|
|
alt={locale === 'zh' ? 'Star' : 'Stars'}
|
|
width={70}
|
|
height={20}
|
|
unoptimized
|
|
className="rounded"
|
|
style={{ width: '70px', height: '20px' }}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|
|
|
|
// Special card for "Submit Your Project" CTA
|
|
interface SubmitProjectCardProps {
|
|
locale: string
|
|
translations?: {
|
|
submitProjectTitle: string
|
|
submitProjectDescription: string
|
|
submitNow: string
|
|
}
|
|
}
|
|
|
|
export function SubmitProjectCard({ locale, translations }: SubmitProjectCardProps) {
|
|
const submitProjectTitle = translations?.submitProjectTitle || (locale === 'zh' ? '提交你的项目' : 'Submit Your Project')
|
|
const submitProjectDescription =
|
|
translations?.submitProjectDescription || (locale === 'zh' ? '帮助我们一起完善 AI 生态' : 'Help us grow the AI ecosystem')
|
|
const submitNow = translations?.submitNow || (locale === 'zh' ? '立即提交' : 'Submit Now')
|
|
|
|
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">
|
|
<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>
|
|
</div>
|
|
<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">
|
|
{submitProjectDescription}
|
|
</p>
|
|
<Link
|
|
href={`/${locale}/submit`}
|
|
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"
|
|
>
|
|
{submitNow}
|
|
</Link>
|
|
</article>
|
|
)
|
|
}
|