Files
agent-park/src/components/project/ProjectDetail.tsx
T
mzaxdandClaude bf69555978 feat: 添加项目详情 Markdown 渲染支持及项目添加脚本
- 新增 MarkdownContent 组件支持 Markdown 渲染
- 新增 scripts 目录下的项目添加工具脚本
- 更新项目详情页面支持 Markdown 内容显示
- 更新依赖包支持 Markdown 解析

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-27 17:19:39 +08:00

184 lines
7.0 KiB
TypeScript

'use client'
import Link from 'next/link'
import { MarkdownContent } from './MarkdownContent'
interface ProjectDetailProps {
project: {
id: string
name: string
nameEn?: string | null
slug: string
description: string
descriptionEn?: string | null
content?: string | null
contentEn?: string | null
source?: string | null
createdAt: Date
tags: Array<{
id: string
name: string
nameEn?: string | null
slug: string
}>
links: Array<{
id: string
type: string
url: string
title?: string | null
}>
}
locale: string
}
// Helper function to format date
function formatDate(date: Date | string, locale: string): string {
const dateObj = typeof date === 'string' ? new Date(date) : date
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' }
return dateObj.toLocaleDateString(locale === 'en' ? 'en-US' : 'zh-CN', options)
}
export function ProjectDetail({ project, locale }: ProjectDetailProps) {
const displayName = locale === 'en' && project.nameEn ? project.nameEn : project.name
const displayDescription =
locale === 'en' && project.descriptionEn ? project.descriptionEn : project.description
const displayContent = locale === 'en' && project.contentEn ? project.contentEn : project.content
// Get category from first tag
const category = project.tags[0]?.name || 'AI Agent'
const categoryEn = project.tags[0]?.nameEn || project.tags[0]?.name || 'AI Agent'
const displayCategory = locale === 'en' ? categoryEn : category
const handleShare = () => {
if (typeof window !== 'undefined') {
const url = encodeURIComponent(window.location.href)
const text = encodeURIComponent(`Check out ${displayName} on Agent Park`)
window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank')
}
}
const handleCopyLink = () => {
if (typeof window !== 'undefined') {
navigator.clipboard.writeText(window.location.href)
}
}
return (
<>
{/* Header Section */}
<header className="mb-10 relative">
{/* Decorative shape */}
<div
className="absolute -top-10 -right-10 w-24 h-24 border border-black dark:border-white opacity-20 rounded-full hidden md:block pointer-events-none"
style={{ borderRadius: '40% 60% 70% 30% / 40% 50% 60% 50%' }}
></div>
<h1 className="font-display text-4xl md:text-6xl font-bold mb-6 leading-tight uppercase relative z-10">
{displayName}
</h1>
{/* Metadata */}
<div className="flex flex-wrap items-center gap-4 text-sm font-mono text-gray-600 dark:text-gray-400 mb-8 pb-8 border-b border-gray-300 dark:border-gray-700">
<div className="flex items-center gap-2">
<span className="material-icons text-base">calendar_today</span>
<span>Added {formatDate(project.createdAt, locale)}</span>
</div>
<span className="hidden sm:inline text-gray-300">|</span>
<div className="flex items-center gap-2">
<span className="material-icons text-base">category</span>
<span>{displayCategory}</span>
</div>
<span className="hidden sm:inline text-gray-300">|</span>
<div className="flex items-center gap-2">
<span className="material-icons text-base">code</span>
<span>Open Source</span>
</div>
</div>
{/* Tags */}
<div className="flex flex-wrap gap-3 mb-8">
{project.tags.map((tag) => (
<span
key={tag.id}
className="px-3 py-1 border border-black dark:border-gray-500 text-xs font-display font-bold uppercase bg-white dark:bg-gray-800"
>
{tag.name}
</span>
))}
</div>
{/* Featured Image/Video Placeholder */}
<div className="relative w-full aspect-video border-2 border-black dark:border-gray-600 bg-gray-100 dark:bg-gray-800 mb-10 overflow-hidden shadow-brutal dark:shadow-brutal-dark flex items-center justify-center">
{project.source ? (
<iframe
src={project.source}
className="w-full h-full"
title={`${displayName} demo`}
allowFullScreen
/>
) : (
<div className="text-center">
<span className="material-icons text-6xl text-gray-400 dark:text-gray-600 mb-2">
smart_toy
</span>
<p className="text-gray-500 dark:text-gray-400 font-display text-sm">Project Preview</p>
</div>
)}
</div>
</header>
{/* Article Content */}
<article className="max-w-4xl mx-auto">
{/* Lead paragraph */}
<p className="lead font-display text-xl mb-8 text-gray-700 dark:text-gray-300">
{displayDescription}
</p>
{/* Full content with Markdown rendering */}
{displayContent && <MarkdownContent content={displayContent} />}
{/* Installation section if GitHub link exists */}
{project.links.some((l) => l.type === 'GITHUB') && (
<>
<h3>Getting Started</h3>
<p>To install this project, clone the repository and install dependencies:</p>
<pre className="bg-gray-100 dark:bg-gray-800 border border-black dark:border-gray-600 p-4 font-mono text-sm overflow-x-auto">
<code>{`git clone ${
project.links.find((l) => l.type === 'GITHUB')?.url || 'https://github.com/example/project'
}
cd ${project.slug}
npm install`}</code>
</pre>
</>
)}
</article>
{/* Share and Feedback Section */}
<div className="mt-12 pt-8 border-t border-gray-300 dark:border-gray-700 flex flex-col sm:flex-row justify-between items-center gap-6">
<div className="flex gap-4">
<button
className="p-2 border border-black dark:border-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
title="Share on X"
onClick={handleShare}
>
<span className="material-icons text-lg">share</span>
</button>
<button
className="p-2 border border-black dark:border-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
title="Copy Link"
onClick={handleCopyLink}
>
<span className="material-icons text-lg">link</span>
</button>
</div>
<div className="flex items-center gap-2">
<span className="text-sm font-display font-bold text-gray-500">DID THIS AGENT HELP YOU?</span>
<button className="px-4 py-2 bg-primary text-black font-display font-bold text-xs uppercase border border-black hover:bg-yellow-400 transition-colors shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:shadow-none active:translate-x-[2px] active:translate-y-[2px]">
Yes, it rocks
</button>
</div>
</div>
</>
)
}