Skip to main content

🎨 Module Project: Portfolio Landing Page

Congratulations on mastering the React Basics! You've learned about components, props, JSX/TSX, styling, and event handling. Now it's time to bring it all together. In this module project, you'll build a professional personal portfolio landing page that showcases your skills as a developer. You'll create reusable components, handle user interactions, apply beautiful styling, and build something you can actually use in the real world! 🚀

🎯 Project Objectives

By completing this project, you will:

  • Build a complete, multi-section website using React components
  • Create reusable UI components with typed props
  • Implement responsive design that works on all devices
  • Apply multiple styling approaches (inline, CSS modules, or Tailwind)
  • Handle user interactions with properly typed event handlers
  • Compose components to build complex layouts
  • Use conditional rendering for interactive features
  • Implement a contact form with validation

Estimated Time: 2-3 hours

Difficulty: Intermediate - applies all Module 2 concepts

💡 What You'll Build

A complete portfolio landing page with:

  • Hero section with your name and tagline
  • About section showcasing your background
  • Skills section with skill cards
  • Projects section displaying your work
  • Contact form with validation
  • Responsive navigation bar
  • Interactive elements and smooth animations

📑 Project Sections

📋 Project Requirements

Let's start by understanding exactly what we're building. A portfolio landing page is your chance to make a great first impression as a developer. It should be visually appealing, easy to navigate, and showcase your work effectively.

Core Requirements

Page Sections Required

  1. Navigation Bar - Fixed/sticky header with links to all sections
  2. Hero Section - Eye-catching introduction with your name and title
  3. About Section - Brief bio and background information
  4. Skills Section - Display of your technical skills with visual cards
  5. Projects Section - Showcase of 3-6 projects with descriptions
  6. Contact Section - Working contact form with validation
  7. Footer - Social links and copyright information

Technical Requirements

  • Built with React functional components
  • All components properly typed with TypeScript
  • Responsive design (mobile, tablet, desktop)
  • Proper use of props for component reusability
  • Event handlers for all interactive elements
  • Form validation with error messages
  • Smooth scroll navigation between sections
  • Accessible (semantic HTML, ARIA labels where needed)

Styling Requirements

  • Choose one primary styling approach (inline styles, CSS Modules, or Tailwind)
  • Consistent color scheme and typography
  • Hover effects and transitions
  • Cards with shadows and borders for visual hierarchy
  • Responsive breakpoints for different screen sizes
  • Professional and modern appearance

Component Structure Overview

graph TD
    A[App] --> B[Navigation]
    A --> C[Hero]
    A --> D[About]
    A --> E[Skills]
    A --> F[Projects]
    A --> G[Contact]
    A --> H[Footer]
    
    E --> E1[SkillCard]
    E --> E2[SkillCard]
    E --> E3[SkillCard]
    
    F --> F1[ProjectCard]
    F --> F2[ProjectCard]
    F --> F3[ProjectCard]
    
    G --> G1[ContactForm]
    
    style A fill:#667eea,color:#fff
    style B fill:#764ba2,color:#fff
    style C fill:#764ba2,color:#fff
    style D fill:#764ba2,color:#fff
    style E fill:#764ba2,color:#fff
    style F fill:#764ba2,color:#fff
    style G fill:#764ba2,color:#fff
    style H fill:#764ba2,color:#fff

TypeScript Concepts to Apply

Concept Where to Use Example
Interfaces Define prop types interface ProjectCardProps
Type Aliases Reusable types type Skill = { name: string; level: number }
React.FC Component typing const Hero: React.FC = () => {...}
Event Types Form handlers React.FormEvent, React.ChangeEvent
Union Types Skill levels 'beginner' | 'intermediate' | 'advanced'
Optional Props Component flexibility description?: string

💡 Success Criteria

Your portfolio is complete when:

  • ✅ All sections are present and functional
  • ✅ Navigation links scroll smoothly to each section
  • ✅ All components have proper TypeScript types
  • ✅ Form validates input and shows appropriate errors
  • ✅ Page is responsive on mobile, tablet, and desktop
  • ✅ No TypeScript errors in the console
  • ✅ Professional appearance with consistent styling
  • ✅ Code is well-organized and readable

⚙️ Project Setup

Before we start building, let's set up our project properly. We'll use Vite for fast development and configure TypeScript correctly.

Step 1: Create the Project

Using Vite

# Create new Vite project with React and TypeScript
npm create vite@latest portfolio -- --template react-ts

# Navigate into the project
cd portfolio

# Install dependencies
npm install

# Start development server
npm run dev

Your project should now be running at http://localhost:5173

Step 2: Project Structure

Recommended Folder Structure

portfolio/
├── src/
│   ├── components/
│   │   ├── Navigation.tsx
│   │   ├── Hero.tsx
│   │   ├── About.tsx
│   │   ├── Skills.tsx
│   │   ├── SkillCard.tsx
│   │   ├── Projects.tsx
│   │   ├── ProjectCard.tsx
│   │   ├── Contact.tsx
│   │   ├── ContactForm.tsx
│   │   └── Footer.tsx
│   ├── types/
│   │   └── index.ts
│   ├── data/
│   │   └── portfolio.ts
│   ├── App.tsx
│   ├── App.css
│   └── main.tsx
├── public/
├── index.html
├── package.json
└── tsconfig.json

Step 3: Create Type Definitions

src/types/index.ts

Let's define all our TypeScript types in one place:

// Skill type with name, level, and icon
export interface Skill {
    name: string;
    level: 'beginner' | 'intermediate' | 'advanced' | 'expert';
    icon?: string; // Optional emoji or icon
}

// Project type for portfolio items
export interface Project {
    id: number;
    title: string;
    description: string;
    technologies: string[];
    imageUrl?: string;
    demoUrl?: string;
    githubUrl?: string;
}

// Contact form data type
export interface ContactFormData {
    name: string;
    email: string;
    message: string;
}

// Form validation errors
export interface FormErrors {
    name?: string;
    email?: string;
    message?: string;
}

// Navigation link type
export interface NavLink {
    label: string;
    href: string;
}

// Social link type for footer
export interface SocialLink {
    platform: string;
    url: string;
    icon: string;
}

Step 4: Create Sample Data

src/data/portfolio.ts

Create sample data to populate your portfolio:

import { Skill, Project, NavLink, SocialLink } from '../types';

export const skills: Skill[] = [
    { name: 'TypeScript', level: 'advanced', icon: '📘' },
    { name: 'React', level: 'advanced', icon: '⚛️' },
    { name: 'JavaScript', level: 'expert', icon: '💛' },
    { name: 'HTML/CSS', level: 'expert', icon: '🎨' },
    { name: 'Node.js', level: 'intermediate', icon: '🟢' },
    { name: 'Git', level: 'advanced', icon: '🔧' },
];

export const projects: Project[] = [
    {
        id: 1,
        title: 'E-Commerce Platform',
        description: 'A full-stack e-commerce application with cart, checkout, and payment integration.',
        technologies: ['React', 'TypeScript', 'Node.js', 'MongoDB'],
        demoUrl: 'https://example.com',
        githubUrl: 'https://github.com/username/project',
    },
    {
        id: 2,
        title: 'Task Management App',
        description: 'Collaborative task manager with real-time updates and team features.',
        technologies: ['React', 'TypeScript', 'Firebase', 'Tailwind CSS'],
        demoUrl: 'https://example.com',
        githubUrl: 'https://github.com/username/project',
    },
    {
        id: 3,
        title: 'Weather Dashboard',
        description: 'Real-time weather dashboard with forecasts and location search.',
        technologies: ['React', 'TypeScript', 'Weather API', 'Chart.js'],
        demoUrl: 'https://example.com',
        githubUrl: 'https://github.com/username/project',
    },
];

