Skip to content

Commit 2387751

Browse files
Complete Next.js website enhancement: comprehensive project data parsing, individual project pages, and professional UI with rich metadata display
Co-authored-by: rezwana-karim <126201034+rezwana-karim@users.noreply.github.com>
1 parent eece130 commit 2387751

9 files changed

Lines changed: 2139 additions & 257 deletions

File tree

package-lock.json

Lines changed: 1247 additions & 53 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@radix-ui/react-dropdown-menu": "^2.1.16",
1717
"@radix-ui/react-icons": "^1.3.2",
1818
"@radix-ui/react-navigation-menu": "^1.2.14",
19+
"@radix-ui/react-separator": "^1.1.7",
1920
"@radix-ui/react-slot": "^1.2.3",
2021
"@radix-ui/react-toast": "^1.2.15",
2122
"class-variance-authority": "^0.7.1",
@@ -24,6 +25,7 @@
2425
"next": "15.5.4",
2526
"react": "19.1.0",
2627
"react-dom": "19.1.0",
28+
"react-markdown": "^10.1.0",
2729
"tailwind-merge": "^3.3.1"
2830
},
2931
"devDependencies": {

src/app/projects/[slug]/page.tsx

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
import Link from "next/link"
2+
import { Container } from "@/components/ui/container"
3+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
4+
import { Button } from "@/components/ui/button"
5+
import { Badge } from "@/components/ui/badge"
6+
import { Separator } from "@/components/ui/separator"
7+
import { GitHubLogoIcon, ExternalLinkIcon, StarIcon, CopyIcon, CalendarIcon, PersonIcon } from "@radix-ui/react-icons"
8+
import { getAllProjects, getProjectBySlug } from "@/lib/project-data"
9+
import { notFound } from "next/navigation"
10+
import ReactMarkdown from "react-markdown"
11+
12+
interface ProjectPageProps {
13+
params: {
14+
slug: string
15+
}
16+
}
17+
18+
export async function generateStaticParams() {
19+
const projects = getAllProjects()
20+
return projects.map((project) => ({
21+
slug: project.slug,
22+
}))
23+
}
24+
25+
export async function generateMetadata({ params }: ProjectPageProps) {
26+
const project = getProjectBySlug(params.slug)
27+
28+
if (!project) {
29+
return {
30+
title: "Project Not Found",
31+
}
32+
}
33+
34+
return {
35+
title: `${project.name} | CodeStorm Hub Projects`,
36+
description: project.overview || project.description || `Learn more about ${project.name} - an innovative project by CodeStorm Hub.`,
37+
openGraph: {
38+
title: project.name,
39+
description: project.overview || project.description,
40+
type: "website",
41+
},
42+
}
43+
}
44+
45+
export default function ProjectPage({ params }: ProjectPageProps) {
46+
const project = getProjectBySlug(params.slug)
47+
48+
if (!project) {
49+
notFound()
50+
}
51+
52+
const getStatusBadge = (status: string | undefined) => {
53+
const statusConfig = {
54+
'Active': { emoji: '✅', color: 'bg-green-100 text-green-800 border-green-200' },
55+
'Archived': { emoji: '📦', color: 'bg-gray-100 text-gray-800 border-gray-200' },
56+
'Maintenance': { emoji: '🚧', color: 'bg-yellow-100 text-yellow-800 border-yellow-200' },
57+
'In Development': { emoji: '🔄', color: 'bg-blue-100 text-blue-800 border-blue-200' },
58+
'Under Construction': { emoji: '🏗️', color: 'bg-orange-100 text-orange-800 border-orange-200' },
59+
}
60+
const config = statusConfig[status as keyof typeof statusConfig] || statusConfig['Active']
61+
return (
62+
<Badge variant="outline" className={`${config.color} text-sm font-medium`}>
63+
<span className="mr-2">{config.emoji}</span>
64+
{status}
65+
</Badge>
66+
)
67+
}
68+
69+
const getTechBadgeColor = (tech: string) => {
70+
const colors: Record<string, string> = {
71+
'JavaScript': 'bg-yellow-100 text-yellow-800 border-yellow-200',
72+
'TypeScript': 'bg-blue-100 text-blue-800 border-blue-200',
73+
'Python': 'bg-green-100 text-green-800 border-green-200',
74+
'PHP': 'bg-purple-100 text-purple-800 border-purple-200',
75+
'Laravel': 'bg-red-100 text-red-800 border-red-200',
76+
'Vue.js': 'bg-emerald-100 text-emerald-800 border-emerald-200',
77+
'React': 'bg-cyan-100 text-cyan-800 border-cyan-200',
78+
'Next.js': 'bg-gray-100 text-gray-800 border-gray-200',
79+
'HTML': 'bg-orange-100 text-orange-800 border-orange-200',
80+
'CSS': 'bg-pink-100 text-pink-800 border-pink-200',
81+
}
82+
return colors[tech] || 'bg-secondary text-secondary-foreground border-border'
83+
}
84+
85+
return (
86+
<div className="py-12">
87+
<Container>
88+
<div className="space-y-8">
89+
{/* Breadcrumb */}
90+
<nav className="flex items-center space-x-2 text-sm text-muted-foreground">
91+
<Link href="/projects" className="hover:text-foreground transition-colors">Projects</Link>
92+
<span>/</span>
93+
<span className="text-foreground font-medium">{project.name}</span>
94+
</nav>
95+
96+
{/* Project Header */}
97+
<div className="space-y-6">
98+
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-6">
99+
<div className="space-y-4">
100+
<div className="flex items-center gap-3">
101+
<h1 className="text-4xl font-bold tracking-tight">{project.name}</h1>
102+
{project.primaryLanguage && (
103+
<Badge variant="outline" className={`${getTechBadgeColor(project.primaryLanguage)} text-sm`}>
104+
{project.primaryLanguage}
105+
</Badge>
106+
)}
107+
</div>
108+
109+
<div className="flex flex-wrap items-center gap-3">
110+
{getStatusBadge(project.status)}
111+
{project.category && (
112+
<Badge variant="secondary" className="text-sm">
113+
{project.category}
114+
</Badge>
115+
)}
116+
{project.visibility && (
117+
<Badge variant="outline" className={`text-sm ${
118+
project.visibility === 'Public'
119+
? 'bg-green-50 text-green-700 border-green-200'
120+
: 'bg-gray-50 text-gray-700 border-gray-200'
121+
}`}>
122+
{project.visibility === 'Public' ? '🌐' : '🔐'} {project.visibility}
123+
</Badge>
124+
)}
125+
</div>
126+
127+
<p className="text-xl text-muted-foreground max-w-3xl">
128+
{project.overview || project.description || 'An innovative project demonstrating modern development practices and solving real-world challenges.'}
129+
</p>
130+
</div>
131+
132+
{/* Action Buttons */}
133+
<div className="flex flex-col sm:flex-row gap-3 lg:flex-col">
134+
{project.liveDemo && (
135+
<Button size="lg" asChild>
136+
<a href={project.liveDemo} target="_blank" rel="noopener noreferrer">
137+
<ExternalLinkIcon className="mr-2 h-5 w-5" />
138+
Live Demo
139+
</a>
140+
</Button>
141+
)}
142+
{project.github && (
143+
<Button variant="outline" size="lg" asChild>
144+
<a href={project.github} target="_blank" rel="noopener noreferrer">
145+
<GitHubLogoIcon className="mr-2 h-5 w-5" />
146+
View on GitHub
147+
</a>
148+
</Button>
149+
)}
150+
</div>
151+
</div>
152+
153+
{/* Project Stats */}
154+
{project.github && (
155+
<div className="flex items-center gap-6 text-muted-foreground">
156+
{project.stars !== undefined && (
157+
<div className="flex items-center gap-2">
158+
<StarIcon className="h-5 w-5" />
159+
<span className="text-lg font-medium">{project.stars.toLocaleString()}</span>
160+
<span className="text-sm">stars</span>
161+
</div>
162+
)}
163+
{project.forks !== undefined && (
164+
<div className="flex items-center gap-2">
165+
<CopyIcon className="h-5 w-5" />
166+
<span className="text-lg font-medium">{project.forks}</span>
167+
<span className="text-sm">forks</span>
168+
</div>
169+
)}
170+
{project.lastUpdated && (
171+
<div className="flex items-center gap-2">
172+
<CalendarIcon className="h-5 w-5" />
173+
<span className="text-sm">Updated {project.lastUpdated}</span>
174+
</div>
175+
)}
176+
</div>
177+
)}
178+
</div>
179+
180+
<Separator />
181+
182+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
183+
{/* Main Content */}
184+
<div className="lg:col-span-2 space-y-8">
185+
{/* Key Features */}
186+
{project.keyFeatures && project.keyFeatures.length > 0 && (
187+
<Card>
188+
<CardHeader>
189+
<CardTitle className="text-xl">✨ Key Features</CardTitle>
190+
</CardHeader>
191+
<CardContent>
192+
<ul className="space-y-3">
193+
{project.keyFeatures.map((feature, idx) => {
194+
const [title, description] = feature.split(': ')
195+
return (
196+
<li key={idx} className="flex gap-3">
197+
<span className="text-primary font-medium"></span>
198+
<div>
199+
<span className="font-medium">{title}:</span>
200+
<span className="text-muted-foreground ml-1">{description}</span>
201+
</div>
202+
</li>
203+
)
204+
})}
205+
</ul>
206+
</CardContent>
207+
</Card>
208+
)}
209+
210+
{/* Full Documentation */}
211+
<Card>
212+
<CardHeader>
213+
<CardTitle className="text-xl">📋 Complete Documentation</CardTitle>
214+
<CardDescription>
215+
Comprehensive project documentation including setup, usage, and contribution guidelines.
216+
</CardDescription>
217+
</CardHeader>
218+
<CardContent>
219+
<div className="prose prose-gray max-w-none dark:prose-invert">
220+
<ReactMarkdown>{project.content}</ReactMarkdown>
221+
</div>
222+
</CardContent>
223+
</Card>
224+
</div>
225+
226+
{/* Sidebar */}
227+
<div className="space-y-6">
228+
{/* Technology Stack */}
229+
{project.techStack && project.techStack.length > 0 && (
230+
<Card>
231+
<CardHeader>
232+
<CardTitle className="text-lg">🛠️ Technology Stack</CardTitle>
233+
</CardHeader>
234+
<CardContent>
235+
<div className="flex flex-wrap gap-2">
236+
{project.techStack.map((tech, idx) => (
237+
<Badge key={idx} variant="outline" className={getTechBadgeColor(tech)}>
238+
{tech}
239+
</Badge>
240+
))}
241+
</div>
242+
</CardContent>
243+
</Card>
244+
)}
245+
246+
{/* Project Details */}
247+
<Card>
248+
<CardHeader>
249+
<CardTitle className="text-lg">📋 Project Details</CardTitle>
250+
</CardHeader>
251+
<CardContent className="space-y-3">
252+
{project.architecture && (
253+
<div>
254+
<span className="text-sm font-medium text-muted-foreground">Architecture:</span>
255+
<p className="text-sm">{project.architecture}</p>
256+
</div>
257+
)}
258+
{project.repositorySize && (
259+
<div>
260+
<span className="text-sm font-medium text-muted-foreground">Repository Size:</span>
261+
<p className="text-sm">{project.repositorySize}</p>
262+
</div>
263+
)}
264+
{project.languages && project.languages.length > 0 && (
265+
<div>
266+
<span className="text-sm font-medium text-muted-foreground">Languages:</span>
267+
<p className="text-sm">{project.languages.join(', ')}</p>
268+
</div>
269+
)}
270+
</CardContent>
271+
</Card>
272+
273+
{/* Contributors */}
274+
{project.contributors && project.contributors.length > 0 && (
275+
<Card>
276+
<CardHeader>
277+
<CardTitle className="text-lg">👥 Contributors</CardTitle>
278+
</CardHeader>
279+
<CardContent>
280+
<div className="space-y-2">
281+
{project.contributors.map((contributor, idx) => (
282+
<div key={idx} className="flex items-center gap-2">
283+
<PersonIcon className="h-4 w-4" />
284+
<span className="text-sm">{contributor}</span>
285+
</div>
286+
))}
287+
</div>
288+
</CardContent>
289+
</Card>
290+
)}
291+
292+
{/* Tags */}
293+
{project.tags && project.tags.length > 0 && (
294+
<Card>
295+
<CardHeader>
296+
<CardTitle className="text-lg">🏷️ Tags</CardTitle>
297+
</CardHeader>
298+
<CardContent>
299+
<div className="flex flex-wrap gap-2">
300+
{project.tags.map((tag, idx) => (
301+
<Badge key={idx} variant="secondary" className="text-xs">
302+
#{tag}
303+
</Badge>
304+
))}
305+
</div>
306+
</CardContent>
307+
</Card>
308+
)}
309+
</div>
310+
</div>
311+
</div>
312+
</Container>
313+
</div>
314+
)
315+
}

0 commit comments

Comments
 (0)