Merge pull request 'Fix-projects' (#8) from Fix-projects into main
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
@@ -73,7 +73,7 @@
|
|||||||
color: $accent-fire;
|
color: $accent-fire;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
animation: fall linear infinite;
|
animation: fall linear infinite;
|
||||||
opacity: 0.8;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fall {
|
@keyframes fall {
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
@use './variables' as *;
|
||||||
|
|
||||||
|
.filterContainer {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid $card-border;
|
||||||
|
color: $text-ash;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 0 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton:hover,
|
||||||
|
.filterButton.active {
|
||||||
|
background-color: rgba(255, 100, 0, 0.1);
|
||||||
|
border-color: rgba(255, 100, 0, 0.3);
|
||||||
|
color: $accent-fire;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterButton.active {
|
||||||
|
border-color: $accent-fire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project Cards */
|
||||||
|
.card {
|
||||||
|
background-color: $card-bg;
|
||||||
|
border: 1px solid $card-border;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
border-color: rgba(255, 100, 0, 0.3);
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardImgTop {
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardText {
|
||||||
|
color: $text-ash;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuredCard {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuredCard .cardImgTop {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category badges */
|
||||||
|
.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;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuredBadge {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
background: linear-gradient(45deg, $accent-dragon, $accent-fire);
|
||||||
|
color: $text-light;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tech stack */
|
||||||
|
.techTitle {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0.625rem;
|
||||||
|
color: $text-ash;
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techStack {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techItem {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
color: $text-ash;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techItem:hover {
|
||||||
|
background-color: rgba(255, 100, 0, 0.1);
|
||||||
|
color: $accent-fire;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.techIcon {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: $accent-fire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project links */
|
||||||
|
.btnOutlineSecondary {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
border-color: $card-border;
|
||||||
|
color: $text-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnOutlineSecondary:hover {
|
||||||
|
background-color: rgba(255, 100, 0, 0.1);
|
||||||
|
border-color: $accent-fire;
|
||||||
|
color: $text-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnIcon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
color: $accent-fire;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View All Button */
|
||||||
|
.btnPrimary {
|
||||||
|
background: linear-gradient(45deg, $accent-dragon, $accent-fire);
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
box-shadow: 0 4px 15px rgba(139, 0, 0, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnPrimary:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 7px 25px rgba(139, 0, 0, 0.5);
|
||||||
|
background-color: $accent-fire;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Styling */
|
||||||
|
.modalContent {
|
||||||
|
background-color: $card-bg;
|
||||||
|
border: 1px solid $card-border;
|
||||||
|
color: $text-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalHeader {
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalTitle {
|
||||||
|
color: $text-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalFooter {
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btnClose {
|
||||||
|
filter: invert(1) grayscale(100%) brightness(200%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresList {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
color: $text-ash;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresList li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuresList li::before {
|
||||||
|
content: "•";
|
||||||
|
color: $accent-fire;
|
||||||
|
position: absolute;
|
||||||
|
left: -1rem;
|
||||||
|
top: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techChip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techChip:hover {
|
||||||
|
background-color: rgba(255, 100, 0, 0.1);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.techChip-icon {
|
||||||
|
color: $accent-fire;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techChip-name {
|
||||||
|
color: $text-ash;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.galleryItem {
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.galleryItem:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.galleryImage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Binary background effect */
|
||||||
|
.binaryLine {
|
||||||
|
position: absolute;
|
||||||
|
font-family: 'Fira Code', monospace;
|
||||||
|
color: $accent-fire;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.15;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ember particle effect */
|
||||||
|
.emberParticle {
|
||||||
|
position: absolute;
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
background-color: $accent-fire;
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 0 5px $accent-fire;
|
||||||
|
animation: float 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0) rotate(0deg);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(-100px) rotate(360deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background-color: $primary-dark;
|
||||||
|
padding: 3rem 0;
|
||||||
|
border-top: 1px solid rgba(255, 100, 0, 0.2);
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.socialLinks {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.socialIcon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: $text-ash;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.socialIcon:hover {
|
||||||
|
color: $accent-fire;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
color: $text-ash;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.pageTitle {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featuredCard .row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.pageTitle {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techStack {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.pageTitle {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectLinks {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectLinks .btn {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,7 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import styles from '@styles/BlogCard.module.scss';
|
import styles from '@styles/BlogCard.module.scss';
|
||||||
|
|
||||||
export interface BlogPostProps {
|
import type {BlogPostProps} from '../untils/BlogPostProps';
|
||||||
id: string;
|
|
||||||
slug: string;
|
|
||||||
title: string;
|
|
||||||
excerpt: string;
|
|
||||||
image: string;
|
|
||||||
category: string;
|
|
||||||
date: string;
|
|
||||||
author: string;
|
|
||||||
commentCount: number;
|
|
||||||
readTime: string;
|
|
||||||
featured?: boolean;
|
|
||||||
tags: string[];
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BlogCardProps {
|
interface BlogCardProps {
|
||||||
post: BlogPostProps;
|
post: BlogPostProps;
|
||||||
@@ -26,7 +12,7 @@ export const BlogCard: React.FC<BlogCardProps> = ({ post }) => {
|
|||||||
<div className={`card ${styles.card} ${post.featured ? styles.featuredCard : ''}`}>
|
<div className={`card ${styles.card} ${post.featured ? styles.featuredCard : ''}`}>
|
||||||
<div className="position-relative">
|
<div className="position-relative">
|
||||||
<img src={post.image} className={`card-img-top ${post.featured ? styles.featuredImg : ''}`} alt={post.title} />
|
<img src={post.image} className={`card-img-top ${post.featured ? styles.featuredImg : ''}`} alt={post.title} />
|
||||||
<div className="category-badge">{post.category}</div>
|
<div className={styles.categoryBadge}>{post.category}</div>
|
||||||
|
|
||||||
{post.featured && (
|
{post.featured && (
|
||||||
<div className={`featured-badge ${styles.featuredBadge}`}>
|
<div className={`featured-badge ${styles.featuredBadge}`}>
|
||||||
|
|||||||
+122
-71
@@ -1,90 +1,141 @@
|
|||||||
import { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
export interface ProjectTech {
|
import style from '@styles/Project.module.scss'
|
||||||
name: string;
|
import type {ProjectTech, Category, ProjectProps } from '../untils/ProjectProps'
|
||||||
icon: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProjectProps {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
image: string;
|
|
||||||
category: string[];
|
|
||||||
featured?: boolean;
|
|
||||||
technologies: ProjectTech[];
|
|
||||||
links: {
|
|
||||||
github?: string;
|
|
||||||
demo?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProjectCardProps {
|
interface ProjectCardProps {
|
||||||
project: ProjectProps;
|
project: ProjectProps;
|
||||||
onOpenDetails: (id: string) => void;
|
onOpenDetails: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ProjectCardTech: React.FC<{ technology: ProjectTech[] }> = ({ technology }) => (
|
||||||
|
<div>
|
||||||
|
<h4 className={style.techTitle}>// TECHNOLOGIES_USED</h4>
|
||||||
|
<div className={style.techStack}>
|
||||||
|
{technology.map((tech, index) => (
|
||||||
|
<span className={`${style.techItem}`} key={index}>
|
||||||
|
<i className={`${tech.icon} ${style.techIcon}`}></i>
|
||||||
|
<span>{tech.name}</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const CategoryBadge: React.FC<{ category: Category[] }> = ({ category }) => (
|
||||||
|
< div className={style.categoryBadgeContainer} >
|
||||||
|
{
|
||||||
|
category.map((cat, index) => (
|
||||||
|
<span key={index} className={`${style.categoryBadge}`}>
|
||||||
|
{cat.shortName}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ProjectLinks: React.FC<{ links: { github?: string; demo?: string }, id: string, onOpenDetails: (id: string) => void }> = ({ links, id, onOpenDetails }) => (
|
||||||
|
<div className={`mt-auto d-flex gap-2 flex-wrap project-links ${style.projectLinks}`}>
|
||||||
|
<button
|
||||||
|
className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`}
|
||||||
|
onClick={() => onOpenDetails(id)}
|
||||||
|
data-bs-toggle="modal" data-bs-target="#shadowguardianModal">
|
||||||
|
<i className={`fas fa-info-circle ${style.btnIcon}`} ></i>
|
||||||
|
Project Details
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{links.github && (
|
||||||
|
<a href={links.github} className={`btn btn-outline-secondary ${style.btnOutlineSecondary} `}>
|
||||||
|
<i className={`fab fa-github ${style.btnIcon}`} ></i>
|
||||||
|
View Code
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{links.demo && (
|
||||||
|
<a href={links.demo} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`}>
|
||||||
|
<i className={`fas fa-external-link-alt ${style.btnIcon}`}></i>
|
||||||
|
Live Demo
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
export const ProjectCard: React.FC<ProjectCardProps> = ({ project, onOpenDetails }) => {
|
export const ProjectCard: React.FC<ProjectCardProps> = ({ project, onOpenDetails }) => {
|
||||||
const [, setHovered] = useState(false);
|
const [, setHovered] = useState(false);
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div
|
const createEmbers = () => {
|
||||||
className={`card h-100 ${project.featured ? 'featured-card' : ''}`}
|
const embersContainer = document.querySelectorAll(`.${style.cardImgTop}`);
|
||||||
data-category={project.category.join(',')}
|
|
||||||
onMouseEnter={() => setHovered(true)}
|
|
||||||
onMouseLeave={() => setHovered(false)}
|
|
||||||
>
|
|
||||||
<div className="position-relative">
|
|
||||||
<img src={project.image} className="card-img-top" alt={project.title} />
|
|
||||||
<div className="category-badge">{project.category[0]}</div>
|
|
||||||
|
|
||||||
{project.featured && (
|
embersContainer.forEach((container) => {
|
||||||
<div className="featured-badge">
|
for (let i = 0; i < 5; i++) {
|
||||||
<i className="fas fa-star"></i>
|
const ember = document.createElement('div');
|
||||||
<span>Featured</span>
|
ember.className = style.emberParticle;
|
||||||
</div>
|
|
||||||
)}
|
ember.style.left = `${Math.random() * 100}%`;
|
||||||
</div>
|
ember.style.bottom = '0';
|
||||||
<div className="card-body d-flex flex-column">
|
ember.style.animationDuration = `${Math.random() * 2 + 3}s`;
|
||||||
<h3 className="card-title">{project.title}</h3>
|
ember.style.animationDelay = `${Math.random() * 3}s`;
|
||||||
<p className="card-text">
|
container.appendChild(ember);
|
||||||
{project.description}
|
}
|
||||||
</p>
|
});
|
||||||
<div>
|
};
|
||||||
<h4 className="tech-title">// TECHNOLOGIES_USED</h4>
|
createEmbers();
|
||||||
<div className="tech-stack">
|
}, []);
|
||||||
{project.technologies.map((tech, index) => (
|
|
||||||
<span className="tech-item" key={index}>
|
if (project.featured) {
|
||||||
<i className={tech.icon}></i>
|
return (
|
||||||
<span>{tech.name}</span>
|
<div className={`"col-12 mb-4`}>
|
||||||
</span>
|
<div
|
||||||
))}
|
className={`card ${style.card} ${style.featuredCard}`}
|
||||||
|
data-category={project.category.join(',')}
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
>
|
||||||
|
<div className={`row g-0`}>
|
||||||
|
<div className={`col-md-6 position-relative`}>
|
||||||
|
<img src={project.image} className={`img-fluid rounded-start h-100 w-100 object-fit-cover`} alt={project.title} />
|
||||||
|
<div className={style.featuredBadge}>
|
||||||
|
<i className="fas fa-star"></i>
|
||||||
|
<span>Featured</span>
|
||||||
|
</div>
|
||||||
|
<CategoryBadge category={project.category} />
|
||||||
|
</div>
|
||||||
|
<div className={`col-md-6`}>
|
||||||
|
<div className={`card-body d-flex flex-column h-100 ${style.cardBody}`}>
|
||||||
|
<h3 className={`${style.cardTitle} card-title`}>{project.title}</h3>
|
||||||
|
<p className={`${style.cardText} card-text`}>
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
<ProjectCardTech technology={project.technologies} />
|
||||||
|
<ProjectLinks links={project.links} id={project.id} onOpenDetails={onOpenDetails} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-auto d-flex gap-2 flex-wrap project-links">
|
</div>
|
||||||
<button
|
)
|
||||||
className="btn btn-outline-secondary"
|
}
|
||||||
onClick={() => onOpenDetails(project.id)}
|
|
||||||
>
|
|
||||||
<i className="fas fa-info-circle me-2"></i>
|
|
||||||
Project Details
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{project.links.github && (
|
|
||||||
<a href={project.links.github} className="btn btn-outline-secondary">
|
|
||||||
<i className="fab fa-github me-2"></i>
|
|
||||||
View Code
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{project.links.demo && (
|
return (
|
||||||
<a href={project.links.demo} className="btn btn-outline-secondary">
|
<div className={`col-md-6 col-lg-4 mb-4`}>
|
||||||
<i className="fas fa-external-link-alt me-2"></i>
|
<div className={`card ${style.card} h-100`}
|
||||||
Live Demo
|
data-category={project.category.join(',')}
|
||||||
</a>
|
onMouseEnter={() => setHovered(true)}
|
||||||
)}
|
onMouseLeave={() => setHovered(false)}>
|
||||||
|
<div className={`position-relative`}>
|
||||||
|
<img src={project.image} className={style.cardImgTop} alt={project.title} />
|
||||||
|
<CategoryBadge category={project.category} />
|
||||||
|
</div>
|
||||||
|
<div className={`card-body d-flex flex-column ${style.cardBody}`}>
|
||||||
|
<h3 className={`${style.cardTitle} card-title`}>{project.title}</h3>
|
||||||
|
<p className={`${style.cardText} card-text`}>
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
<ProjectCardTech technology={project.technologies} />
|
||||||
|
<ProjectLinks links={project.links} id={project.id} onOpenDetails={onOpenDetails} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|||||||
+151
-49
@@ -1,56 +1,158 @@
|
|||||||
import type { ProjectProps } from './ProjectCard';
|
import type { ProjectProps } from '../untils/ProjectProps';
|
||||||
|
|
||||||
|
import style from '@styles/Project.module.scss'
|
||||||
|
|
||||||
|
|
||||||
interface ProjectModalProps {
|
interface ProjectModalProps {
|
||||||
project: ProjectProps | null;
|
project: ProjectProps | null;
|
||||||
onClose: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProjectModal: React.FC<ProjectModalProps> = ({ project, onClose }) => {
|
export const ProjectModal: React.FC<ProjectModalProps> = ({ project }) => {
|
||||||
if (!project) return null;
|
if (!project) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal fade show" style={{ display: 'block' }} tabIndex={-1}>
|
<div className="modal fade" id="shadowguardianModal" tabIndex={-1} aria-labelledby="shadowguardianModalLabel" aria-hidden="true">
|
||||||
<div className="modal-dialog modal-xl modal-dialog-scrollable">
|
<div className="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
<div className="modal-content">
|
<div className={`modal-content ${style.modalContent}`}>
|
||||||
<div className="modal-header">
|
<div className={`modal-header ${style.modalHeader}`}>
|
||||||
<h5 className="modal-title">{project.title}</h5>
|
<h5 className={style.modalHeader} id="shadowguardianModalLabel">
|
||||||
<button type="button" className="btn-close" onClick={onClose}></button>
|
{project.title}
|
||||||
</div>
|
</h5>
|
||||||
<div className="modal-body">
|
<button type="button" className={`btn-close ${style.btnClose}`} data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
<div className="mb-4">
|
</div>
|
||||||
<img src={project.image} className="img-fluid rounded mb-3" alt={project.title} />
|
<div className={`modal-body ${style.modalBody}`}>
|
||||||
|
<div className="mb-4">
|
||||||
|
<img src={project.image} className="img-fluid rounded mb-3" alt={project.title} />
|
||||||
|
<div className={`row ${style.row}`}>
|
||||||
|
<div className="col-md-8">
|
||||||
|
{
|
||||||
|
project.projectDetails ? project.projectDetails.descriptions?.map((desc, index) => (
|
||||||
|
<p key={index} className="mb-3">{desc}</p>
|
||||||
|
)) :
|
||||||
|
<p>{project.description}</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="col-md-4">
|
||||||
|
<div className={`card h-100 ${style.card}`}>
|
||||||
|
<div className={`card-header ${style.cardHeader}`}>
|
||||||
|
<h5 className="mb-0">Project Overview</h5>
|
||||||
|
</div>
|
||||||
|
<div className="card-body">
|
||||||
|
{project.projectDetails && project.projectDetails.startDate && project.projectDetails.endDate ? (
|
||||||
|
<p><strong>Duration:</strong> {new Date(project.projectDetails.startDate).toLocaleDateString()} - {new Date(project.projectDetails.endDate).toLocaleDateString()}</p>
|
||||||
|
) : (
|
||||||
|
<p><strong>Duration:</strong> Ongoing</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
project.projectDetails && project.projectDetails.role ? (
|
||||||
|
<p><strong>Role:</strong> {project.projectDetails.role}</p>
|
||||||
|
) : (
|
||||||
|
<p><strong>Role:</strong> Developer</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
project.projectDetails && project.projectDetails.client ? (
|
||||||
|
<p><strong>Client:</strong> {project.projectDetails.client}</p>
|
||||||
|
) : (
|
||||||
|
<p><strong>Client:</strong> Internal Project</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
project.projectDetails?.keyFeatures && project.projectDetails.keyFeatures.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h4 className="mt-4 mb-3">Key Features</h4>
|
||||||
|
{
|
||||||
|
project.projectDetails?.keyFeatures.map((feature, index) => (
|
||||||
|
<p key={index} className="mb-2">
|
||||||
|
<i className="fas fa-check-circle text-success me-2"></i>
|
||||||
|
{feature}
|
||||||
|
</p>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<h4 className="mt-4 mb-3">Technologies Used</h4>
|
||||||
|
<div className="mb-4"></div>
|
||||||
|
{
|
||||||
|
project.projectDetails?.fullTechStack && project.projectDetails?.fullTechStack?.length > 0 ? (
|
||||||
|
<>
|
||||||
|
{project.projectDetails.fullTechStack.map((tech, index) => (
|
||||||
|
<span key={index} className={`tech-chip ${style.techChip}`}>
|
||||||
|
<i className={`${tech.icons} tech-chip-icon`}></i>
|
||||||
|
<span className="tech-chip-name">{tech.name}</span>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{project.technologies && project.technologies.length > 0 ? (
|
||||||
|
project.technologies.map((tech, index) => (
|
||||||
|
|
||||||
|
<span key={index} className={`tech-chip ${style.techChip}`}>
|
||||||
|
<i className={`${tech.icon} tech-chip-icon`}></i>
|
||||||
|
<span className="tech-chip-name">{tech.name}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-muted">No technologies listed.</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
project.projectDetails?.gallery && project.projectDetails?.gallery.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h4 className="mt-4 mb-3">Gallery</h4>
|
||||||
|
<div className="row">
|
||||||
|
{
|
||||||
|
project.projectDetails.gallery.map((image, index) => (
|
||||||
|
<div className="col-md-4 col-6">
|
||||||
|
<div key={index} className={`gallery-item ${style.galleryItem}`}>
|
||||||
|
<img src={image} alt={`Gallery Image ${index + 1}`} className={`gallery-image ${style.galleryImage}`}></img>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={`modal-footer ${style.modalFooter}`}>
|
||||||
|
{project.links.github && (
|
||||||
|
<a href={project.links.github} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`} target="_blank" rel="noopener noreferrer">
|
||||||
|
<i className="fab fa-github btn-icon"></i>Source Code
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{project.links.demo && (
|
||||||
|
<a href={project.links.demo} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`} target="_blank" rel="noopener noreferrer">
|
||||||
|
<i className="fas fa-external-link-alt btn-icon"></i>Live Demo
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{project.links.documentation && (
|
||||||
|
<a href={project.links.documentation} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`} target="_blank" rel="noopener noreferrer">
|
||||||
|
<i className="fas fa-book btn-icon"></i>Documentation
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button type="button" className={`btn btn-secondary`} data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>{project.description}</p>
|
|
||||||
|
|
||||||
<h4 className="mt-4 mb-3">Technologies Used</h4>
|
|
||||||
<div className="mb-4">
|
|
||||||
{project.technologies.map((tech, index) => (
|
|
||||||
<span className="tech-chip" key={index}>
|
|
||||||
<i className={`${tech.icon} tech-chip-icon`}></i>
|
|
||||||
<span className="tech-chip-name">{tech.name}</span>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="modal-footer">
|
|
||||||
{project.links.github && (
|
|
||||||
<a href={project.links.github} className="btn btn-outline-secondary">
|
|
||||||
<i className="fab fa-github me-2"></i>Source Code
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{project.links.demo && (
|
|
||||||
<a href={project.links.demo} className="btn btn-outline-secondary">
|
|
||||||
<i className="fas fa-external-link-alt me-2"></i>Live Demo
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button type="button" className="btn btn-secondary" onClick={onClose}>Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
<div className="modal-backdrop fade show"></div>
|
};
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { BlogPostProps } from "../components/BlogCard";
|
import type {BlogPostProps} from '../untils/BlogPostProps'
|
||||||
|
|
||||||
export const blogPostsData: BlogPostProps[] = [
|
export const blogPostsData: BlogPostProps[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
+67
-16
@@ -1,23 +1,74 @@
|
|||||||
import type { ProjectProps } from "../components/ProjectCard";
|
import type { ProjectProps, Category } from "../untils/ProjectProps";
|
||||||
|
|
||||||
|
type CategoryMap = Record<string, Category>
|
||||||
|
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[] = [
|
export const projectsData: ProjectProps[] = [
|
||||||
{
|
{
|
||||||
id: "shadowguardian",
|
id: "SecCodeSmithFrontend",
|
||||||
title: "ShadowGuardian: Secure IoT Monitoring System",
|
title: "SecCodeSmith Portfolio Page - Frontend",
|
||||||
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.",
|
description: "A modern, responsive portfolio page showcasing my projects, skills, and blog posts. Built with React and styled-components for a sleek user interface.",
|
||||||
image: "/images/projects/shadowguardian.jpg",
|
image: "/images/projects/shadowguardian.jpg",
|
||||||
category: ["iot", "embedded"],
|
category: [Categories["Web"]],
|
||||||
featured: true,
|
featured: true,
|
||||||
technologies: [
|
technologies: [
|
||||||
{ name: "C++", icon: "devicon-cplusplus-plain tech-icon" },
|
{ name: "React", icon: "devicon-react-original tech-icon" },
|
||||||
{ name: "STM32", icon: "fas fa-microchip tech-icon" },
|
{ name: "TypeScript", icon: "devicon-typescript-plain tech-icon" },
|
||||||
{ name: "ESP32", icon: "fas fa-wifi tech-icon" },
|
{ name: "SCSS", icon: "devicon-sass-original tech-icon" },
|
||||||
{ name: "Encryption", icon: "fas fa-shield-alt tech-icon" },
|
{ name: "Vite", icon: "devicon-vitejs-plain tech-icon" }
|
||||||
{ name: "React Native", icon: "devicon-react-original tech-icon" }
|
|
||||||
],
|
],
|
||||||
links: {
|
links: {
|
||||||
github: "https://github.com/SecCodeSmith/shadowguardian",
|
github: "https://github.com/SecCodeSmith/shadowguardian",
|
||||||
demo: "https://shadowguardian.demo.com"
|
demo: "https://seccodesmith.pl"
|
||||||
|
},
|
||||||
|
projectDetails: {
|
||||||
|
descriptions: [
|
||||||
|
"This portfolio page is designed to highlight my skills and projects in a visually appealing way. It features a clean layout, smooth animations, and responsive design for optimal viewing on all devices.",
|
||||||
|
"The project utilizes React for building the user interface, TypeScript for type safety, and SCSS for styling. The use of Vite ensures fast development and build times."
|
||||||
|
],
|
||||||
|
startDate: "12-05-2025",
|
||||||
|
endDate: "",
|
||||||
|
dateFormatted: "DD MMMM YYYY",
|
||||||
|
role: "Frontend Developer",
|
||||||
|
status: "In Progress",
|
||||||
|
keyFeatures: [
|
||||||
|
"Responsive design with mobile-first approach",
|
||||||
|
"Dynamic project showcase with filtering options",
|
||||||
|
"Interactive blog section with latest posts"
|
||||||
|
],
|
||||||
|
gallery: ["/images/projects/shadowguardian1.jpg", "/images/projects/shadowguardian2.jpg"],
|
||||||
|
fullTechStack: [
|
||||||
|
{ name: "React", icons: "devicon-react-original" },
|
||||||
|
{ name: "TypeScript", icons: "devicon-typescript-plain" },
|
||||||
|
{ name: "SCSS", icons: "devicon-sass-original" },
|
||||||
|
{ name: "Vite", icons: "devicon-vitejs-plain" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -25,7 +76,7 @@ export const projectsData: ProjectProps[] = [
|
|||||||
title: "PyroSight: Thermal Anomaly Detection",
|
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.",
|
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",
|
image: "/images/projects/pyrosight.jpg",
|
||||||
category: ["ml", "iot"],
|
category: [Categories["ML"], Categories["IoT"]],
|
||||||
technologies: [
|
technologies: [
|
||||||
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
||||||
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
|
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
|
||||||
@@ -41,7 +92,7 @@ export const projectsData: ProjectProps[] = [
|
|||||||
title: "NexusFlow: DevOps Dashboard",
|
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.",
|
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",
|
image: "/images/projects/nexusflow.jpg",
|
||||||
category: ["web"],
|
category: [Categories["Web"]],
|
||||||
technologies: [
|
technologies: [
|
||||||
{ name: "C#", icon: "devicon-csharp-plain tech-icon" },
|
{ name: "C#", icon: "devicon-csharp-plain tech-icon" },
|
||||||
{ name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" },
|
{ name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" },
|
||||||
@@ -58,7 +109,7 @@ export const projectsData: ProjectProps[] = [
|
|||||||
title: "EtherWhisper: Secure Communication Node",
|
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.",
|
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",
|
image: "/images/projects/etherwhisper.jpg",
|
||||||
category: ["embedded", "iot"],
|
category: [Categories["Embedded"], Categories["IoT"]],
|
||||||
technologies: [
|
technologies: [
|
||||||
{ name: "C", icon: "devicon-c-plain tech-icon" },
|
{ name: "C", icon: "devicon-c-plain tech-icon" },
|
||||||
{ name: "STM32", icon: "fas fa-memory tech-icon" },
|
{ name: "STM32", icon: "fas fa-memory tech-icon" },
|
||||||
@@ -74,7 +125,7 @@ export const projectsData: ProjectProps[] = [
|
|||||||
title: "DataForge: Advanced Analytics Platform",
|
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.",
|
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",
|
image: "/images/projects/dataforge.jpg",
|
||||||
category: ["web"],
|
category: [Categories["Web"]],
|
||||||
technologies: [
|
technologies: [
|
||||||
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
||||||
{ name: "Django", icon: "devicon-django-plain tech-icon" },
|
{ name: "Django", icon: "devicon-django-plain tech-icon" },
|
||||||
@@ -91,7 +142,7 @@ export const projectsData: ProjectProps[] = [
|
|||||||
title: "VisionKeeper: Object Recognition System",
|
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.",
|
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",
|
image: "/images/projects/visionkeeper.jpg",
|
||||||
category: ["ml"],
|
category: [Categories["ML"]],
|
||||||
technologies: [
|
technologies: [
|
||||||
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
||||||
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
|
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import style from '@styles/Blog.module.scss';
|
|||||||
|
|
||||||
export const Blog = () => {
|
export const Blog = () => {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, /*setCurrentPage*/] = useState(1);
|
||||||
|
|
||||||
const filteredPosts = searchQuery
|
const filteredPosts = searchQuery
|
||||||
? blogPostsData.filter(post =>
|
? blogPostsData.filter(post =>
|
||||||
|
|||||||
+30
-52
@@ -1,30 +1,35 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { PageHeader } from '../components/PageHeader';
|
import { PageHeader } from '../components/PageHeader';
|
||||||
import { ProjectCard, type ProjectProps } from '../components/ProjectCard';
|
import { ProjectCard } from '../components/ProjectCard';
|
||||||
import { ProjectModal } from '../components/ProjectModal';
|
import { ProjectModal } from '../components/ProjectModal';
|
||||||
import { projectsData } from '../data/projectsData';
|
import { projectsData, Categories } from '../data/projectsData';
|
||||||
|
import type {ProjectProps} from '../untils/ProjectProps';
|
||||||
|
|
||||||
|
import style from '@styles/Project.module.scss'
|
||||||
|
|
||||||
|
|
||||||
export const Projects = () => {
|
export const Projects = () => {
|
||||||
const [activeFilter, setActiveFilter] = useState('all');
|
const [activeFilter, setActiveFilter] = useState('all');
|
||||||
const [selectedProject, setSelectedProject] = useState<ProjectProps | null>(null);
|
const [selectedProject, setSelectedProject] = useState<ProjectProps | null>(null);
|
||||||
|
|
||||||
// Filter projects based on selected category
|
|
||||||
const filteredProjects = activeFilter === 'all'
|
const filteredProjects = activeFilter === 'all'
|
||||||
? projectsData
|
? projectsData
|
||||||
: projectsData.filter(project => project.category.includes(activeFilter));
|
: projectsData.filter(project => project.category.some(cat => cat.shortName.toLowerCase() === activeFilter));
|
||||||
|
|
||||||
|
|
||||||
|
if (selectedProject == null && filteredProjects.length > 0)
|
||||||
|
setSelectedProject(filteredProjects[0]);
|
||||||
|
|
||||||
|
const categories = Categories;
|
||||||
|
|
||||||
// Get the featured project
|
|
||||||
const featuredProject = projectsData.find(project => project.featured);
|
const featuredProject = projectsData.find(project => project.featured);
|
||||||
|
|
||||||
// Get regular projects (non-featured)
|
|
||||||
const regularProjects = filteredProjects.filter(project => !project.featured);
|
const regularProjects = filteredProjects.filter(project => !project.featured);
|
||||||
|
|
||||||
// Handle project filter click
|
|
||||||
const handleFilterClick = (filter: string) => {
|
const handleFilterClick = (filter: string) => {
|
||||||
setActiveFilter(filter);
|
setActiveFilter(filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle opening project details modal
|
|
||||||
const handleOpenDetails = (id: string) => {
|
const handleOpenDetails = (id: string) => {
|
||||||
const project = projectsData.find(p => p.id === id);
|
const project = projectsData.find(p => p.id === id);
|
||||||
if (project) {
|
if (project) {
|
||||||
@@ -32,11 +37,6 @@ export const Projects = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle closing project details modal
|
|
||||||
const handleCloseDetails = () => {
|
|
||||||
setSelectedProject(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -46,74 +46,52 @@ export const Projects = () => {
|
|||||||
|
|
||||||
<div className="container mt-5">
|
<div className="container mt-5">
|
||||||
{/* Projects Filter */}
|
{/* Projects Filter */}
|
||||||
<div className="filter-container text-center">
|
<div className={`filter-container ${style.filterContainer} text-center`}>
|
||||||
<button
|
<button
|
||||||
className={`filter-button ${activeFilter === 'all' ? 'active' : ''}`}
|
className={`filter-button ${style.filterButton} ${activeFilter === 'all' ? style.active : ''}`}
|
||||||
onClick={() => handleFilterClick('all')}
|
onClick={() => handleFilterClick('all')}
|
||||||
>
|
>
|
||||||
All Artifacts
|
All Artifacts
|
||||||
</button>
|
</button>
|
||||||
<button
|
{Object.values(categories).map((category) => (
|
||||||
className={`filter-button ${activeFilter === 'embedded' ? 'active' : ''}`}
|
<button
|
||||||
onClick={() => handleFilterClick('embedded')}
|
key={category.shortName}
|
||||||
>
|
className={`filter-button ${style.filterButton} ${activeFilter === category.shortName.toLowerCase() ? style.active : ''}`}
|
||||||
Embedded Systems
|
onClick={() => handleFilterClick(category.shortName.toLowerCase())}
|
||||||
</button>
|
>
|
||||||
<button
|
{category.fullName}
|
||||||
className={`filter-button ${activeFilter === 'web' ? 'active' : ''}`}
|
</button>
|
||||||
onClick={() => handleFilterClick('web')}
|
))}
|
||||||
>
|
|
||||||
Web Development
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`filter-button ${activeFilter === 'ml' ? 'active' : ''}`}
|
|
||||||
onClick={() => handleFilterClick('ml')}
|
|
||||||
>
|
|
||||||
Machine Learning
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`filter-button ${activeFilter === 'iot' ? 'active' : ''}`}
|
|
||||||
onClick={() => handleFilterClick('iot')}
|
|
||||||
>
|
|
||||||
IoT
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Projects Grid */}
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{/* Featured Project */}
|
{featuredProject && (activeFilter === 'all' || featuredProject.category.some(cat => cat.shortName.toLowerCase() === activeFilter)) && (
|
||||||
{featuredProject && (activeFilter === 'all' || featuredProject.category.includes(activeFilter)) && (
|
|
||||||
<div className="col-12 mb-4">
|
<div className="col-12 mb-4">
|
||||||
<ProjectCard project={featuredProject} onOpenDetails={handleOpenDetails} />
|
<ProjectCard project={featuredProject} onOpenDetails={handleOpenDetails} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Regular Projects */}
|
|
||||||
{regularProjects.map(project => (
|
{regularProjects.map(project => (
|
||||||
<div className="col-md-6 col-lg-4 mb-4" key={project.id}>
|
|
||||||
<ProjectCard project={project} onOpenDetails={handleOpenDetails} />
|
<ProjectCard project={project} onOpenDetails={handleOpenDetails} />
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* No Projects Message */}
|
|
||||||
{filteredProjects.length === 0 && (
|
{filteredProjects.length === 0 && (
|
||||||
<div className="text-center mt-5">
|
<div className="text-center mt-5">
|
||||||
<h3>No projects found for this category.</h3>
|
<h3>No projects found for this category.</h3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* View All Button */}
|
<div className="text-center mt-4 mb-5">
|
||||||
<div className="text-center mt-4">
|
<a href="https://github.com/SecCodeSmith" className={`btn btn-primary ${style.btnPrimary}`}>
|
||||||
<a href="https://github.com/SecCodeSmith" className="btn btn-primary">
|
|
||||||
<i className="fab fa-github me-2"></i> View All Projects on GitHub
|
<i className="fab fa-github me-2"></i> View All Projects on GitHub
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Project Modal */}
|
|
||||||
{selectedProject && (
|
{selectedProject && (
|
||||||
<ProjectModal project={selectedProject} onClose={handleCloseDetails} />
|
<ProjectModal project={selectedProject} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export interface BlogPostProps {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
excerpt: string;
|
||||||
|
image: string;
|
||||||
|
category: string;
|
||||||
|
date: string;
|
||||||
|
author: string;
|
||||||
|
commentCount: number;
|
||||||
|
readTime: string;
|
||||||
|
featured?: boolean;
|
||||||
|
tags: string[];
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
export interface ProjectProps {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
projectDetails?: ProjectDetails;
|
||||||
|
image: string;
|
||||||
|
category: Category[];
|
||||||
|
featured?: boolean;
|
||||||
|
technologies: ProjectTech[];
|
||||||
|
links: {
|
||||||
|
github?: string;
|
||||||
|
demo?: string;
|
||||||
|
documentation?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectDetails {
|
||||||
|
descriptions?: string[];
|
||||||
|
startDate?: string;
|
||||||
|
endDate?: string;
|
||||||
|
dateFormatted?: string;
|
||||||
|
role?: string;
|
||||||
|
status?: string;
|
||||||
|
client?: string;
|
||||||
|
keyFeatures?: string[];
|
||||||
|
gallery?: string[];
|
||||||
|
fullTechStack?: FullTechStack[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Category {
|
||||||
|
[key: string]: string;
|
||||||
|
fullName: string;
|
||||||
|
shortName: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectTech {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FullTechStack {
|
||||||
|
name: string;
|
||||||
|
icons: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user