export const navLinks: NavLink[] = [
    { label: 'Home', href: '#home' },
    { label: 'About', href: '#about' },
    { label: 'Skills', href: '#skills' },
    { label: 'Projects', href: '#projects' },
    { label: 'Contact', href: '#contact' },
];

export const socialLinks: SocialLink[] = [
    { platform: 'GitHub', url: 'https://github.com/username', icon: '🐙' },
    { platform: 'LinkedIn', url: 'https://linkedin.com/in/username', icon: '💼' },
    { platform: 'Twitter', url: 'https://twitter.com/username', icon: '🐦' },
];

Note: Replace these with your own information as you build!

✅ Setup Complete!

You now have:

  • A working React + TypeScript project with Vite
  • Organized folder structure
  • Type definitions for all data structures
  • Sample data to work with

Let's start building components!

🏗️ Component Architecture

Before we dive into coding individual components, let's understand how they'll work together. Good component architecture makes your code maintainable and reusable.

Component Hierarchy

Top-Level Structure

The App.tsx component will be our main container that renders all sections:

import Navigation from './components/Navigation';
import Hero from './components/Hero';
import About from './components/About';
import Skills from './components/Skills';
import Projects from './components/Projects';
import Contact from './components/Contact';
import Footer from './components/Footer';

function App() {
    return (
        <>
            <Navigation />
            <main>
                <Hero />
                <About />
                <Skills />
                <Projects />
                <Contact />
            </main>
            <Footer />
        </>
    );
}

export default App;

Component Categories

Category Components Purpose
Layout Navigation, Footer Structure and navigation
Sections Hero, About, Skills, Projects, Contact Main content areas
Reusable Cards SkillCard, ProjectCard Display repeated data
Forms ContactForm User input and validation

Data Flow Pattern

graph LR
    A[portfolio.ts
Sample Data] --> B[App.tsx] B --> C[Skills Component] B --> D[Projects Component] C --> E[SkillCard
Props: skill] D --> F[ProjectCard
Props: project] style A fill:#f9f9f9 style B fill:#667eea,color:#fff style C fill:#764ba2,color:#fff style D fill:#764ba2,color:#fff style E fill:#e3f2fd style F fill:#e3f2fd

💡 Component Design Principles

  • Single Responsibility: Each component does one thing well
  • Reusability: Cards accept props to display different data
  • Composition: Build complex UIs from simple components
  • Type Safety: All props are properly typed with TypeScript
  • Separation of Concerns: Data, types, and components in separate files

Styling Strategy

Choose Your Approach

For this project, you can use any styling method you prefer. Here are recommendations:

Method Pros Best For
Inline Styles Quick, component-scoped Small projects, prototypes
CSS Modules Scoped styles, familiar CSS Medium projects
Tailwind CSS Utility-first, fast development Modern, professional sites

For this tutorial, we'll use inline styles for simplicity, but feel free to convert to your preferred method!

📦 Component Checklist

Each component we build will have:

  • ✅ Proper TypeScript typing (props interface if needed)
  • ✅ React.FC type annotation
  • ✅ Clear, descriptive naming
  • ✅ Responsive styling
  • ✅ Accessibility considerations
  • ✅ Comments explaining complex logic

🧭 Building the Navigation

Let's start by building the navigation bar - the component that helps users navigate through your portfolio. It will be sticky, responsive, and include smooth scrolling.

Navigation Component Structure

What We're Building

  • Fixed/sticky navigation bar at the top
  • Logo/name on the left
  • Navigation links on the right
  • Smooth scroll to sections when clicked
  • Mobile-friendly hamburger menu (optional enhancement)

Step 1: Create Navigation Component

src/components/Navigation.tsx

import React from 'react';
import { navLinks } from '../data/portfolio';

const Navigation: React.FC = () => {
    const handleSmoothScroll = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
        e.preventDefault();
        const targetId = href.replace('#', '');
        const element = document.getElementById(targetId);
        
        if (element) {
            element.scrollIntoView({
                behavior: 'smooth',
                block: 'start',
            });
        }
    };

    return (
        <nav style={styles.nav}>
            <div style={styles.container}>
                <h1 style={styles.logo}>Your Name</h1>
                
                <ul style={styles.navList}>
                    {navLinks.map((link) => (
                        <li key={link.href} style={styles.navItem}>
                            <a
                                href={link.href}
                                onClick={(e) => handleSmoothScroll(e, link.href)}
                                style={styles.navLink}
                            >
                                {link.label}
                            </a>
                        </li>
                    ))}
                </ul>
            </div>
        </nav>
    );
};

const styles = {
    nav: {
        position: 'fixed' as const,
        top: 0,
        left: 0,
        right: 0,
        backgroundColor: '#ffffff',
        boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
        zIndex: 1000,
        padding: '1rem 0',
    },
    container: {
        maxWidth: '1200px',
        margin: '0 auto',
        padding: '0 2rem',
        display: 'flex',
        justifyContent: 'space-between',
        alignItems: 'center',
    },
    logo: {
        fontSize: '1.5rem',
        fontWeight: 'bold',
        color: '#667eea',
        margin: 0,
    },
    navList: {
        display: 'flex',
        listStyle: 'none',
        gap: '2rem',
        margin: 0,
        padding: 0,
    },
    navItem: {
        margin: 0,
    },
    navLink: {
        textDecoration: 'none',
        color: '#333',
        fontSize: '1rem',
        fontWeight: 500,
        transition: 'color 0.3s ease',
        cursor: 'pointer',
    },
};

export default Navigation;

💡 Key Features Explained

  • Fixed positioning: Navigation stays at top while scrolling
  • Smooth scrolling: scrollIntoView creates smooth transitions
  • Event prevention: preventDefault() stops default anchor behavior
  • Type safety: Event handler properly typed with React.MouseEvent
  • Dynamic links: Maps over navLinks array for maintainability

Adding Hover Effects

Enhanced Navigation with Hover States

To add hover effects with inline styles, we can use the onMouseEnter and onMouseLeave events:

import React, { useState } from 'react';
import { navLinks } from '../data/portfolio';

const Navigation: React.FC = () => {
    const [hoveredLink, setHoveredLink] = useState<string | null>(null);

    const handleSmoothScroll = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
        e.preventDefault();
        const targetId = href.replace('#', '');
        const element = document.getElementById(targetId);
        
        if (element) {
            element.scrollIntoView({
                behavior: 'smooth',
                block: 'start',
            });
        }
    };

    return (
        <nav style={styles.nav}>
            <div style={styles.container}>
                <h1 style={styles.logo}>Your Name</h1>
                
                <ul style={styles.navList}>
                    {navLinks.map((link) => (
                        <li key={link.href} style={styles.navItem}>
                            <a
                                href={link.href}
                                onClick={(e) => handleSmoothScroll(e, link.href)}
                                onMouseEnter={() => setHoveredLink(link.href)}
                                onMouseLeave={() => setHoveredLink(null)}
                                style={{
                                    ...styles.navLink,
                                    color: hoveredLink === link.href ? '#667eea' : '#333',
                                }}
                            >
                                {link.label}
                            </a>
                        </li>
                    ))}
                </ul>
            </div>
        </nav>
    );
};

export default Navigation;

✅ Navigation Complete!

You now have a functional, sticky navigation with:

  • ✅ Fixed positioning at the top
  • ✅ Smooth scrolling to sections
  • ✅ Hover effects for better UX
  • ✅ Properly typed event handlers

🦸 Creating the Hero Section

The Hero section is the first thing visitors see - it should immediately grab attention and communicate who you are. Let's make it impressive!

Hero Component Design

What We're Building

  • Full-screen or large hero section
  • Your name in large, bold text
  • Tagline describing what you do
  • Call-to-action buttons (View Projects, Contact Me)
  • Gradient or eye-catching background

