Files
software-workspace/src/pages/ProjectDetailPage.tsx
T
shenjianZ 3ee9b9e6de
CI / build (push) Has been cancelled
Add privacy policy modal and enhance project details
- Introduce PolicyModal component for license and privacy policy display
- Add Quick Start section to project detail pages
- Update project descriptions, features, and URLs for several projects
- Add new logos, screenshots, and favicon
- Extend types and configuration for new features
2026-05-26 14:14:37 +08:00

232 lines
8.4 KiB
TypeScript

import { Link, useParams } from 'react-router-dom';
import { useI18n } from '../hooks/useI18n';
import { siteData } from '../data/siteData';
import DownloadTable from '../components/DownloadTable';
import RoadmapGrid from '../components/RoadmapGrid';
import ChangelogList from '../components/ChangelogList';
import ScreenshotCarousel from '../components/ScreenshotCarousel';
import { getIcon } from '../utils/iconRegistry';
import { ExternalLink, Download, BookOpen, Globe } from 'lucide-react';
export default function ProjectDetailPage() {
const { id } = useParams();
const { t, bi, biArray, lang } = useI18n();
const p = siteData.projects.find((pr) => pr.id === id);
if (!p) {
return (
<div className="container">
<div className="empty-state">Project not found</div>
</div>
);
}
const statusDef = siteData.statuses[p.status];
const hasDownloads = p.downloads && p.downloads.length > 0;
const IconComponent = getIcon(p.icon);
return (
<div className="container fade-in">
{/* Breadcrumb */}
<div className="breadcrumb">
<Link to="/">
{bi(siteData.nav[0].label)}
</Link>
{' / '}
<Link to="/projects">
{bi(siteData.nav[1].label)}
</Link>
{' / '}
<span className="breadcrumb-current">{p.name}</span>
</div>
{/* Header */}
<div className="detail-header">
<div className="detail-header-top">
<div className="detail-icon">
{p.logo
? <img src={p.logo} alt={p.name} className="project-logo" />
: (IconComponent ? <IconComponent size={28} /> : <span>{p.icon}</span>)
}
</div>
<div>
<h1 className="detail-title">{p.displayName[lang] || p.name}</h1>
<p className="detail-slogan">{bi(p.slogan)}</p>
</div>
</div>
<div className="detail-badges">
{p.techStack.map((ts) => (
<span key={ts} className="badge badge-accent">
{ts}
</span>
))}
{p.platforms.map((pl) => (
<span key={pl} className="badge">
{pl}
</span>
))}
<span
className="badge badge-status"
style={{ background: statusDef?.color || '#6B6B6B' }}
>
{bi(statusDef?.label)}
</span>
</div>
<div className="detail-actions">
{p.websiteUrl && (
<a href={p.websiteUrl} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<Globe size={16} />
{t('detail.website')}
</a>
)}
{hasDownloads && (
<a href="#downloads" className="btn" onClick={(e) => {
e.preventDefault();
document.getElementById('downloads')?.scrollIntoView({ behavior: 'smooth' });
}}>
<Download size={16} />
{t('common.download')}
</a>
)}
{p.docsUrl && (
<a href={p.docsUrl} target="_blank" rel="noopener noreferrer" className="btn">
<BookOpen size={16} />
{t('common.docs')}
</a>
)}
<a href={`${p.repoUrl}/issues`} target="_blank" className="btn btn-ghost">
<ExternalLink size={16} />
{t('contact.issues')}
</a>
</div>
</div>
{/* Body */}
<div className="detail-body">
<div className="detail-main">
{/* Overview */}
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.overview')}</h2>
<p className="detail-prose">{bi(p.description)}</p>
</div>
{/* Quick Start */}
{p.quickStart && p.quickStart[lang] && p.quickStart[lang].length > 0 && (
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.quickStart')}</h2>
<div className="quickstart-commands">
{p.quickStart[lang].map((cmd, i) => (
<div key={i} className="quickstart-line">
<span className="quickstart-prompt">$</span>
<code className="quickstart-cmd">{cmd}</code>
</div>
))}
</div>
</div>
)}
{/* Features */}
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.features')}</h2>
<div className="feature-tags">
{biArray(p.features).map((f, i) => (
<span key={i} className="feature-tag">
{f}
</span>
))}
</div>
</div>
{/* Screenshots */}
{p.screenshots && p.screenshots.length > 0 && (
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.screenshots')}</h2>
<ScreenshotCarousel screenshots={p.screenshots} />
</div>
)}
{/* Downloads */}
{hasDownloads && (
<div className="detail-section" id="downloads">
<h2 className="detail-section-title">{t('detail.downloads')}</h2>
<DownloadTable downloads={p.downloads} />
{p.installGuide && (
<div className="install-guide-inline">
{p.installGuide[lang].map((item) => (
<div key={item.platform} className="install-tip-row">
<span className="install-tip-icon">{item.icon}</span>
<span className="install-tip-format">{item.format}</span>
<span className="install-tip-text">{item.tip}</span>
</div>
))}
</div>
)}
<div className="trust-note">{t('downloads.trustNote')}</div>
</div>
)}
{/* Changelog */}
{p.changelog && p.changelog.length > 0 && (
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.changelog')}</h2>
<ChangelogList changelog={p.changelog} />
</div>
)}
{/* Roadmap */}
{p.roadmap && (
<div className="detail-section">
<h2 className="detail-section-title">{t('detail.roadmap')}</h2>
<RoadmapGrid roadmap={p.roadmap} />
</div>
)}
</div>
{/* Sidebar */}
<aside className="detail-sidebar">
<div className="detail-meta-panel">
<div className="detail-meta-item">
<span className="detail-meta-label">{t('detail.version')}</span>
<span className="detail-meta-value mono">{p.latestVersion}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">{t('detail.status')}</span>
<span className="detail-meta-value">{bi(statusDef?.label)}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">{t('detail.license')}</span>
<span className="detail-meta-value">{p.license}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">{t('detail.lastUpdate')}</span>
<span className="detail-meta-value">{p.lastUpdated}</span>
</div>
<div className="detail-meta-item">
<span className="detail-meta-label">Language</span>
<span className="detail-meta-value">{p.language}</span>
</div>
</div>
<div className="detail-link-grid">
<a href={p.repoUrl} target="_blank" className="detail-link-btn">
<ExternalLink size={15} />
{t('detail.repo')}
</a>
{p.websiteUrl && (
<a href={p.websiteUrl} target="_blank" rel="noopener noreferrer" className="detail-link-btn">
<Globe size={15} />
{t('detail.website')}
</a>
)}
{p.docsUrl && (
<a href={p.docsUrl} target="_blank" rel="noopener noreferrer" className="detail-link-btn">
<BookOpen size={15} />
{t('detail.docs')}
</a>
)}
</div>
</aside>
</div>
</div>
);
}