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