Step 1: Create Hero Component

src/components/Hero.tsx

import React from 'react';

const Hero: React.FC = () => {
    const scrollToSection = (sectionId: string) => {
        const element = document.getElementById(sectionId);
        if (element) {
            element.scrollIntoView({ behavior: 'smooth' });
        }
    };

    return (
        <section id="home" style={styles.hero}>
            <div style={styles.content}>
                <h1 style={styles.name}>
                    Hi, I'm <span style={styles.highlight}>Your Name</span>
                </h1>
                
                <p style={styles.tagline}>
                    Full-Stack Developer | React Enthusiast | TypeScript Advocate
                </p>
                
                <p style={styles.description}>
                    I build beautiful, functional web applications that solve real problems.
                    Passionate about clean code, user experience, and continuous learning.
                </p>
                
                <div style={styles.buttonGroup}>
                    <button
                        onClick={() => scrollToSection('projects')}
                        style={styles.primaryButton}
                    >
                        View My Work
                    </button>
                    <button
                        onClick={() => scrollToSection('contact')}
                        style={styles.secondaryButton}
                    >
                        Contact Me
                    </button>
                </div>
            </div>
        </section>
    );
};

const styles = {
    hero: {
        minHeight: '100vh',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
        color: '#ffffff',
        textAlign: 'center' as const,
        padding: '2rem',
        marginTop: '60px', // Account for fixed nav
    },
    content: {
        maxWidth: '800px',
    },
    name: {
        fontSize: '3rem',
        fontWeight: 'bold',
        marginBottom: '1rem',
        lineHeight: 1.2,
    },
    highlight: {
        color: '#ffd700',
    },
    tagline: {
        fontSize: '1.5rem',
        marginBottom: '1.5rem',
        fontWeight: 500,
    },
    description: {
        fontSize: '1.125rem',
        marginBottom: '2rem',
        lineHeight: 1.6,
        opacity: 0.95,
    },
    buttonGroup: {
        display: 'flex',
        gap: '1rem',
        justifyContent: 'center',
        flexWrap: 'wrap' as const,
    },
    primaryButton: {
        padding: '0.875rem 2rem',
        fontSize: '1rem',
        fontWeight: 600,
        border: 'none',
        borderRadius: '8px',
        backgroundColor: '#ffffff',
        color: '#667eea',
        cursor: 'pointer',
        transition: 'transform 0.2s ease, box-shadow 0.2s ease',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
    },
    secondaryButton: {
        padding: '0.875rem 2rem',
        fontSize: '1rem',
        fontWeight: 600,
        border: '2px solid #ffffff',
        borderRadius: '8px',
        backgroundColor: 'transparent',
        color: '#ffffff',
        cursor: 'pointer',
        transition: 'background-color 0.2s ease',
    },
};

export default Hero;

Adding Interactive Button Effects

Enhanced Hero with Hover States

import React, { useState } from 'react';

const Hero: React.FC = () => {
    const [hoveredButton, setHoveredButton] = useState<string | null>(null);

    const scrollToSection = (sectionId: string) => {
        const element = document.getElementById(sectionId);
        if (element) {
            element.scrollIntoView({ behavior: 'smooth' });
        }
    };

    const getPrimaryButtonStyle = () => ({
        ...styles.primaryButton,
        transform: hoveredButton === 'primary' ? 'translateY(-2px)' : 'translateY(0)',
        boxShadow: hoveredButton === 'primary' 
            ? '0 6px 12px rgba(0, 0, 0, 0.2)' 
            : '0 4px 6px rgba(0, 0, 0, 0.1)',
    });

    const getSecondaryButtonStyle = () => ({
        ...styles.secondaryButton,
        backgroundColor: hoveredButton === 'secondary' ? 'rgba(255, 255, 255, 0.1)' : 'transparent',
    });

    return (
        <section id="home" style={styles.hero}>
            <div style={styles.content}>
                <h1 style={styles.name}>
                    Hi, I'm <span style={styles.highlight}>Your Name</span>
                </h1>
                
                <p style={styles.tagline}>
                    Full-Stack Developer | React Enthusiast | TypeScript Advocate
                </p>
                
                <p style={styles.description}>
                    I build beautiful, functional web applications that solve real problems.
                    Passionate about clean code, user experience, and continuous learning.
                </p>
                
                <div style={styles.buttonGroup}>
                    <button
                        onClick={() => scrollToSection('projects')}
                        onMouseEnter={() => setHoveredButton('primary')}
                        onMouseLeave={() => setHoveredButton(null)}
                        style={getPrimaryButtonStyle()}
                    >
                        View My Work
                    </button>
                    <button
                        onClick={() => scrollToSection('contact')}
                        onMouseEnter={() => setHoveredButton('secondary')}
                        onMouseLeave={() => setHoveredButton(null)}
                        style={getSecondaryButtonStyle()}
                    >
                        Contact Me
                    </button>
                </div>
            </div>
        </section>
    );
};

export default Hero;

💡 Design Decisions Explained

  • Full-height section: minHeight: '100vh' creates impactful first impression
  • Gradient background: Modern, eye-catching visual
  • Highlight color: Golden accent draws attention to your name
  • Button hierarchy: Primary (filled) vs Secondary (outlined)
  • Responsive text: Large text sizes scale with viewport

✅ Hero Section Complete!

Your hero section now has:

  • ✅ Eye-catching gradient background
  • ✅ Clear name and tagline
  • ✅ Call-to-action buttons with hover effects
  • ✅ Smooth scrolling to other sections

👤 About Section

The About section tells your story - who you are, your background, and what drives you as a developer. Let's make it personal and engaging!

About Component Design

What We're Building

  • Section heading
  • Professional photo or avatar (optional)
  • Brief biography (2-3 paragraphs)
  • Key highlights or achievements
  • Clean, readable layout

Step 1: Create About Component

src/components/About.tsx

import React from 'react';

const About: React.FC = () => {
    return (
        <section id="about" style={styles.section}>
            <div style={styles.container}>
                <h2 style={styles.heading}>About Me</h2>
                
                <div style={styles.content}>
                    <div style={styles.imageContainer}>
                        {/* Placeholder for your photo */}
                        <div style={styles.imagePlaceholder}>
                            <span style={styles.emoji}>👨‍💻</span>
                        </div>
                    </div>
                    
                    <div style={styles.text}>
                        <p style={styles.paragraph}>
                            Hi! I'm a passionate full-stack developer with a love for creating 
                            elegant solutions to complex problems. I specialize in building 
                            modern web applications using React, TypeScript, and Node.js.
                        </p>
                        
                        <p style={styles.paragraph}>
                            My journey into web development started [X years ago], and I've 
                            been hooked ever since. I believe in writing clean, maintainable 
                            code and following best practices to deliver high-quality products.
                        </p>
                        
                        <p style={styles.paragraph}>
                            When I'm not coding, you can find me [your hobbies/interests]. 
                            I'm always eager to learn new technologies and take on challenging 
                            projects that push me to grow as a developer.
                        </p>
                        
                        <div style={styles.highlights}>
                            <h3 style={styles.subheading}>Quick Facts</h3>
                            <ul style={styles.list}>
                                <li style={styles.listItem}>🎓 Computer Science graduate</li>
                                <li style={styles.listItem}>💼 [X] years of development experience</li>
                                <li style={styles.listItem}>🚀 [X]+ projects completed</li>
                                <li style={styles.listItem}>🌱 Currently learning [new technology]</li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    );
};

