project-details-page.tsx

1'use client'
2
3import { Link } from 'nextjs13-progress'
4import { motion } from 'framer-motion'
5import Layout from './Layout'
6import { Badge } from '@/components/ui/badge'
7import {
8	GitFork,
9	Star,
10	Eye,
11	Calendar,
12	Code,
13	FileText,
14	GitBranch,
15	Download,
16	ExternalLink,
17	Folder,
18} from 'lucide-react'
19import { format } from 'date-fns'
20import markdownStyles from '@/app/styles/markdown.module.css'
21import { processMarkdownLinks } from '@/lib/fileUtils'
22import { useState } from 'react'
23import { useEffect } from 'react'
24
25const fadeInUp = {
26	initial: { opacity: 0, y: 20 },
27	animate: { opacity: 1, y: 0 },
28	transition: { duration: 0.6 },
29}
30
31// eslint-disable-next-line @typescript-eslint/no-explicit-any
32export function ProjectDetailsPage({ project }: { project: any }) {
33	const [readme, setReadme] = useState('')
34
35	useEffect(() => {
36		setReadme(
37			processMarkdownLinks(
38				project.readme,
39				project.name,
40				project.default_branch,
41			),
42		)
43	}, [project])
44
45	return (
46		<Layout>
47			<div className="container mx-auto py-12">
48				<motion.div
49					className="space-y-8"
50					initial="initial"
51					animate="animate"
52					variants={{
53						initial: { opacity: 0 },
54						animate: { opacity: 1, transition: { staggerChildren: 0.1 } },
55					}}
56				>
57					{/* Header */}
58					<motion.div variants={fadeInUp} className="space-y-4">
59						<div className="flex items-center space-x-4">
60							<h1 className="text-4xl font-bold">{project.name}</h1>
61							<Badge variant="secondary" className="text-sm">
62								{project.private ? 'Private' : 'Public'}
63							</Badge>
64						</div>
65						<p className="text-lg text-gray-400">{project.description}</p>
66					</motion.div>
67
68					{/* Stats */}
69					<motion.div variants={fadeInUp} className="flex flex-wrap gap-6">
70						<div className="flex items-center space-x-2">
71							<Star className="h-5 w-5 text-yellow-500" />
72							<span>{project.stargazers_count} stars</span>
73						</div>
74						<div className="flex items-center space-x-2">
75							<GitFork className="h-5 w-5" />
76							<span>{project.forks_count} forks</span>
77						</div>
78						<div className="flex items-center space-x-2">
79							<Eye className="h-5 w-5" />
80							<span>{project.watchers_count} watchers</span>
81						</div>
82						<div className="flex items-center space-x-2">
83							<Calendar className="h-5 w-5" />
84							<span>Created {format(new Date(project.created_at), 'PP')}</span>
85						</div>
86						<div className="flex items-center space-x-2">
87							<Code className="h-5 w-5" />
88							<span>{project.language}</span>
89						</div>
90					</motion.div>
91
92					{/* Actions */}
93					<motion.div variants={fadeInUp} className="flex space-x-4">
94						<a
95							href={project.html_url}
96							target="_blank"
97							rel="noopener noreferrer"
98							className="flex items-center space-x-2 rounded-lg bg-blue-600 px-4 py-2 hover:bg-blue-700"
99						>
100							<GitBranch className="h-5 w-5" />
101							<span>View on GitHub</span>
102						</a>
103						{project.homepage && (
104							<a
105								href={project.homepage}
106								target="_blank"
107								rel="noopener noreferrer"
108								className="flex items-center space-x-2 rounded-lg bg-gray-700 px-4 py-2 hover:bg-gray-600"
109							>
110								<ExternalLink className="h-5 w-5" />
111								<span>Visit Website</span>
112							</a>
113						)}
114						<a
115							href={`${project.html_url}/archive/refs/heads/main.zip`}
116							className="flex items-center space-x-2 rounded-lg bg-gray-700 px-4 py-2 hover:bg-gray-600"
117						>
118							<Download className="h-5 w-5" />
119							<span>Download ZIP</span>
120						</a>
121					</motion.div>
122
123					{/* README */}
124					<motion.div variants={fadeInUp} className="space-y-4">
125						<div className="flex items-center space-x-2 border-b border-gray-700 pb-3">
126							<FileText className="h-5 w-5" />
127							<h2 className="text-lg font-semibold">README.md</h2>
128						</div>
129						<div
130							className={`${markdownStyles.markdown} prose prose-invert max-w-none rounded-lg bg-gray-800/50 p-6`}
131							dangerouslySetInnerHTML={{ __html: readme }}
132						/>
133					</motion.div>
134
135					{/* Files */}
136					<motion.div variants={fadeInUp} className="space-y-4">
137						<h2 className="text-2xl font-bold">Files</h2>
138						<div className="rounded-lg bg-gray-800/50 p-4 backdrop-blur-sm">
139							<table className="w-full">
140								<thead>
141									<tr className="text-left">
142										<th className="pb-4">Name</th>
143										<th className="pb-4">Size</th>
144										<th className="pb-4">Type</th>
145									</tr>
146								</thead>
147								<tbody>
148									{project.contents
149										// eslint-disable-next-line @typescript-eslint/no-explicit-any
150										.sort((a: any, b: any) => {
151											// Sort directories first, then files
152											if (a.type === 'dir' && b.type !== 'dir') return -1
153											if (a.type !== 'dir' && b.type === 'dir') return 1
154											// Then sort alphabetically by name
155											return a.name.localeCompare(b.name)
156										})
157										// eslint-disable-next-line @typescript-eslint/no-explicit-any
158										.map((file: any) => (
159											<tr key={file.name} className="border-t border-gray-700">
160												<td className="py-3">
161													<Link
162														href={`/projects/${project.name}/${project.default_branch || 'main'}/~/${file.path}`}
163														className="flex items-center space-x-2 hover:text-blue-400"
164													>
165														{file.type === 'dir' ?
166															<Folder className="h-4 w-4" />
167														:	<FileText className="h-4 w-4" />}
168														<span>{file.name}</span>
169													</Link>
170												</td>
171												<td className="py-3">{file.size}</td>
172												<td className="py-3">{file.type}</td>
173											</tr>
174										))}
175								</tbody>
176							</table>
177						</div>
178					</motion.div>
179				</motion.div>
180			</div>
181		</Layout>
182	)
183}
184