From bce2276dfa49a06bcfcfa28882a70998c191c199 Mon Sep 17 00:00:00 2001 From: seccodesmith Date: Sat, 24 May 2025 19:31:43 +0200 Subject: [PATCH] feat: refactor ProjectCard and Projects components for improved category handling and styling --- src/assets/styles/Project.module.scss | 13 +- src/components/ProjectCard.tsx | 189 ++++++++++++++++++-------- src/data/projectsData.ts | 43 +++++- src/pages/Projects.tsx | 84 +++++------- 4 files changed, 215 insertions(+), 114 deletions(-) diff --git a/src/assets/styles/Project.module.scss b/src/assets/styles/Project.module.scss index e4b1c75..d66485a 100644 --- a/src/assets/styles/Project.module.scss +++ b/src/assets/styles/Project.module.scss @@ -42,7 +42,7 @@ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); } -.cardImg-top { +.cardImgTop { height: 200px; object-fit: cover; } @@ -68,10 +68,18 @@ } /* Category badges */ -.categoryBadge { +.categoryBadgeContainer { position: absolute; top: 1rem; right: 1rem; + + display: flex; + flex-direction: row-reverse; + gap: 0.5rem; + z-index: 1; +} + +.categoryBadge { background-color: rgba(139, 0, 0, 0.9); color: $text-light; padding: 0.25rem 0.75rem; @@ -80,7 +88,6 @@ font-weight: 600; text-transform: uppercase; letter-spacing: 1px; - z-index: 1; } .featuredBadge { diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx index fc339ca..4d92450 100644 --- a/src/components/ProjectCard.tsx +++ b/src/components/ProjectCard.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import React, { useEffect, useState } from 'react' import style from '@styles/Project.module.scss' @@ -12,7 +12,7 @@ export interface ProjectProps { title: string; description: string; image: string; - category: string[]; + category: Category[]; featured?: boolean; technologies: ProjectTech[]; links: { @@ -21,71 +21,146 @@ export interface ProjectProps { }; } +export interface Category { + [key: string]: string; + fullName: string; + shortName: string; + icon: string; +} + interface ProjectCardProps { project: ProjectProps; onOpenDetails: (id: string) => void; } +const ProjectCardTech: React.FC<{ technology: ProjectTech[] }> = ({ technology }) => ( +
+

// TECHNOLOGIES_USED

+
+ {technology.map((tech, index) => ( + + + {tech.name} + + ))} +
+
+) + +const CategoryBadge: React.FC<{ category: Category[] }> = ({ category }) => ( + < div className={style.categoryBadgeContainer} > + { + category.map((cat, index) => ( + + {cat.shortName} + + ))} + +) + +const ProjectLinks: React.FC<{ links: { github?: string; demo?: string }, id: string, onOpenDetails: (id: string) => void }> = ({ links, id, onOpenDetails }) => ( +
+ + + {links.github && ( + + + View Code + + )} + + {links.demo && ( + + + Live Demo + + )} +
+) + export const ProjectCard: React.FC = ({ project, onOpenDetails }) => { const [, setHovered] = useState(false); - - return ( -
setHovered(true)} - onMouseLeave={() => setHovered(false)} - > -
- {project.title} -
{project.category[0]}
- {project.featured && ( -
- - Featured -
- )} -
-
-

{project.title}

-

- {project.description} -

-
-

// TECHNOLOGIES_USED

-
- {project.technologies.map((tech, index) => ( - - - {tech.name} - - ))} + + useEffect(() => { + const createEmbers = () => { + const embersContainer = document.querySelectorAll(`.${style.cardImgTop}`); + + embersContainer.forEach((container) => { + for (let i = 0; i < 5; i++) { + const ember = document.createElement('div'); + ember.className = style.emberParticle; + + ember.style.left = `${Math.random() * 100}%`; + ember.style.bottom = '0'; + ember.style.animationDuration = `${Math.random() * 2 + 3}s`; + ember.style.animationDelay = `${Math.random() * 3}s`; + container.appendChild(ember); + } + }); + }; + createEmbers(); + }, []); + + if (project.featured) { + return ( +
+
setHovered(true)} + onMouseLeave={() => setHovered(false)} + > +
+
+ {project.title} +
+ + Featured +
+ +
+
+
+

{project.title}

+

+ {project.description} +

+ + +
+
-
- - - {project.links.github && ( - - - View Code - - )} - - {project.links.demo && ( - - - Live Demo - - )} +
+ ) + } + + + return ( +
+
setHovered(true)} + onMouseLeave={() => setHovered(false)}> +
+ {project.title} + +
+
+

{project.title}

+

+ {project.description} +

+ +
) -} \ No newline at end of file +}; diff --git a/src/data/projectsData.ts b/src/data/projectsData.ts index 4132324..aa6a8ae 100644 --- a/src/data/projectsData.ts +++ b/src/data/projectsData.ts @@ -1,4 +1,33 @@ -import type { ProjectProps } from "../components/ProjectCard"; +import type { ProjectProps, Category } from "../components/ProjectCard"; + +type CategoryMap = Record +export const Categories: CategoryMap = { + Embedded: { + fullName: "Embedded Systems", + shortName: "Embedded", + icon: "fas fa-microchip", + }, + Web: { + fullName: "Web Development", + shortName: "Web", + icon: "fas fa-code", + }, + ML: { + fullName: "Machine Learning", + shortName: "ML", + icon: "fas fa-robot", + }, + IoT: { + fullName: "Internet of Things", + shortName: "IoT", + icon: "fas fa-network-wired", + }, + Sec: { + fullName: "Cybersecurity", + shortName: "Security", + icon: "fas fa-shield-alt", + }, +} export const projectsData: ProjectProps[] = [ { @@ -6,7 +35,7 @@ export const projectsData: ProjectProps[] = [ title: "ShadowGuardian: Secure IoT Monitoring System", description: "A comprehensive security monitoring system built on STM32 microcontrollers with end-to-end encryption and low-power operation. Integrates with home automation systems and provides real-time alerts through a mobile application.", image: "/images/projects/shadowguardian.jpg", - category: ["iot", "embedded"], + category: [Categories["IoT"], Categories["Embedded"]], featured: true, technologies: [ { name: "C++", icon: "devicon-cplusplus-plain tech-icon" }, @@ -25,7 +54,7 @@ export const projectsData: ProjectProps[] = [ title: "PyroSight: Thermal Anomaly Detection", description: "An intelligent system that uses computer vision and thermal imaging to detect anomalies in industrial equipment. Employs PyTorch for deep learning to predict potential failures before they occur.", image: "/images/projects/pyrosight.jpg", - category: ["ml", "iot"], + category: [Categories["ML"], Categories["IoT"]], technologies: [ { name: "Python", icon: "devicon-python-plain tech-icon" }, { name: "PyTorch", icon: "devicon-pytorch-original tech-icon" }, @@ -41,7 +70,7 @@ export const projectsData: ProjectProps[] = [ title: "NexusFlow: DevOps Dashboard", description: "A real-time monitoring dashboard for DevOps teams that visualizes CI/CD pipelines, infrastructure metrics, and deployment statuses. Built with Blazor for seamless updates without page refreshes.", image: "/images/projects/nexusflow.jpg", - category: ["web"], + category: [Categories["Web"]], technologies: [ { name: "C#", icon: "devicon-csharp-plain tech-icon" }, { name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" }, @@ -58,7 +87,7 @@ export const projectsData: ProjectProps[] = [ title: "EtherWhisper: Secure Communication Node", description: "A low-power, long-range communication node for IoT networks with military-grade encryption and mesh capabilities. Perfect for remote sensing applications in harsh environments.", image: "/images/projects/etherwhisper.jpg", - category: ["embedded", "iot"], + category: [Categories["Embedded"], Categories["IoT"]], technologies: [ { name: "C", icon: "devicon-c-plain tech-icon" }, { name: "STM32", icon: "fas fa-memory tech-icon" }, @@ -74,7 +103,7 @@ export const projectsData: ProjectProps[] = [ title: "DataForge: Advanced Analytics Platform", description: "A comprehensive data analytics platform with interactive visualization tools and automated reporting features. Integrates with various data sources and provides ML-powered insights.", image: "/images/projects/dataforge.jpg", - category: ["web"], + category: [Categories["Web"]], technologies: [ { name: "Python", icon: "devicon-python-plain tech-icon" }, { name: "Django", icon: "devicon-django-plain tech-icon" }, @@ -91,7 +120,7 @@ export const projectsData: ProjectProps[] = [ title: "VisionKeeper: Object Recognition System", description: "A real-time object recognition system optimized for edge devices. Capable of identifying and tracking objects with high accuracy even in challenging lighting conditions.", image: "/images/projects/visionkeeper.jpg", - category: ["ml"], + category: [Categories["ML"]], technologies: [ { name: "Python", icon: "devicon-python-plain tech-icon" }, { name: "PyTorch", icon: "devicon-pytorch-original tech-icon" }, diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx index faf2fa0..d59354f 100644 --- a/src/pages/Projects.tsx +++ b/src/pages/Projects.tsx @@ -2,88 +2,78 @@ import { useState } from 'react'; import { PageHeader } from '../components/PageHeader'; import { ProjectCard, type ProjectProps } from '../components/ProjectCard'; import { ProjectModal } from '../components/ProjectModal'; -import { projectsData } from '../data/projectsData'; +import { projectsData, Categories } from '../data/projectsData'; +import { styleText } from 'util'; + +import style from '@styles/Project.module.scss' + export const Projects = () => { const [activeFilter, setActiveFilter] = useState('all'); const [selectedProject, setSelectedProject] = useState(null); - - const filteredProjects = activeFilter === 'all' - ? projectsData - : projectsData.filter(project => project.category.includes(activeFilter)); - + + const filteredProjects = activeFilter === 'all' + ? projectsData + : projectsData.filter(project => project.category.some(cat => cat.shortName.toLowerCase() === activeFilter)); + + const categories = Categories; + const featuredProject = projectsData.find(project => project.featured); - + const regularProjects = filteredProjects.filter(project => !project.featured); - + const handleFilterClick = (filter: string) => { setActiveFilter(filter); }; - + const handleOpenDetails = (id: string) => { const project = projectsData.find(p => p.id === id); if (project) { setSelectedProject(project); } }; - + const handleCloseDetails = () => { setSelectedProject(null); }; - + return ( <> - - +
{/* Projects Filter */} -
- - - - - + {Object.values(categories).map((category) => ( + + ))}
- {featuredProject && (activeFilter === 'all' || featuredProject.category.includes(activeFilter)) && ( + {featuredProject && (activeFilter === 'all' || featuredProject.category.some(cat => cat.shortName.toLowerCase() === activeFilter)) && (
)} {regularProjects.map(project => ( -
- -
+ + ))}
@@ -99,7 +89,7 @@ export const Projects = () => {
- + {selectedProject && ( )}