const styles = {
    section: {
        padding: '5rem 2rem',
        backgroundColor: '#f8f9fa',
    },
    container: {
        maxWidth: '1200px',
        margin: '0 auto',
    },
    heading: {
        fontSize: '2.5rem',
        fontWeight: 'bold',
        textAlign: 'center' as const,
        marginBottom: '3rem',
        color: '#333',
    },
    content: {
        display: 'flex',
        gap: '3rem',
        alignItems: 'center',
        flexWrap: 'wrap' as const,
    },
    imageContainer: {
        flex: '0 0 250px',
    },
    imagePlaceholder: {
        width: '250px',
        height: '250px',
        borderRadius: '50%',
        backgroundColor: '#667eea',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
    },
    emoji: {
        fontSize: '6rem',
    },
    text: {
        flex: '1',
        minWidth: '300px',
    },
    paragraph: {
        fontSize: '1.125rem',
        lineHeight: 1.8,
        color: '#555',
        marginBottom: '1.5rem',
    },
    highlights: {
        marginTop: '2rem',
        padding: '1.5rem',
        backgroundColor: '#ffffff',
        borderRadius: '8px',
        boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)',
    },
    subheading: {
        fontSize: '1.5rem',
        fontWeight: 'bold',
        marginBottom: '1rem',
        color: '#667eea',
    },
    list: {
        listStyle: 'none',
        padding: 0,
        margin: 0,
    },
    listItem: {
        fontSize: '1.125rem',
        marginBottom: '0.75rem',
        color: '#555',
    },
};

export default About;

Adding a Real Image

Using an Actual Photo

To use a real photo instead of the placeholder:

// Replace the imagePlaceholder div with:
<img 
    src="/path-to-your-photo.jpg" 
    alt="Your Name"
    style={styles.image}
/>

// Add to styles:
image: {
    width: '250px',
    height: '250px',
    borderRadius: '50%',
    objectFit: 'cover' as const,
    boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
},

✅ About Section Complete!

Your about section now includes:

  • ✅ Professional layout with image and text
  • ✅ Personal biography that tells your story
  • ✅ Quick facts highlighting key information
  • ✅ Responsive flex layout

💪 Skills Section

The Skills section showcases your technical abilities in a visual, engaging way. We'll create reusable SkillCard components to display each skill with its level.

Component Structure

graph LR
    A[Skills Section] --> B[SkillCard]
    A --> C[SkillCard]
    A --> D[SkillCard]
    A --> E[SkillCard]
    
    B --> B1[Skill Name]
    B --> B2[Skill Level]
    B --> B3[Icon]
    
    style A fill:#667eea,color:#fff
    style B fill:#e3f2fd
    style C fill:#e3f2fd
    style D fill:#e3f2fd
    style E fill:#e3f2fd

Step 1: Create SkillCard Component

src/components/SkillCard.tsx

First, let's create a reusable card for individual skills:

import React from 'react';
import { Skill } from '../types';

interface SkillCardProps {
    skill: Skill;
}

const SkillCard: React.FC<SkillCardProps> = ({ skill }) => {
    // Convert skill level to percentage for visual indicator
    const getLevelPercentage = (level: Skill['level']): number => {
        switch (level) {
            case 'beginner': return 25;
            case 'intermediate': return 50;
            case 'advanced': return 75;
            case 'expert': return 100;
            default: return 0;
        }
    };

    // Get color based on skill level
    const getLevelColor = (level: Skill['level']): string => {
        switch (level) {
            case 'beginner': return '#ffc107';
            case 'intermediate': return '#2196F3';
            case 'advanced': return '#4CAF50';
            case 'expert': return '#667eea';
            default: return '#ccc';
        }
    };

    const percentage = getLevelPercentage(skill.level);
    const color = getLevelColor(skill.level);

    return (
        <div style={styles.card}>
            {skill.icon && (
                <div style={styles.icon}>{skill.icon}</div>
            )}
            
            <h3 style={styles.skillName}>{skill.name}</h3>
            
            <div style={styles.levelContainer}>
                <div style={styles.levelBackground}>
                    <div 
                        style={{
                            ...styles.levelBar,
                            width: `${percentage}%`,
                            backgroundColor: color,
                        }}
                    />
                </div>
                <span style={styles.levelText}>
                    {skill.level.charAt(0).toUpperCase() + skill.level.slice(1)}
                </span>
            </div>
        </div>
    );
};

const styles = {
    card: {
        backgroundColor: '#ffffff',
        borderRadius: '12px',
        padding: '1.5rem',
        boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
        transition: 'transform 0.3s ease, box-shadow 0.3s ease',
        cursor: 'pointer',
    },
    icon: {
        fontSize: '3rem',
        marginBottom: '1rem',
        textAlign: 'center' as const,
    },
    skillName: {
        fontSize: '1.25rem',
        fontWeight: 'bold',
        marginBottom: '1rem',
        textAlign: 'center' as const,
        color: '#333',
    },
    levelContainer: {
        marginTop: '1rem',
    },
    levelBackground: {
        width: '100%',
        height: '8px',
        backgroundColor: '#e0e0e0',
        borderRadius: '4px',
        overflow: 'hidden',
        marginBottom: '0.5rem',
    },
    levelBar: {
        height: '100%',
        transition: 'width 0.5s ease',
        borderRadius: '4px',
    },
    levelText: {
        fontSize: '0.875rem',
        color: '#666',
        textAlign: 'center' as const,
        display: 'block',
    },
};

export default SkillCard;

Step 2: Create Skills Section Component

src/components/Skills.tsx

Now let's create the section that displays all skill cards:

import React, { useState } from 'react';
import { skills } from '../data/portfolio';
import SkillCard from './SkillCard';

const Skills: React.FC = () => {
    const [hoveredCard, setHoveredCard] = useState<number | null>(null);

    return (
        <section id="skills" style={styles.section}>
            <div style={styles.container}>
                <h2 style={styles.heading}>My Skills</h2>
                
                <p style={styles.intro}>
                    Here are the technologies and tools I work with. I'm always 
                    learning and expanding my skill set!
                </p>
                
                <div style={styles.grid}>
                    {skills.map((skill, index) => (
                        <div
                            key={skill.name}
                            onMouseEnter={() => setHoveredCard(index)}
                            onMouseLeave={() => setHoveredCard(null)}
                            style={{
                                transform: hoveredCard === index 
                                    ? 'translateY(-8px)' 
                                    : 'translateY(0)',
                                transition: 'transform 0.3s ease',
                            }}
                        >
                            <SkillCard skill={skill} />
                        </div>
                    ))}
                </div>
            </div>
        </section>
    );
};

const styles = {
    section: {
        padding: '5rem 2rem',
        backgroundColor: '#ffffff',
    },
    container: {
        maxWidth: '1200px',
        margin: '0 auto',
    },
    heading: {
        fontSize: '2.5rem',
        fontWeight: 'bold',
        textAlign: 'center' as const,
        marginBottom: '1rem',
        color: '#333',
    },
    intro: {
        fontSize: '1.125rem',
        textAlign: 'center' as const,
        color: '#666',
        marginBottom: '3rem',
        maxWidth: '600px',
        margin: '0 auto 3rem',
    },
    grid: {
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
        gap: '2rem',
    },
};

export default Skills;

💡 Component Design Patterns

  • Reusable SkillCard: Accepts skill prop for flexibility
  • Type safety: Props interface ensures correct data structure
  • Dynamic styling: Level determines color and percentage
  • Responsive grid: auto-fit and minmax create flexible layout
  • Hover effects: Cards lift on hover for interactivity
  • Visual indicators: Progress bars show skill levels at a glance

✅ Skills Section Complete!

Your skills section now has:

  • ✅ Reusable SkillCard component with typed props
  • ✅ Visual skill level indicators with colors
  • ✅ Responsive grid layout
  • ✅ Smooth hover animations
  • ✅ Dynamic rendering from data array

🚀 Projects Section

