Skip to content

Commit c6c40a6

Browse files
committed
Fixed the layout and added animation removed skills hardcode
1 parent 482c1f6 commit c6c40a6

File tree

2 files changed

+226
-4
lines changed

2 files changed

+226
-4
lines changed

app/api/skills/route.ts

Lines changed: 193 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,145 @@
11
import { NextResponse } from "next/server"
2+
import { Client } from "@notionhq/client"
3+
4+
const notion = new Client({
5+
auth: process.env.NOTION_INTEGRATION_SECRET,
6+
})
7+
8+
const SKILLS_DATABASE_ID = "93762143-ef43-4c4b-be97-cb7e7d2dd2f4"
29

310
// Removed force-static export for Vercel deployment
411

512
export async function GET() {
613
try {
7-
// Hard-coded skills data
8-
const skills = [
14+
// Fetch skills from Notion database
15+
const response = await notion.databases.query({
16+
database_id: SKILLS_DATABASE_ID,
17+
})
18+
19+
// Group skills by category
20+
const skillsMap: Record<string, any> = {}
21+
22+
response.results.forEach((page: any) => {
23+
const properties = page.properties
24+
const name =
25+
properties.Name?.title?.[0]?.plain_text ||
26+
properties.name?.title?.[0]?.plain_text ||
27+
"Untitled Skill"
28+
29+
const category =
30+
properties.category?.select?.name ||
31+
properties.Category?.select?.name ||
32+
"Uncategorized"
33+
34+
const icon =
35+
properties.icon?.rich_text?.[0]?.plain_text ||
36+
properties.Icon?.rich_text?.[0]?.plain_text ||
37+
"Code"
38+
39+
const display =
40+
properties.display?.checkbox ||
41+
properties.Display?.checkbox ||
42+
true // Default to true (checked) if not set
43+
44+
// Only process skills that are checked (display === true) and have a valid category
45+
if (display === true && category !== "Uncategorized" && category) {
46+
// Create category group if it doesn't exist
47+
if (!skillsMap[category]) {
48+
const categoryId = category.toLowerCase().replace(/\s+/g, '-').replace(/&/g, '').replace(/[^a-z0-9-]/g, '')
49+
skillsMap[category] = {
50+
id: categoryId,
51+
title: category,
52+
icon: icon,
53+
skills: []
54+
}
55+
}
56+
57+
// Add skill name to the category
58+
skillsMap[category].skills.push(name)
59+
}
60+
})
61+
62+
// Convert to array and format skills into groups of related items
63+
const skills = Object.values(skillsMap).map((category: any) => {
64+
// Group skills into logical chunks for display
65+
const groupedSkills: string[] = []
66+
const skillsList = category.skills
67+
68+
if (category.title === "Programming Languages") {
69+
// Group programming languages logically
70+
const cLanguages = skillsList.filter((s: string) => s.match(/^(C|C\+\+|C#)$/))
71+
const pythonR = skillsList.filter((s: string) => s.match(/^(Python|R|Java)$/))
72+
const webLangs = skillsList.filter((s: string) => s.match(/^(JavaScript|TypeScript|HTML|CSS)$/))
73+
const databases = skillsList.filter((s: string) => s.match(/^(SQL|NoSQL)$/))
74+
75+
if (cLanguages.length > 0 || pythonR.length > 0) {
76+
groupedSkills.push([...cLanguages, ...pythonR].join(", "))
77+
}
78+
if (webLangs.length > 0) {
79+
groupedSkills.push(webLangs.join(", "))
80+
}
81+
if (databases.length > 0) {
82+
groupedSkills.push(databases.join(", "))
83+
}
84+
} else if (category.title === "Developer Tools") {
85+
// Group dev tools logically
86+
const ides = skillsList.filter((s: string) => s.match(/(PyCharm|Eclipse|Jupyter|XCode|Visual Studio|VSCode|Code Blocks)/i))
87+
const versionControl = skillsList.filter((s: string) => s.match(/(Git|GitHub|Robot Framework)/i))
88+
89+
if (ides.length > 0) {
90+
// Split IDEs into chunks of 3-4
91+
for (let i = 0; i < ides.length; i += 3) {
92+
groupedSkills.push(ides.slice(i, i + 3).join(", "))
93+
}
94+
}
95+
if (versionControl.length > 0) {
96+
groupedSkills.push(versionControl.join(", "))
97+
}
98+
} else if (category.title === "Libraries & Frameworks") {
99+
// Group libraries logically
100+
const aiLibs = skillsList.filter((s: string) => s.match(/(OpenCV|TensorFlow|PyTorch|Scikit-learn)/i))
101+
const dataLibs = skillsList.filter((s: string) => s.match(/(Seaborn|Selenium|Pandas|NumPy|Matplotlib)/i))
102+
const webFrameworks = skillsList.filter((s: string) => s.match(/(React|Next\.js|OpenAI Gym|Nengo)/i))
103+
104+
if (aiLibs.length > 0) {
105+
groupedSkills.push(aiLibs.join(", "))
106+
}
107+
if (dataLibs.length > 0) {
108+
groupedSkills.push(dataLibs.join(", "))
109+
}
110+
if (webFrameworks.length > 0) {
111+
groupedSkills.push(webFrameworks.join(", "))
112+
}
113+
} else if (category.title === "DevOps") {
114+
// Group DevOps tools logically
115+
const cicd = skillsList.filter((s: string) => s.match(/(CI\/CD|GitHub Actions|CodePipeline)/i))
116+
const automation = skillsList.filter((s: string) => s.match(/(Jenkins|Ansible|Docker|Kubernetes)/i))
117+
const iac = skillsList.filter((s: string) => s.match(/(Infrastructure|Terraform)/i))
118+
119+
if (cicd.length > 0) {
120+
groupedSkills.push(cicd.join(", "))
121+
}
122+
if (automation.length > 0) {
123+
groupedSkills.push(automation.join(", "))
124+
}
125+
if (iac.length > 0) {
126+
groupedSkills.push(iac.join(", "))
127+
}
128+
} else {
129+
// For other categories, group in chunks of 2-3
130+
for (let i = 0; i < skillsList.length; i += 2) {
131+
groupedSkills.push(skillsList.slice(i, i + 2).join(", "))
132+
}
133+
}
134+
135+
return {
136+
...category,
137+
skills: groupedSkills.length > 0 ? groupedSkills : skillsList
138+
}
139+
})
140+
141+
// Fallback to hardcoded data if Notion fails
142+
const fallbackSkills = [
9143
{
10144
id: "programming",
11145
title: "Programming Languages",
@@ -57,9 +191,64 @@ export async function GET() {
57191
]
58192

59193
// Return the skills data as JSON
60-
return NextResponse.json({ skills }, { status: 200 })
194+
return NextResponse.json({
195+
skills: skills.length > 0 ? skills : fallbackSkills
196+
}, { status: 200 })
61197
} catch (error) {
62198
console.error("Error fetching skills:", error)
63-
return NextResponse.json({ error: "Failed to fetch skills data" }, { status: 500 })
199+
200+
// Return fallback data on error
201+
const fallbackSkills = [
202+
{
203+
id: "programming",
204+
title: "Programming Languages",
205+
icon: "Code",
206+
skills: ["C, C++, C#, Java, R and Python", "JavaScript, TypeScript, HTML, CSS", "SQL, NoSQL"],
207+
},
208+
{
209+
id: "developer-tools",
210+
title: "Developer Tools",
211+
icon: "Terminal",
212+
skills: [
213+
"Pycharm, Eclipse, Jupyter Notebook",
214+
"XCode, Visual Studio, VSCode, Code Blocks",
215+
"Robot Framework, Git, GitHub",
216+
],
217+
},
218+
{
219+
id: "libraries",
220+
title: "Libraries & Frameworks",
221+
icon: "Library",
222+
skills: [
223+
"OpenCV, TensorFlow, PyTorch, Scikit-learn",
224+
"Seaborn, Selenium, Pandas, NumPy, Matplotlib",
225+
"OpenAIGym, Nengo, React, Next.js",
226+
],
227+
},
228+
{
229+
id: "devops",
230+
title: "DevOps",
231+
icon: "Server",
232+
skills: [
233+
"CI/CD, GitHub Actions, CodePipeline",
234+
"Jenkins, Ansible, Docker, Kubernetes",
235+
"Infrastructure as Code, Terraform",
236+
],
237+
},
238+
{
239+
id: "database",
240+
title: "Database",
241+
icon: "Database",
242+
skills: ["PostgreSQL, MySQL, Aurora", "MongoDB, DynamoDB"],
243+
},
244+
{
245+
id: "cloud",
246+
title: "Cloud",
247+
icon: "Cloud",
248+
skills: ["AWS (EC2, S3, Lambda, etc.)", "GCP, Azure"],
249+
},
250+
]
251+
252+
return NextResponse.json({ skills: fallbackSkills }, { status: 500 })
64253
}
65254
}

app/components/Projects.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export default function Projects() {
2727
const [isLoading, setIsLoading] = useState(true)
2828
const [error, setError] = useState<string | null>(null)
2929
const [selectedProject, setSelectedProject] = useState<Project | null>(null)
30+
const [imageLoadingStates, setImageLoadingStates] = useState<Record<string, boolean>>({})
31+
const [modalImageLoading, setModalImageLoading] = useState(false)
3032

3133
useEffect(() => {
3234
async function fetchProjects() {
@@ -40,6 +42,13 @@ export default function Projects() {
4042
const data = await response.json()
4143
setProjects(data.projects)
4244
setFilteredProjects(data.projects)
45+
46+
// Initialize loading states for all projects
47+
const initialLoadingStates = data.projects.reduce((acc: Record<string, boolean>, project: Project) => {
48+
acc[project.id] = true
49+
return acc
50+
}, {})
51+
setImageLoadingStates(initialLoadingStates)
4352
} catch (err) {
4453
console.error("Error fetching projects:", err)
4554
setError("Failed to load projects. Please try again later.")
@@ -66,9 +75,19 @@ export default function Projects() {
6675
}
6776
}
6877

78+
// Handle image loading
79+
const handleImageLoad = (projectId: string) => {
80+
setImageLoadingStates(prev => ({ ...prev, [projectId]: false }))
81+
}
82+
83+
const handleImageError = (projectId: string) => {
84+
setImageLoadingStates(prev => ({ ...prev, [projectId]: false }))
85+
}
86+
6987
// Open project details modal
7088
const openProjectDetails = (project: Project) => {
7189
setSelectedProject(project)
90+
setModalImageLoading(true)
7291
document.body.style.overflow = "hidden" // Prevent scrolling when modal is open
7392
}
7493

@@ -139,15 +158,22 @@ export default function Projects() {
139158
className="bg-gray-800/50 rounded-lg overflow-hidden border border-gray-700 hover:border-blue-500 transition-all duration-300 flex flex-col h-full group"
140159
>
141160
<div className="relative h-48 overflow-hidden">
161+
{imageLoadingStates[project.id] && (
162+
<div className="absolute inset-0 flex items-center justify-center bg-gray-800 z-10">
163+
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
164+
</div>
165+
)}
142166
<Image
143167
src={project.image || "/placeholder.svg?height=400&width=600&query=project"}
144168
alt={project.title}
145169
fill
146170
className="object-cover transition-transform duration-500 group-hover:scale-110"
147171
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
172+
onLoad={() => handleImageLoad(project.id)}
148173
onError={(e) => {
149174
console.warn(`Failed to load image for ${project.title}:`, project.image)
150175
e.currentTarget.src = "/project-management-team.png"
176+
handleImageError(project.id)
151177
}}
152178
/>
153179
{project.featured && (
@@ -213,15 +239,22 @@ export default function Projects() {
213239
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4">
214240
<div className="bg-gray-800 rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto">
215241
<div className="relative h-64 md:h-80">
242+
{modalImageLoading && (
243+
<div className="absolute inset-0 flex items-center justify-center bg-gray-800 z-10">
244+
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
245+
</div>
246+
)}
216247
<Image
217248
src={selectedProject.image || "/placeholder.svg?height=400&width=600&query=project"}
218249
alt={selectedProject.title}
219250
fill
220251
className="object-cover"
221252
sizes="(max-width: 768px) 100vw, 800px"
253+
onLoad={() => setModalImageLoading(false)}
222254
onError={(e) => {
223255
console.warn(`Failed to load modal image for ${selectedProject.title}:`, selectedProject.image)
224256
e.currentTarget.src = "/project-management-team.png"
257+
setModalImageLoading(false)
225258
}}
226259
/>
227260
<button

0 commit comments

Comments
 (0)