React Portfolio Website: Modern Frontend Development with TypeScript
React Portfolio Website: Modern Frontend Development with TypeScript
In this post, I’ll share insights from my React Portfolio Website project, which demonstrates modern frontend development practices, TypeScript integration, responsive design, and performance optimization using React, TypeScript, and Tailwind CSS.
Project Overview
The React Portfolio Website is a modern, responsive single-page application designed to showcase professional work, skills, and experience. Built with React and TypeScript, it features dynamic content management, smooth animations, and optimized performance for an exceptional user experience.
Technical Architecture
Project Structure
react-portfolio/
├── src/
│ ├── components/
│ │ ├── common/
│ │ │ ├── Header.tsx
│ │ │ ├── Footer.tsx
│ │ │ ├── Navigation.tsx
│ │ │ ├── Button.tsx
│ │ │ ├── Card.tsx
│ │ │ └── Modal.tsx
│ │ ├── sections/
│ │ │ ├── Hero.tsx
│ │ │ ├── About.tsx
│ │ │ ├── Skills.tsx
│ │ │ ├── Projects.tsx
│ │ │ ├── Experience.tsx
│ │ │ ├── Education.tsx
│ │ │ ├── Contact.tsx
│ │ │ └── Blog.tsx
│ │ ├── ui/
│ │ │ ├── LoadingSpinner.tsx
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── Toast.tsx
│ │ │ └── Tooltip.tsx
│ │ └── animations/
│ │ ├── FadeIn.tsx
│ │ ├── SlideIn.tsx
│ │ ├── ScaleIn.tsx
│ │ └── Parallax.tsx
│ ├── hooks/
│ │ ├── useLocalStorage.ts
│ │ ├── useIntersectionObserver.ts
│ │ ├── useDebounce.ts
│ │ ├── useMediaQuery.ts
│ │ └── useScrollPosition.ts
│ ├── utils/
│ │ ├── constants.ts
│ │ ├── helpers.ts
│ │ ├── validators.ts
│ │ └── formatters.ts
│ ├── types/
│ │ ├── index.ts
│ │ ├── project.ts
│ │ ├── experience.ts
│ │ └── skill.ts
│ ├── data/
│ │ ├── projects.ts
│ │ ├── experience.ts
│ │ ├── skills.ts
│ │ └── education.ts
│ ├── styles/
│ │ ├── globals.css
│ │ ├── components.css
│ │ └── animations.css
│ ├── assets/
│ │ ├── images/
│ │ ├── icons/
│ │ └── fonts/
│ ├── App.tsx
│ ├── App.css
│ └── index.tsx
├── public/
│ ├── index.html
│ ├── favicon.ico
│ ├── manifest.json
│ └── robots.txt
├── package.json
├── tsconfig.json
├── tailwind.config.js
├── postcss.config.js
├── .eslintrc.js
├── .prettierrc
└── README.mdCore Implementation
TypeScript Type Definitions
// src/types/index.ts
export interface Project {
id: string;
title: string;
description: string;
longDescription?: string;
image: string;
technologies: string[];
githubUrl?: string;
liveUrl?: string;
featured: boolean;
category: ProjectCategory;
startDate: string;
endDate?: string;
status: ProjectStatus;
}
export interface Experience {
id: string;
company: string;
position: string;
location: string;
startDate: string;
endDate?: string;
current: boolean;
description: string[];
technologies: string[];
achievements: string[];
}
export interface Skill {
id: string;
name: string;
category: SkillCategory;
level: SkillLevel;
icon?: string;
description?: string;
}
export interface Education {
id: string;
institution: string;
degree: string;
field: string;
startDate: string;
endDate: string;
gpa?: number;
description?: string;
achievements?: string[];
}
export interface ContactForm {
name: string;
email: string;
subject: string;
message: string;
}
export interface BlogPost {
id: string;
title: string;
excerpt: string;
content: string;
author: string;
publishDate: string;
tags: string[];
readTime: number;
featured: boolean;
}
export enum ProjectCategory {
WEB_DEVELOPMENT = 'web-development',
MOBILE_DEVELOPMENT = 'mobile-development',
DATA_SCIENCE = 'data-science',
MACHINE_LEARNING = 'machine-learning',
DEVOPS = 'devops',
AUTOMATION = 'automation'
}
export enum ProjectStatus {
COMPLETED = 'completed',
IN_PROGRESS = 'in-progress',
PLANNED = 'planned',
ARCHIVED = 'archived'
}
export enum SkillCategory {
PROGRAMMING_LANGUAGES = 'programming-languages',
FRAMEWORKS = 'frameworks',
DATABASES = 'databases',
TOOLS = 'tools',
CLOUD_PLATFORMS = 'cloud-platforms',
SOFT_SKILLS = 'soft-skills'
}
export enum SkillLevel {
BEGINNER = 'beginner',
INTERMEDIATE = 'intermediate',
ADVANCED = 'advanced',
EXPERT = 'expert'
}
export interface Theme {
name: string;
colors: {
primary: string;
secondary: string;
accent: string;
background: string;
surface: string;
text: string;
textSecondary: string;
};
fonts: {
primary: string;
secondary: string;
};
}Custom Hooks
// src/hooks/useIntersectionObserver.ts
import { useEffect, useRef, useState } from 'react';
interface UseIntersectionObserverOptions {
threshold?: number;
root?: Element | null;
rootMargin?: string;
freezeOnceVisible?: boolean;
}
export const useIntersectionObserver = (
options: UseIntersectionObserverOptions = {}
) => {
const {
threshold = 0.1,
root = null,
rootMargin = '0px',
freezeOnceVisible = false,
} = options;
const [entry, setEntry] = useState<IntersectionObserverEntry>();
const [node, setNode] = useState<Element | null>(null);
const observer = useRef<IntersectionObserver | null>(null);
useEffect(() => {
if (!node) return;
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver(
([entry]) => {
setEntry(entry);
if (freezeOnceVisible && entry.isIntersecting) {
observer.current?.disconnect();
}
},
{
threshold,
root,
rootMargin,
}
);
observer.current.observe(node);
return () => {
if (observer.current) {
observer.current.disconnect();
}
};
}, [node, threshold, root, rootMargin, freezeOnceVisible]);
return [setNode, entry] as const;
};
// src/hooks/useLocalStorage.ts
import { useState, useEffect } from 'react';
export const useLocalStorage = <T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] => {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
};
return [storedValue, setValue];
};
// src/hooks/useDebounce.ts
import { useState, useEffect } from 'react';
export const useDebounce = <T>(value: T, delay: number): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
};
// src/hooks/useMediaQuery.ts
import { useState, useEffect } from 'react';
export const useMediaQuery = (query: string): boolean => {
const [matches, setMatches] = useState<boolean>(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => setMatches(media.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [matches, query]);
return matches;
};
// src/hooks/useScrollPosition.ts
import { useState, useEffect } from 'react';
export const useScrollPosition = (): number => {
const [scrollPosition, setScrollPosition] = useState<number>(0);
useEffect(() => {
const updatePosition = () => {
setScrollPosition(window.pageYOffset);
};
window.addEventListener('scroll', updatePosition);
updatePosition();
return () => window.removeEventListener('scroll', updatePosition);
}, []);
return scrollPosition;
};Animation Components
// src/components/animations/FadeIn.tsx
import React, { ReactNode } from 'react';
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
interface FadeInProps {
children: ReactNode;
delay?: number;
duration?: number;
direction?: 'up' | 'down' | 'left' | 'right';
className?: string;
}
export const FadeIn: React.FC<FadeInProps> = ({
children,
delay = 0,
duration = 0.6,
direction = 'up',
className = '',
}) => {
const [ref, entry] = useIntersectionObserver({
threshold: 0.1,
freezeOnceVisible: true,
});
const isVisible = entry?.isIntersecting ?? false;
const getTransform = () => {
switch (direction) {
case 'up':
return 'translateY(20px)';
case 'down':
return 'translateY(-20px)';
case 'left':
return 'translateX(20px)';
case 'right':
return 'translateX(-20px)';
default:
return 'translateY(20px)';
}
};
return (
<div
ref={ref}
className={`transition-all ease-out ${className}`}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translate(0, 0)' : getTransform(),
transitionDelay: `${delay}s`,
transitionDuration: `${duration}s`,
}}
>
{children}
</div>
);
};
// src/components/animations/SlideIn.tsx
import React, { ReactNode } from 'react';
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
interface SlideInProps {
children: ReactNode;
direction?: 'left' | 'right' | 'up' | 'down';
distance?: number;
delay?: number;
duration?: number;
className?: string;
}
export const SlideIn: React.FC<SlideInProps> = ({
children,
direction = 'left',
distance = 50,
delay = 0,
duration = 0.8,
className = '',
}) => {
const [ref, entry] = useIntersectionObserver({
threshold: 0.1,
freezeOnceVisible: true,
});
const isVisible = entry?.isIntersecting ?? false;
const getTransform = () => {
switch (direction) {
case 'left':
return `translateX(-${distance}px)`;
case 'right':
return `translateX(${distance}px)`;
case 'up':
return `translateY(-${distance}px)`;
case 'down':
return `translateY(${distance}px)`;
default:
return `translateX(-${distance}px)`;
}
};
return (
<div
ref={ref}
className={`transition-all ease-out ${className}`}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'translate(0, 0)' : getTransform(),
transitionDelay: `${delay}s`,
transitionDuration: `${duration}s`,
}}
>
{children}
</div>
);
};
// src/components/animations/ScaleIn.tsx
import React, { ReactNode } from 'react';
import { useIntersectionObserver } from '../../hooks/useIntersectionObserver';
interface ScaleInProps {
children: ReactNode;
scale?: number;
delay?: number;
duration?: number;
className?: string;
}
export const ScaleIn: React.FC<ScaleInProps> = ({
children,
scale = 0.8,
delay = 0,
duration = 0.6,
className = '',
}) => {
const [ref, entry] = useIntersectionObserver({
threshold: 0.1,
freezeOnceVisible: true,
});
const isVisible = entry?.isIntersecting ?? false;
return (
<div
ref={ref}
className={`transition-all ease-out ${className}`}
style={{
opacity: isVisible ? 1 : 0,
transform: isVisible ? 'scale(1)' : `scale(${scale})`,
transitionDelay: `${delay}s`,
transitionDuration: `${duration}s`,
}}
>
{children}
</div>
);
};Project Components
// src/components/sections/Projects.tsx
import React, { useState, useMemo } from 'react';
import { Project, ProjectCategory } from '../../types';
import { projects } from '../../data/projects';
import { ProjectCard } from './ProjectCard';
import { FilterButton } from '../common/FilterButton';
import { FadeIn } from '../animations/FadeIn';
export const Projects: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState<ProjectCategory | 'all'>('all');
const [searchTerm, setSearchTerm] = useState('');
const filteredProjects = useMemo(() => {
return projects.filter((project) => {
const matchesCategory = selectedCategory === 'all' || project.category === selectedCategory;
const matchesSearch = project.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
project.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
project.technologies.some(tech =>
tech.toLowerCase().includes(searchTerm.toLowerCase())
);
return matchesCategory && matchesSearch;
});
}, [selectedCategory, searchTerm]);
const categories = [
{ key: 'all', label: 'All Projects' },
{ key: ProjectCategory.WEB_DEVELOPMENT, label: 'Web Development' },
{ key: ProjectCategory.MOBILE_DEVELOPMENT, label: 'Mobile Development' },
{ key: ProjectCategory.DATA_SCIENCE, label: 'Data Science' },
{ key: ProjectCategory.MACHINE_LEARNING, label: 'Machine Learning' },
{ key: ProjectCategory.DEVOPS, label: 'DevOps' },
{ key: ProjectCategory.AUTOMATION, label: 'Automation' },
];
return (
<section id="projects" className="py-20 bg-gray-50 dark:bg-gray-900">
<div className="container mx-auto px-4">
<FadeIn>
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
Featured Projects
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
A showcase of my recent work and projects, demonstrating my skills and expertise
in various technologies and domains.
</p>
</div>
</FadeIn>
<FadeIn delay={0.2}>
<div className="flex flex-wrap justify-center gap-4 mb-12">
{categories.map((category) => (
<FilterButton
key={category.key}
isActive={selectedCategory === category.key}
onClick={() => setSelectedCategory(category.key as ProjectCategory | 'all')}
>
{category.label}
</FilterButton>
))}
</div>
</FadeIn>
<FadeIn delay={0.4}>
<div className="mb-8">
<input
type="text"
placeholder="Search projects..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full max-w-md mx-auto block px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-gray-800 dark:text-white"
/>
</div>
</FadeIn>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredProjects.map((project, index) => (
<FadeIn key={project.id} delay={index * 0.1}>
<ProjectCard project={project} />
</FadeIn>
))}
</div>
{filteredProjects.length === 0 && (
<FadeIn>
<div className="text-center py-12">
<p className="text-gray-600 dark:text-gray-400 text-lg">
No projects found matching your criteria.
</p>
</div>
</FadeIn>
)}
</div>
</section>
);
};
// src/components/sections/ProjectCard.tsx
import React, { useState } from 'react';
import { Project } from '../../types';
import { Card } from '../common/Card';
import { Button } from '../common/Button';
import { Modal } from '../common/Modal';
import { ScaleIn } from '../animations/ScaleIn';
interface ProjectCardProps {
project: Project;
}
export const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const handleViewDetails = () => {
setIsModalOpen(true);
};
const handleCloseModal = () => {
setIsModalOpen(false);
};
return (
<>
<ScaleIn>
<Card className="h-full flex flex-col group hover:shadow-xl transition-shadow duration-300">
<div className="relative overflow-hidden rounded-t-lg">
<img
src={project.image}
alt={project.title}
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
{project.featured && (
<div className="absolute top-4 right-4 bg-blue-500 text-white px-2 py-1 rounded-full text-sm font-medium">
Featured
</div>
)}
</div>
<div className="p-6 flex-1 flex flex-col">
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-2">
{project.title}
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4 flex-1">
{project.description}
</p>
<div className="flex flex-wrap gap-2 mb-4">
{project.technologies.slice(0, 3).map((tech) => (
<span
key={tech}
className="px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full text-sm"
>
{tech}
</span>
))}
{project.technologies.length > 3 && (
<span className="px-2 py-1 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full text-sm">
+{project.technologies.length - 3} more
</span>
)}
</div>
<div className="flex gap-3 mt-auto">
<Button
variant="primary"
onClick={handleViewDetails}
className="flex-1"
>
View Details
</Button>
{project.githubUrl && (
<Button
variant="outline"
onClick={() => window.open(project.githubUrl, '_blank')}
className="px-4"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
</Button>
)}
{project.liveUrl && (
<Button
variant="outline"
onClick={() => window.open(project.liveUrl, '_blank')}
className="px-4"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</Button>
)}
</div>
</div>
</Card>
</ScaleIn>
<Modal isOpen={isModalOpen} onClose={handleCloseModal}>
<div className="max-w-4xl mx-auto">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div>
<img
src={project.image}
alt={project.title}
className="w-full h-64 object-cover rounded-lg"
/>
</div>
<div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
{project.title}
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
{project.longDescription || project.description}
</p>
<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">
Technologies Used
</h3>
<div className="flex flex-wrap gap-2">
{project.technologies.map((tech) => (
<span
key={tech}
className="px-3 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded-full text-sm"
>
{tech}
</span>
))}
</div>
</div>
<div className="flex gap-4">
{project.githubUrl && (
<Button
variant="primary"
onClick={() => window.open(project.githubUrl, '_blank')}
className="flex items-center gap-2"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
View Code
</Button>
)}
{project.liveUrl && (
<Button
variant="outline"
onClick={() => window.open(project.liveUrl, '_blank')}
className="flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
Live Demo
</Button>
)}
</div>
</div>
</div>
</div>
</Modal>
</>
);
};Skills Section
// src/components/sections/Skills.tsx
import React, { useState } from 'react';
import { Skill, SkillCategory } from '../../types';
import { skills } from '../../data/skills';
import { FadeIn } from '../animations/FadeIn';
import { ScaleIn } from '../animations/ScaleIn';
export const Skills: React.FC = () => {
const [selectedCategory, setSelectedCategory] = useState<SkillCategory | 'all'>('all');
const filteredSkills = skills.filter(skill =>
selectedCategory === 'all' || skill.category === selectedCategory
);
const categories = [
{ key: 'all', label: 'All Skills' },
{ key: SkillCategory.PROGRAMMING_LANGUAGES, label: 'Programming Languages' },
{ key: SkillCategory.FRAMEWORKS, label: 'Frameworks' },
{ key: SkillCategory.DATABASES, label: 'Databases' },
{ key: SkillCategory.TOOLS, label: 'Tools' },
{ key: SkillCategory.CLOUD_PLATFORMS, label: 'Cloud Platforms' },
{ key: SkillCategory.SOFT_SKILLS, label: 'Soft Skills' },
];
const getSkillLevelColor = (level: string) => {
switch (level) {
case 'expert':
return 'bg-green-500';
case 'advanced':
return 'bg-blue-500';
case 'intermediate':
return 'bg-yellow-500';
case 'beginner':
return 'bg-gray-500';
default:
return 'bg-gray-500';
}
};
const getSkillLevelWidth = (level: string) => {
switch (level) {
case 'expert':
return 'w-full';
case 'advanced':
return 'w-4/5';
case 'intermediate':
return 'w-3/5';
case 'beginner':
return 'w-2/5';
default:
return 'w-2/5';
}
};
return (
<section id="skills" className="py-20 bg-white dark:bg-gray-800">
<div className="container mx-auto px-4">
<FadeIn>
<div className="text-center mb-16">
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
Skills & Technologies
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
A comprehensive overview of my technical skills and expertise across various
programming languages, frameworks, and tools.
</p>
</div>
</FadeIn>
<FadeIn delay={0.2}>
<div className="flex flex-wrap justify-center gap-4 mb-12">
{categories.map((category) => (
<button
key={category.key}
onClick={() => setSelectedCategory(category.key as SkillCategory | 'all')}
className={`px-6 py-2 rounded-full transition-colors duration-200 ${
selectedCategory === category.key
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
}`}
>
{category.label}
</button>
))}
</div>
</FadeIn>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredSkills.map((skill, index) => (
<ScaleIn key={skill.id} delay={index * 0.1}>
<div className="bg-gray-50 dark:bg-gray-900 p-6 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300">
<div className="flex items-center mb-4">
{skill.icon && (
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900 rounded-lg flex items-center justify-center mr-4">
<span className="text-2xl">{skill.icon}</span>
</div>
)}
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{skill.name}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 capitalize">
{skill.level} Level
</p>
</div>
</div>
<div className="mb-4">
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-400 mb-1">
<span>Proficiency</span>
<span className="capitalize">{skill.level}</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className={`h-2 rounded-full transition-all duration-500 ${getSkillLevelColor(skill.level)} ${getSkillLevelWidth(skill.level)}`}
/>
</div>
</div>
{skill.description && (
<p className="text-gray-600 dark:text-gray-300 text-sm">
{skill.description}
</p>
)}
</div>
</ScaleIn>
))}
</div>
{filteredSkills.length === 0 && (
<FadeIn>
<div className="text-center py-12">
<p className="text-gray-600 dark:text-gray-400 text-lg">
No skills found in this category.
</p>
</div>
</FadeIn>
)}
</div>
</section>
);
};Lessons Learned
React Development
- Component Architecture: Clean, reusable component design
- TypeScript Integration: Strong typing for better development experience
- Custom Hooks: Reusable logic with custom hooks
- Performance Optimization: Efficient rendering and state management
Frontend Design
- Responsive Design: Mobile-first approach with Tailwind CSS
- Animation: Smooth animations and transitions
- User Experience: Intuitive navigation and interactions
- Accessibility: WCAG compliance and keyboard navigation
State Management
- Local State: Efficient local state management
- Context API: Global state management where needed
- Custom Hooks: Reusable stateful logic
- Performance: Optimized re-renders and memoization
Development Practices
- Code Organization: Clean project structure and separation of concerns
- Type Safety: Comprehensive TypeScript usage
- Testing: Unit and integration testing strategies
- Documentation: Clear code documentation and examples
Future Enhancements
Advanced Features
- CMS Integration: Content management system integration
- Blog System: Dynamic blog with markdown support
- Dark Mode: Theme switching capabilities
- PWA Features: Progressive web app functionality
Technical Improvements
- Performance: Further optimization and lazy loading
- SEO: Enhanced SEO and meta tag management
- Analytics: User analytics and performance monitoring
- Internationalization: Multi-language support
Conclusion
The React Portfolio Website demonstrates comprehensive frontend development expertise and modern web development practices. Key achievements include:
- Modern React: Advanced React patterns and best practices
- TypeScript: Strong typing and developer experience
- Responsive Design: Mobile-first responsive design
- Animations: Smooth animations and interactions
- Performance: Optimized performance and user experience
- Accessibility: WCAG compliant accessibility features
The project is available on GitHub and serves as a comprehensive example of modern React development with TypeScript.
This project represents my deep dive into modern frontend development and demonstrates how React and TypeScript can be used to build exceptional user experiences. The lessons learned here continue to influence my approach to frontend development and user interface design.