The Projects section is where you showcase your best work. We'll create a ProjectCard component to display each project with its details, technologies used, and links to demos and code.

Component Structure

graph LR
    A[Projects Section] --> B[ProjectCard]
    A --> C[ProjectCard]
    A --> D[ProjectCard]
    
    B --> B1[Title]
    B --> B2[Description]
    B --> B3[Tech Tags]
    B --> B4[Links]
    
    style A fill:#667eea,color:#fff
    style B fill:#e3f2fd
    style C fill:#e3f2fd
    style D fill:#e3f2fd

Step 1: Create ProjectCard Component

src/components/ProjectCard.tsx

import React, { useState } from 'react';
import { Project } from '../types';

interface ProjectCardProps {
    project: Project;
}

const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
    const [isHovered, setIsHovered] = useState(false);

    return (
        <div
            style={{
                ...styles.card,
                transform: isHovered ? 'translateY(-8px)' : 'translateY(0)',
                boxShadow: isHovered 
                    ? '0 12px 24px rgba(0, 0, 0, 0.15)' 
                    : '0 4px 6px rgba(0, 0, 0, 0.1)',
            }}
            onMouseEnter={() => setIsHovered(true)}
            onMouseLeave={() => setIsHovered(false)}
        >
            {/* Project Image Placeholder */}
            {project.imageUrl ? (
                <img 
                    src={project.imageUrl} 
                    alt={project.title}
                    style={styles.image}
                />
            ) : (
                <div style={styles.imagePlaceholder}>
                    <span style={styles.placeholderIcon}>💻</span>
                </div>
            )}
            
            {/* Project Content */}
            <div style={styles.content}>
                <h3 style={styles.title}>{project.title}</h3>
                
                <p style={styles.description}>{project.description}</p>
                
                {/* Technology Tags */}
                <div style={styles.techTags}>
                    {project.technologies.map((tech) => (
                        <span key={tech} style={styles.tag}>
                            {tech}
                        </span>
                    ))}
                </div>
                
                {/* Project Links */}
                <div style={styles.links}>
                    {project.demoUrl && (
                        <a 
                            href={project.demoUrl}
                            target="_blank"
                            rel="noopener noreferrer"
                            style={styles.linkButton}
                        >
                            🌐 Live Demo
                        </a>
                    )}
                    {project.githubUrl && (
                        <a 
                            href={project.githubUrl}
                            target="_blank"
                            rel="noopener noreferrer"
                            style={styles.linkButton}
                        >
                            🐙 View Code
                        </a>
                    )}
                </div>
            </div>
        </div>
    );
};

const styles = {
    card: {
        backgroundColor: '#ffffff',
        borderRadius: '12px',
        overflow: 'hidden',
        transition: 'transform 0.3s ease, box-shadow 0.3s ease',
        cursor: 'pointer',
    },
    image: {
        width: '100%',
        height: '200px',
        objectFit: 'cover' as const,
    },
    imagePlaceholder: {
        width: '100%',
        height: '200px',
        backgroundColor: '#667eea',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
    },
    placeholderIcon: {
        fontSize: '4rem',
    },
    content: {
        padding: '1.5rem',
    },
    title: {
        fontSize: '1.5rem',
        fontWeight: 'bold',
        marginBottom: '0.75rem',
        color: '#333',
    },
    description: {
        fontSize: '1rem',
        lineHeight: 1.6,
        color: '#666',
        marginBottom: '1rem',
    },
    techTags: {
        display: 'flex',
        flexWrap: 'wrap' as const,
        gap: '0.5rem',
        marginBottom: '1.5rem',
    },
    tag: {
        padding: '0.25rem 0.75rem',
        backgroundColor: '#e3f2fd',
        color: '#2196F3',
        borderRadius: '16px',
        fontSize: '0.875rem',
        fontWeight: 500,
    },
    links: {
        display: 'flex',
        gap: '1rem',
        flexWrap: 'wrap' as const,
    },
    linkButton: {
        padding: '0.5rem 1rem',
        backgroundColor: '#667eea',
        color: '#ffffff',
        textDecoration: 'none',
        borderRadius: '6px',
        fontSize: '0.875rem',
        fontWeight: 600,
        transition: 'background-color 0.2s ease',
    },
};

export default ProjectCard;

Step 2: Create Projects Section Component

src/components/Projects.tsx

import React from 'react';
import { projects } from '../data/portfolio';
import ProjectCard from './ProjectCard';

const Projects: React.FC = () => {
    return (
        <section id="projects" style={styles.section}>
            <div style={styles.container}>
                <h2 style={styles.heading}>My Projects</h2>
                
                <p style={styles.intro}>
                    Here are some of the projects I've worked on. Each one taught me 
                    something new and helped me grow as a developer.
                </p>
                
                <div style={styles.grid}>
                    {projects.map((project) => (
                        <ProjectCard key={project.id} project={project} />
                    ))}
                </div>
                
                <div style={styles.ctaContainer}>
                    <p style={styles.ctaText}>
                        Want to see more? Check out my GitHub for additional projects!
                    </p>
                    <a 
                        href="https://github.com/username"
                        target="_blank"
                        rel="noopener noreferrer"
                        style={styles.ctaButton}
                    >
                        View All Projects on GitHub
                    </a>
                </div>
            </div>
        </section>
    );
};

const styles = {
    section: {
        padding: '5rem 2rem',
        backgroundColor: '#f8f9fa',
    },
    container: {
        maxWidth: '1200px',
        margin: '0 auto',
    },
    heading: {
        fontSize: '2.5rem',
        fontWeight: 'bold',
        textAlign: 'center' as const,
        marginBottom: '1rem',
        color: '#333',
    },
    intro: {
        fontSize: '1.125rem',
        textAlign: 'center' as const,
        color: '#666',
        marginBottom: '3rem',
        maxWidth: '600px',
        margin: '0 auto 3rem',
    },
    grid: {
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
        gap: '2rem',
        marginBottom: '3rem',
    },
    ctaContainer: {
        textAlign: 'center' as const,
        marginTop: '3rem',
    },
    ctaText: {
        fontSize: '1.125rem',
        color: '#666',
        marginBottom: '1rem',
    },
    ctaButton: {
        display: 'inline-block',
        padding: '0.875rem 2rem',
        backgroundColor: '#333',
        color: '#ffffff',
        textDecoration: 'none',
        borderRadius: '8px',
        fontSize: '1rem',
        fontWeight: 600,
        transition: 'background-color 0.2s ease',
    },
};

export default Projects;

💡 Key Features

  • Reusable ProjectCard: Clean separation of concerns
  • Conditional rendering: Shows image or placeholder
  • Technology tags: Visual display of tech stack
  • External links: Opens in new tab with security attributes
  • Hover effects: Cards animate on hover for better UX
  • CTA section: Encourages visitors to see more work

✅ Projects Section Complete!

Your projects section now features:

  • ✅ Reusable ProjectCard component
  • ✅ Professional card design with images
  • ✅ Technology tag display
  • ✅ Demo and GitHub links
  • ✅ Responsive grid layout
  • ✅ Call-to-action for more projects

📧 Contact Form

The Contact section allows visitors to reach out to you. We'll build a form with proper validation, error handling, and a great user experience.

Form Requirements

What We Need

  • Name field (required, min 2 characters)
  • Email field (required, valid email format)
  • Message field (required, min 10 characters)
  • Real-time validation with error messages
  • Submit button with disabled state
  • Success message after submission

Step 1: Create ContactForm Component

src/components/ContactForm.tsx

import React, { useState } from 'react';
import { ContactFormData, FormErrors } from '../types';

const ContactForm: React.FC = () => {
    const [formData, setFormData] = useState<ContactFormData>({
        name: '',
        email: '',
        message: '',
    });

    const [errors, setErrors] = useState<FormErrors>({});
    const [isSubmitted, setIsSubmitted] = useState(false);
    const [isSubmitting, setIsSubmitting] = useState(false);

    // Validation functions
    const validateName = (name: string): string | undefined => {
        if (!name.trim()) {
            return 'Name is required';
        }
        if (name.trim().length < 2) {
            return 'Name must be at least 2 characters';
        }
        return undefined;
    };

    const validateEmail = (email: string): string | undefined => {
        if (!email.trim()) {
            return 'Email is required';
        }
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(email)) {
            return 'Please enter a valid email address';
        }
        return undefined;
    };

    const validateMessage = (message: string): string | undefined => {
        if (!message.trim()) {
            return 'Message is required';
        }
        if (message.trim().length < 10) {
            return 'Message must be at least 10 characters';
        }
        return undefined;
    };

    // Handle input changes
    const handleChange = (
        e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
    ) => {
        const { name, value } = e.target;
        setFormData(prev => ({ ...prev, [name]: value }));
        
        // Clear error for this field
        setErrors(prev => ({ ...prev, [name]: undefined }));
    };

    // Handle blur events for validation
    const handleBlur = (
        e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>
    ) => {
        const { name, value } = e.target;
        let error: string | undefined;

        switch (name) {
            case 'name':
                error = validateName(value);
                break;
            case 'email':
                error = validateEmail(value);
                break;
            case 'message':
                error = validateMessage(value);
                break;
        }

        setErrors(prev => ({ ...prev, [name]: error }));
    };

    // Validate entire form
    const validateForm = (): boolean => {
        const nameError = validateName(formData.name);
        const emailError = validateEmail(formData.email);
        const messageError = validateMessage(formData.message);

        setErrors({
            name: nameError,
            email: emailError,
            message: messageError,
        });

        return !nameError && !emailError && !messageError;
    };

    // Handle form submission
    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();

        if (!validateForm()) {
            return;
        }

        setIsSubmitting(true);

        // Simulate API call
        await new Promise(resolve => setTimeout(resolve, 1500));

        console.log('Form submitted:', formData);
        
        setIsSubmitted(true);
        setIsSubmitting(false);
        
        // Reset form
        setFormData({ name: '', email: '', message: '' });
    };

    if (isSubmitted) {
        return (
            <div style={styles.successMessage}>
                <h3 style={styles.successTitle}>✅ Message Sent!</h3>
                <p style={styles.successText}>
                    Thank you for reaching out! I'll get back to you as soon as possible.
                </p>
                <button
                    onClick={() => setIsSubmitted(false)}
                    style={styles.resetButton}
                >
                    Send Another Message
                </button>
            </div>
        );
    }

    return (
        <form onSubmit={handleSubmit} style={styles.form}>
            {/* Name Field */}
            <div style={styles.field}>
                <label htmlFor="name" style={styles.label}>
                    Name *
                </label>
                <input
                    type="text"
                    id="name"
                    name="name"
                    value={formData.name}
                    onChange={handleChange}
                    onBlur={handleBlur}
                    style={{
                        ...styles.input,
                        borderColor: errors.name ? '#f44336' : '#ddd',
                    }}
                    placeholder="Your name"
                />
                {errors.name && (
                    <span style={styles.error}>{errors.name}</span>
                )}
            </div>

            {/* Email Field */}
            <div style={styles.field}>
                <label htmlFor="email" style={styles.label}>
                    Email *
                </label>
                <input
                    type="email"
                    id="email"
                    name="email"
                    value={formData.email}
                    onChange={handleChange}
                    onBlur={handleBlur}
                    style={{
                        ...styles.input,
                        borderColor: errors.email ? '#f44336' : '#ddd',
                    }}
                    placeholder="your.email@example.com"
                />
                {errors.email && (
                    <span style={styles.error}>{errors.email}</span>
                )}
            </div>

            {/* Message Field */}
            <div style={styles.field}>
                <label htmlFor="message" style={styles.label}>
                    Message *
                </label>
                <textarea
                    id="message"
                    name="message"
                    value={formData.message}
                    onChange={handleChange}
                    onBlur={handleBlur}
                    rows={6}
                    style={{
                        ...styles.textarea,
                        borderColor: errors.message ? '#f44336' : '#ddd',
                    }}
                    placeholder="Your message..."
                />
                {errors.message && (
                    <span style={styles.error}>{errors.message}</span>
                )}
            </div>

            {/* Submit Button */}
            <button
                type="submit"
                disabled={isSubmitting}
                style={{
                    ...styles.submitButton,
                    opacity: isSubmitting ? 0.6 : 1,
                    cursor: isSubmitting ? 'not-allowed' : 'pointer',
                }}
            >
                {isSubmitting ? 'Sending...' : 'Send Message'}
            </button>
        </form>
    );
};

const styles = {
    form: {
        maxWidth: '600px',
        margin: '0 auto',
    },
    field: {
        marginBottom: '1.5rem',
    },
    label: {
        display: 'block',
        marginBottom: '0.5rem',
        fontSize: '1rem',
        fontWeight: 600,
        color: '#333',
    },
    input: {
        width: '100%',
        padding: '0.75rem',
        fontSize: '1rem',
        border: '2px solid #ddd',
        borderRadius: '6px',
        transition: 'border-color 0.2s ease',
        boxSizing: 'border-box' as const,
    },
    textarea: {
        width: '100%',
        padding: '0.75rem',
        fontSize: '1rem',
        border: '2px solid #ddd',
        borderRadius: '6px',
        transition: 'border-color 0.2s ease',
        resize: 'vertical' as const,
        fontFamily: 'inherit',
        boxSizing: 'border-box' as const,
    },
    error: {
        display: 'block',
        marginTop: '0.5rem',
        fontSize: '0.875rem',
        color: '#f44336',
    },
    submitButton: {
        width: '100%',
        padding: '1rem',
        fontSize: '1.125rem',
        fontWeight: 600,
        color: '#ffffff',
        backgroundColor: '#667eea',
        border: 'none',
        borderRadius: '8px',
        transition: 'background-color 0.2s ease',
    },
    successMessage: {
        maxWidth: '600px',
        margin: '0 auto',
        padding: '3rem',
        textAlign: 'center' as const,
        backgroundColor: '#e8f5e9',
        borderRadius: '12px',
    },
    successTitle: {
        fontSize: '2rem',
        fontWeight: 'bold',
        color: '#4CAF50',
        marginBottom: '1rem',
    },
    successText: {
        fontSize: '1.125rem',
        color: '#555',
        marginBottom: '2rem',
    },
    resetButton: {
        padding: '0.75rem 2rem',
        fontSize: '1rem',
        fontWeight: 600,
        color: '#667eea',
        backgroundColor: '#ffffff',
        border: '2px solid #667eea',
        borderRadius: '8px',
        cursor: 'pointer',
    },
};

export default ContactForm;

Step 2: Create Contact Section Component

src/components/Contact.tsx

import React from 'react';
import ContactForm from './ContactForm';

const Contact: React.FC = () => {
    return (
        <section id="contact" style={styles.section}>
            <div style={styles.container}>
                <h2 style={styles.heading}>Get In Touch</h2>
                
                <p style={styles.intro}>
                    Have a project in mind or just want to chat? I'd love to hear from you! 
                    Fill out the form below and I'll get back to you as soon as possible.
                </p>
                
                <ContactForm />
                
                <div style={styles.alternativeContact}>
                    <p style={styles.alternativeText}>
                        Prefer email? Reach me directly at{' '}
                        <a href="mailto:your.email@example.com" style={styles.emailLink}>
                            your.email@example.com
                        </a>
                    </p>
                </div>
            </div>
        </section>
    );
};

const styles = {
    section: {
        padding: '5rem 2rem',
        backgroundColor: '#ffffff',
    },
    container: {
        maxWidth: '1200px',
        margin: '0 auto',
    },
    heading: {
        fontSize: '2.5rem',
        fontWeight: 'bold',
        textAlign: 'center' as const,
        marginBottom: '1rem',
        color: '#333',
    },
    intro: {
        fontSize: '1.125rem',
        textAlign: 'center' as const,
        color: '#666',
        marginBottom: '3rem',
        maxWidth: '700px',
        margin: '0 auto 3rem',
    },
    alternativeContact: {
        marginTop: '3rem',
        textAlign: 'center' as const,
    },
    alternativeText: {
        fontSize: '1rem',
        color: '#666',
    },
    emailLink: {
        color: '#667eea',
        textDecoration: 'none',
        fontWeight: 600,
    },
};

export default Contact;

💡 Form Validation Patterns

  • Real-time validation: Errors clear as user types
  • Blur validation: Check field when user leaves it
  • Submit validation: Final check before submission
  • Visual feedback: Red borders for errors
  • Disabled state: Prevent multiple submissions
  • Success state: Show confirmation message

✅ Contact Form Complete!

Your contact section now has:

  • ✅ Fully validated contact form
  • ✅ Real-time error messages
  • ✅ Professional success state
  • ✅ Disabled state during submission
  • ✅ Alternative contact method
  • ✅ Type-safe form handling

🦶 Footer Component

Let's finish with a professional footer containing social links, copyright information, and any additional navigation.

Create Footer Component

src/components/Footer.tsx

import React from 'react';
import { socialLinks } from '../data/portfolio';

const Footer: React.FC = () => {
    const currentYear = new Date().getFullYear();

    return (
        <footer style={styles.footer}>
            <div style={styles.container}>
                {/* Social Links */}
                <div style={styles.socialSection}>
                    <h3 style={styles.socialHeading}>Connect With Me</h3>
                    <div style={styles.socialLinks}>
                        {socialLinks.map((link) => (
                            <a
                                key={link.platform}
                                href={link.url}
                                target="_blank"
                                rel="noopener noreferrer"
                                style={styles.socialLink}
                                aria-label={link.platform}
                            >
                                <span style={styles.socialIcon}>{link.icon}</span>
                                <span>{link.platform}</span>
                            </a>
                        ))}
                    </div>
                </div>

                {/* Divider */}
                <div style={styles.divider} />

                {/* Copyright */}
                <div style={styles.copyright}>
                    <p style={styles.copyrightText}>
                        © {currentYear} Your Name. All rights reserved.
                    </p>
                    <p style={styles.builtWith}>
                        Built with React & TypeScript
                    </p>
                </div>
            </div>
        </footer>
    );
};

const styles = {
    footer: {
        backgroundColor: '#1a1a1a',
        color: '#ffffff',
        padding: '3rem 2rem 2rem',
    },
    container: {
        maxWidth: '1200px',
        margin: '0 auto',
    },
    socialSection: {
        textAlign: 'center' as const,
        marginBottom: '2rem',
    },
    socialHeading: {
        fontSize: '1.5rem',
        fontWeight: 'bold',
        marginBottom: '1.5rem',
        color: '#ffffff',
    },
    socialLinks: {
        display: 'flex',
        justifyContent: 'center',
        gap: '2rem',
        flexWrap: 'wrap' as const,
    },
    socialLink: {
        display: 'flex',
        alignItems: 'center',
        gap: '0.5rem',
        color: '#ffffff',
        textDecoration: 'none',
        fontSize: '1.125rem',
        transition: 'color 0.2s ease',
    },
    socialIcon: {
        fontSize: '1.5rem',
    },
    divider: {
        height: '1px',
        backgroundColor: '#444',
        margin: '2rem 0',
    },
    copyright: {
        textAlign: 'center' as const,
    },
    copyrightText: {
        fontSize: '1rem',
        color: '#ccc',
        marginBottom: '0.5rem',
    },
    builtWith: {
        fontSize: '0.875rem',
        color: '#888',
    },
};

export default Footer;

✅ Footer Complete!

Your footer now includes:

  • ✅ Social media links with icons
  • ✅ Dynamic copyright year
  • ✅ Professional dark styling
  • ✅ Accessibility attributes
  • ✅ Responsive layout

🎉 Complete Solution

Now let's bring everything together! Here's the complete App.tsx that assembles all your components into a working portfolio.

Main App Component

src/App.tsx

import React from 'react';
import Navigation from './components/Navigation';
import Hero from './components/Hero';
import About from './components/About';
import Skills from './components/Skills';
import Projects from './components/Projects';
import Contact from './components/Contact';
import Footer from './components/Footer';
import './App.css';

function App() {
    return (
        <div className="App">
            <Navigation />
            <main>
                <Hero />
                <About />
                <Skills />
                <Projects />
                <Contact />
            </main>
            <Footer />
        </div>
    );
}

export default App;

Basic Global Styles

src/App.css

/* Reset and Base Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
        'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
        sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    line-height: 1.6;
}

html {
    scroll-behavior: smooth;
}

/* Ensure sections have proper spacing with fixed nav */
section {
    scroll-margin-top: 80px;
}

/* Link hover effects */
a {
    transition: color 0.2s ease;
}

a:hover {
    opacity: 0.8;
}

/* Button hover effects */
button:hover:not(:disabled) {
    opacity: 0.9;
}

/* Input focus styles */
input:focus,
textarea:focus {
    outline: none;
    border-color: #667eea !important;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}

/* Responsive Design */
@media (max-width: 768px) {
    /* Hide navigation links on mobile - you can add a hamburger menu here */
    nav ul {
        display: none;
    }
    
    /* Adjust heading sizes for mobile */
    h1 {
        font-size: 2rem !important;
    }
    
    h2 {
        font-size: 2rem !important;
    }
    
    /* Adjust hero section for mobile */
    section#home {
        padding: 1rem;
        min-height: auto;
        padding-top: 100px;
        padding-bottom: 60px;
    }
    
    /* Stack flex items on mobile */
    .about-content,
    .button-group {
        flex-direction: column;
        align-items: center;
    }
}

Complete File Structure

Your Final Project Structure

portfolio/
├── src/
│   ├── components/
│   │   ├── Navigation.tsx          ✅
│   │   ├── Hero.tsx                ✅
│   │   ├── About.tsx               ✅
│   │   ├── Skills.tsx              ✅
│   │   ├── SkillCard.tsx           ✅
│   │   ├── Projects.tsx            ✅
│   │   ├── ProjectCard.tsx         ✅
│   │   ├── Contact.tsx             ✅
│   │   ├── ContactForm.tsx         ✅
│   │   └── Footer.tsx              ✅
│   ├── types/
│   │   └── index.ts                ✅
│   ├── data/
│   │   └── portfolio.ts            ✅
│   ├── App.tsx                     ✅
│   ├── App.css                     ✅
│   ├── main.tsx                    ✅
│   └── vite-env.d.ts
├── public/
│   └── favicon.ico
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

Running Your Portfolio

Development Commands

# Start development server
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview

# Type check
npm run type-check

🎊 Congratulations!

You've built a complete, professional portfolio website with:

  • ✅ 10 React components with proper TypeScript typing
  • ✅ Responsive design for all screen sizes
  • ✅ Interactive forms with validation
  • ✅ Smooth scrolling and animations
  • ✅ Reusable component patterns
  • ✅ Professional styling and UX
  • ✅ Type-safe event handling
  • ✅ Real-world portfolio you can deploy!

🚀 Enhancements & Extensions

Your portfolio is complete, but here are some ideas to take it to the next level! Try implementing these enhancements to practice your skills further.

Easy Enhancements (30-60 minutes each)

✨ Enhancement 1: Dark Mode Toggle

Add a theme switcher to toggle between light and dark modes.

💡 Implementation Hint
// Use useState to track theme
const [theme, setTheme] = useState<'light' | 'dark'>('light');

// Create a theme toggle button in Navigation
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
    {theme === 'light' ? '🌙' : '☀️'}
</button>

// Pass theme to all sections and adjust colors accordingly

✨ Enhancement 2: Mobile Hamburger Menu

Add a responsive hamburger menu for mobile devices.

💡 Implementation Hint
const [isMenuOpen, setIsMenuOpen] = useState(false);

// Add hamburger button (visible only on mobile)
<button 
    onClick={() => setIsMenuOpen(!isMenuOpen)}
    style={{ display: 'none' }} // Show with media query
>
    ☰
</button>

// Conditionally render menu
{isMenuOpen && (
    <div style={styles.mobileMenu}>
        {/* Navigation links */}
    </div>
)}

✨ Enhancement 3: Scroll-to-Top Button

Add a button that appears when scrolling down and returns to the top.

💡 Implementation Hint
const [showScrollTop, setShowScrollTop] = useState(false);

useEffect(() => {
    const handleScroll = () => {
        setShowScrollTop(window.scrollY > 300);
    };
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
}, []);

// Render button conditionally
{showScrollTop && (
    <button 
        onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
        style={styles.scrollTopButton}
    >
        ↑
    </button>
)}

Medium Enhancements (1-2 hours each)

🎨 Enhancement 4: Animation on Scroll

Add animations that trigger when sections scroll into view.

💡 Implementation Hint

Use the Intersection Observer API to detect when elements enter the viewport:

useEffect(() => {
    const observer = new IntersectionObserver(
        (entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    entry.target.classList.add('fade-in');
                }
            });
        },
        { threshold: 0.1 }
    );

    const sections = document.querySelectorAll('section');
    sections.forEach(section => observer.observe(section));

    return () => observer.disconnect();
}, []);

🎨 Enhancement 5: Project Filtering

Add category filters to the projects section.

💡 Implementation Hint
const [filter, setFilter] = useState<string>('all');
const categories = ['all', 'web', 'mobile', 'api'];

const filteredProjects = projects.filter(project => 
    filter === 'all' || project.category === filter
);

// Render filter buttons
{categories.map(cat => (
    <button 
        onClick={() => setFilter(cat)}
        style={{
            ...styles.filterButton,
            backgroundColor: filter === cat ? '#667eea' : '#fff'
        }}
    >
        {cat}
    </button>
))}

🎨 Enhancement 6: Testimonials Section

Add a new section for client testimonials or recommendations.

💡 Implementation Hint
interface Testimonial {
    id: number;
    name: string;
    role: string;
    company: string;
    text: string;
    avatar?: string;
}

const TestimonialCard: React.FC<{ testimonial: Testimonial }> = ({ testimonial }) => {
    return (
        <div style={styles.testimonialCard}>
            <p style={styles.quote}>"{testimonial.text}"</p>
            <p style={styles.author}>
                <strong>{testimonial.name}</strong>
                <br />
                {testimonial.role} at {testimonial.company}
            </p>
        </div>
    );
};

Advanced Enhancements (2-4 hours each)

⚡ Enhancement 7: Blog Section

Add a blog section with articles about your development journey.

  • Create a BlogPost interface and BlogCard component
  • Display blog previews with read more links
  • Add filtering by category or tags
  • Include reading time estimation

⚡ Enhancement 8: Backend Integration

Connect your contact form to a real backend or email service.

  • Use EmailJS or similar service for sending emails
  • Or create a simple API endpoint with Node.js/Express
  • Add proper error handling for failed submissions
  • Store submissions in a database (optional)

⚡ Enhancement 9: CMS Integration

Use a headless CMS to manage your content dynamically.

  • Set up Contentful, Sanity, or Strapi
  • Fetch projects and blog posts from the CMS
  • Update content without redeploying
  • Add image optimization

💡 Challenge Yourself!

Pick 2-3 enhancements that interest you most and implement them. These additions will:

  • Deepen your React and TypeScript skills
  • Make your portfolio stand out
  • Give you more features to discuss in interviews
  • Provide practice with real-world scenarios

🌐 Deployment & Next Steps

Your portfolio is ready to share with the world! Let's deploy it and discuss what comes next in your React journey.

Deploying Your Portfolio

Option 1: Vercel (Recommended)

Vercel offers the easiest deployment for React applications.

# Install Vercel CLI
npm install -g vercel

# Deploy
vercel

# Follow the prompts to deploy your project

Or use the Vercel Dashboard:

  1. Push your code to GitHub
  2. Go to vercel.com
  3. Import your GitHub repository
  4. Vercel auto-detects Vite and deploys!

Option 2: Netlify

Another excellent option with drag-and-drop deployment.

# Build your project
npm run build

# The dist/ folder contains your production build

Deploy via Netlify:

  1. Go to netlify.com
  2. Drag and drop your dist folder
  3. Or connect your GitHub repo for automatic deployments

Option 3: GitHub Pages

Free hosting directly from your GitHub repository.

# Install gh-pages
npm install --save-dev gh-pages

# Add to package.json scripts:
"scripts": {
    "predeploy": "npm run build",
    "deploy": "gh-pages -d dist"
}

# Update vite.config.ts base URL:
export default defineConfig({
    base: '/your-repo-name/',
    // ... rest of config
})

# Deploy
npm run deploy

Before You Deploy - Checklist

Task Status
Update all placeholder text with your information
Replace sample projects with your actual projects
Add your real social media links
Test all links and navigation
Add a professional photo (optional)
Test on mobile devices
Check for TypeScript errors
Update favicon and title
Add meta tags for SEO
Test contact form functionality

What You've Learned

Module 2 Recap

Throughout this module project, you've applied:

From Lesson 2.1 (Intro to React):

  • Component-based architecture
  • Functional components
  • Project structure and organization

From Lesson 2.2 (JSX and TSX):

  • JSX syntax and expressions
  • Conditional rendering
  • Mapping over arrays to render lists
  • TypeScript in JSX

From Lesson 2.3 (Components and Props):

  • Creating reusable components (SkillCard, ProjectCard)
  • Defining and typing props with interfaces
  • Component composition
  • Props destructuring

From Lesson 2.4 (Styling):

  • Inline styles with TypeScript
  • Style objects and dynamic styling
  • Responsive design principles
  • Professional color schemes and layouts

From Lesson 2.5 (Events):

  • Event handlers (onClick, onChange, onSubmit)
  • Form handling and validation
  • Typing events with TypeScript
  • Controlled components

Next Steps in Your Journey

🚀 Where to Go From Here

Module 3: State and Interactivity

  • Learn the useState hook in depth
  • Master state management patterns
  • Build complex interactive applications
  • Understand lifting state up

Keep Building:

  • Add the enhancements from Section 12
  • Build variations of your portfolio
  • Create portfolios for friends or family
  • Experiment with different designs

Share Your Work:

  • Deploy your portfolio and share the link
  • Add it to your resume and LinkedIn
  • Ask for feedback from other developers
  • Use it in job applications

✅ Project Complete!

Congratulations! You've built a professional portfolio from scratch!

You now have:

  • ✅ A fully functional, professional portfolio website
  • ✅ Experience with React component architecture
  • ✅ TypeScript skills for type-safe development
  • ✅ Understanding of modern styling approaches
  • ✅ Knowledge of form handling and validation
  • ✅ A real project to showcase your skills
  • ✅ Confidence to build more React applications

Great work! You're ready for Module 3! 🎉

← Previous Lesson 2.5: Events in React Home Next → Module 3: State and Interactivity