Skip to main content

πŸ“ Module Project: Multi-Page Blog Application

Congratulations on mastering React Router! You've learned the fundamentals of routing, navigation, dynamic parameters, protected routes, query parameters, and sophisticated layout systems. Now it's time to bring it all together in a comprehensive multi-page application. In this module project, you'll build a feature-rich blog platform that showcases professional routing architecture, multiple layouts, search functionality, and seamless navigation. This is your opportunity to create a production-quality application that demonstrates real-world routing patterns! πŸš€

🎯 Project Objectives

By completing this project, you will:

  • Build a complete multi-page blog platform with React Router and TypeScript
  • Implement multiple layouts for different sections (public, auth, dashboard)
  • Create dynamic routes with parameters for posts, categories, and authors
  • Build protected routes with authentication simulation
  • Implement search and filtering with query parameters
  • Add breadcrumb navigation that updates based on location
  • Handle nested routing for author profiles and settings
  • Create loading states and error boundaries for routes
  • Build a responsive navigation system with mobile support
  • Implement programmatic navigation and redirects

Estimated Time: 4-6 hours

Difficulty: Advanced - applies all Module 6 concepts

πŸ’‘ What You'll Build

A production-ready blog application featuring:

  • 🏠 Public homepage with featured posts
  • πŸ“š Blog listing with categories and search
  • πŸ“ Individual post pages with comments
  • πŸ‘€ Author profile pages with their posts
  • πŸ” Login/register pages with authentication
  • πŸ“Š Dashboard for managing posts (protected)
  • βš™οΈ Settings section with nested routes
  • πŸ” Search with query parameters and filters
  • 🍞 Dynamic breadcrumb navigation
  • 🎨 Multiple layouts (public, auth, dashboard)
  • πŸ“± Responsive navigation with mobile menu
  • ⚑ Loading states and error handling
  • 404 Not Found page with helpful navigation
  • πŸ”„ Smooth transitions between routes

πŸ“‘ Project Sections

πŸ“‹ Project Requirements & Features

Let's break down the complete feature set we're building. This isn't just a simple blogβ€”it's a sophisticated multi-page application that demonstrates professional routing patterns you'll use in real-world applications. Each feature builds on the routing concepts you've learned throughout Module 6.

Core Features Breakdown

graph TD A[Blog Application] --> B[Public Section] A --> C[Auth Section] A --> D[Dashboard Section] B --> B1[Home Page] B --> B2[Blog List] B --> B3[Post Detail] B --> B4[Author Profiles] B --> B5[Category Pages] C --> C1[Login] C --> C2[Register] C --> C3[Forgot Password] D --> D1[My Posts] D --> D2[Create Post] D --> D3[Edit Post] D --> D4[Settings] D4 --> D4A[Profile Settings] D4 --> D4B[Account Settings] D4 --> D4C[Preferences] style A fill:#667eea,color:#fff style B fill:#48bb78,color:#fff style C fill:#ed8936,color:#fff style D fill:#9f7aea,color:#fff

Essential Functionality

1️⃣ Public Pages (No Authentication Required)

Page Route Features
Home / Featured posts, recent posts, category links
Blog List /blog All posts with search, filter by category, pagination
Post Detail /blog/:postId Full post content, author info, related posts
Category /category/:categoryName Posts filtered by category
Author Profile /author/:authorId Author bio, their posts, statistics
About /about About the blog platform

2️⃣ Authentication Pages

Page Route Features
Login /login Email/password login, remember me, redirects
Register /register Create new account, validation, auto-login
Forgot Password /forgot-password Password reset email simulation

3️⃣ Protected Dashboard Pages (Authentication Required)

Page Route Features
Dashboard /dashboard Overview, stats, recent activity
My Posts /dashboard/posts List of user's posts, edit/delete actions
Create Post /dashboard/posts/new Form to create new blog post
Edit Post /dashboard/posts/:postId/edit Edit existing post
Settings (nested) /dashboard/settings Index page with overview of settings
β”œβ”€ Profile /dashboard/settings/profile Edit profile information
β”œβ”€ Account /dashboard/settings/account Email, password, security
└─ Preferences /dashboard/settings/preferences Theme, notifications, display options

Technical Requirements

πŸ”§ Implementation Standards

Routing Requirements:

  • βœ… React Router v6+ with TypeScript
  • βœ… Multiple layout components (PublicLayout, AuthLayout, DashboardLayout)
  • βœ… Nested routes with proper Outlet placement
  • βœ… Dynamic route parameters (postId, authorId, categoryName)
  • βœ… Query parameters for search and filters
  • βœ… Protected routes with redirect logic
  • βœ… 404 Not Found page with helpful navigation
  • βœ… Loading states during route transitions
  • βœ… Error boundaries for route errors

Navigation Requirements:

  • βœ… Responsive navigation bar in each layout
  • βœ… Active link highlighting with NavLink
  • βœ… Breadcrumb navigation showing current path
  • βœ… Programmatic navigation after actions
  • βœ… Back navigation where appropriate
  • βœ… Mobile-friendly hamburger menu

State Management:

  • βœ… Authentication state (simulated with localStorage)
  • βœ… Mock data for posts, authors, categories
  • βœ… Search and filter state in URL query params
  • βœ… Form state for create/edit operations

TypeScript Requirements:

  • βœ… All components properly typed
  • βœ… Route parameter types defined
  • βœ… Props interfaces for all components
  • βœ… Data model interfaces (Post, Author, Category)
  • βœ… No use of any type

User Experience Requirements

πŸ’­ User Stories

As a visitor, I want to:

  • Browse blog posts on the homepage and blog listing page
  • Search for posts by title or content
  • Filter posts by category
  • Read individual blog posts with full content
  • View author profiles and their posts
  • Navigate easily with clear breadcrumbs
  • See helpful error messages if a page doesn't exist

As an authenticated user, I want to:

  • Log in to access my dashboard
  • Create new blog posts
  • Edit and delete my existing posts
  • Manage my profile settings
  • Customize my account preferences
  • See my post statistics
  • Be redirected to my intended destination after login

As a developer, I want to:

  • Have a clear, maintainable routing structure
  • Easily add new routes and features
  • Reuse layout components across sections
  • Handle errors gracefully at the route level
  • Type-safe route parameters and navigation

⚠️ Important Project Notes

  • Authentication is simulated - We'll use localStorage to simulate login state. This is NOT production-ready authentication but demonstrates routing patterns.
  • Data is mocked - We'll use mock data arrays instead of real API calls. The focus is on routing, not data fetching.
  • Build incrementally - This is a large project. Build it section by section, testing each part before moving on.
  • Focus on routing - While we'll create nice UI, the main learning goal is mastering React Router patterns.
  • Responsive design - Test on different screen sizes as you build, especially the navigation.

Success Criteria Checklist

βœ… Your Project is Complete When:

  • ☐ All routes are defined and working
  • ☐ Three distinct layouts are implemented
  • ☐ Navigation works on all screen sizes
  • ☐ Protected routes redirect to login
  • ☐ Login redirects back to intended page
  • ☐ Search and filters update URL params
  • ☐ Breadcrumbs show current location
  • ☐ 404 page handles invalid routes
  • ☐ Loading states show during navigation
  • ☐ Dynamic routes load correct data
  • ☐ Nested settings routes work
  • ☐ Active links are highlighted
  • ☐ All TypeScript types are defined
  • ☐ Code is organized and readable
  • ☐ App is tested on mobile and desktop

πŸ› οΈ Project Setup & Planning

Before diving into code, let's set up our project properly and create a clear plan. A well-organized project structure makes development smoother and your code more maintainable.

Step 1: Create the Project

If you haven't already, create a new React + TypeScript project using Vite:

# Create new project
npm create vite@latest blog-app -- --template react-ts

# Navigate to project
cd blog-app

# Install dependencies
npm install

# Install React Router
npm install react-router-dom

# Install TypeScript types for React Router (if needed)
npm install -D @types/react-router-dom

πŸ’‘ Project Structure Preview

We'll organize our code like this:

blog-app/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/          # Reusable UI components
β”‚   β”‚   β”œβ”€β”€ Breadcrumbs.tsx
β”‚   β”‚   β”œβ”€β”€ PostCard.tsx
β”‚   β”‚   β”œβ”€β”€ Navbar.tsx
β”‚   β”‚   └── LoadingSpinner.tsx
β”‚   β”œβ”€β”€ layouts/            # Layout components
β”‚   β”‚   β”œβ”€β”€ PublicLayout.tsx
β”‚   β”‚   β”œβ”€β”€ AuthLayout.tsx
β”‚   β”‚   └── DashboardLayout.tsx
β”‚   β”œβ”€β”€ pages/              # Page components (routes)
β”‚   β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”‚   β”œβ”€β”€ HomePage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ BlogListPage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ PostDetailPage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ AuthorProfilePage.tsx
β”‚   β”‚   β”‚   └── CategoryPage.tsx
β”‚   β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”‚   β”œβ”€β”€ LoginPage.tsx
β”‚   β”‚   β”‚   β”œβ”€β”€ RegisterPage.tsx
β”‚   β”‚   β”‚   └── ForgotPasswordPage.tsx
β”‚   β”‚   └── dashboard/
β”‚   β”‚       β”œβ”€β”€ DashboardHomePage.tsx
β”‚   β”‚       β”œβ”€β”€ MyPostsPage.tsx
β”‚   β”‚       β”œβ”€β”€ CreatePostPage.tsx
β”‚   β”‚       β”œβ”€β”€ EditPostPage.tsx
β”‚   β”‚       └── settings/
β”‚   β”‚           β”œβ”€β”€ SettingsLayout.tsx
β”‚   β”‚           β”œβ”€β”€ ProfileSettingsPage.tsx
β”‚   β”‚           β”œβ”€β”€ AccountSettingsPage.tsx
β”‚   β”‚           └── PreferencesPage.tsx
β”‚   β”œβ”€β”€ data/               # Mock data
β”‚   β”‚   β”œβ”€β”€ posts.ts
β”‚   β”‚   β”œβ”€β”€ authors.ts
β”‚   β”‚   └── categories.ts
β”‚   β”œβ”€β”€ types/              # TypeScript type definitions
β”‚   β”‚   └── index.ts
β”‚   β”œβ”€β”€ utils/              # Utility functions
β”‚   β”‚   └── auth.ts
β”‚   β”œβ”€β”€ App.tsx             # Main app with routing
β”‚   β”œβ”€β”€ main.tsx            # Entry point
β”‚   └── index.css           # Global styles
└── package.json

Step 2: Clean Up Default Files

Remove unnecessary default files and code:

  1. Delete src/App.css (we'll use our own styles)
  2. Clean up src/App.tsx - remove the default content
  3. Clean up src/index.css - we'll add our own base styles
  4. Update src/main.tsx if needed

Step 3: Plan the Development Order

We'll build the project in this order to ensure a logical progression:

graph TD A[1. Data Models & Types] --> B[2. Mock Data] B --> C[3. Route Structure] C --> D[4. Layout Components] D --> E[5. Public Pages] E --> F[6. Auth System] F --> G[7. Protected Routes] G --> H[8. Dashboard Pages] H --> I[9. Search & Filters] I --> J[10. Polish & Styling] style A fill:#667eea,color:#fff style J fill:#48bb78,color:#fff

βœ… Pro Tips for Success

  • Test frequently - Run npm run dev and check your work after each major addition
  • Use TypeScript - Define types early and let TypeScript catch errors
  • Component first - Build small, focused components before assembling pages
  • Start simple - Get basic routing working before adding complex features
  • Console log - Use console.log to verify params, location, and navigation state
  • Read errors carefully - React Router error messages are usually very helpful
  • Commit often - Use Git to save your progress at each milestone

Step 4: Install Additional Dependencies (Optional)

You might want these helpful packages:

# For better date formatting (if needed)
npm install date-fns

# For unique IDs (if needed)
npm install uuid
npm install -D @types/uuid

# For icons (optional)
npm install react-icons

⚠️ Keep It Simple

While these packages are nice, they're not required for this project. The focus is on React Router, not on external libraries. You can build everything with just React, TypeScript, and React Router!

Development Workflow

Follow this workflow as you build:

  1. Read the section - Understand what you're building
  2. Look at the code - Study the example implementations
  3. Type it yourself - Don't copy-paste, type it to learn
  4. Test it - Verify it works in the browser
  5. Experiment - Try variations and see what happens
  6. Move forward - Once it works, proceed to the next section

🎯 Ready to Build?

You have your project set up and a clear plan. Now let's start building! We'll begin with the foundation: defining our data models and types.

πŸ“Š Data Modeling & Mock Data

Before we build any UI or routing, we need to define our data structures. Good TypeScript types and mock data will make the rest of development much smoother. Let's create the data foundation for our blog application.

Step 1: Define TypeScript Interfaces

Create src/types/index.ts with all our data models:

// src/types/index.ts

// User/Author types
export interface Author {
  id: string;
  name: string;
  email: string;
  avatar: string;
  bio: string;
  website?: string;
  social: {
    twitter?: string;
    github?: string;
    linkedin?: string;
  };
  postCount: number;
  joinedDate: string;
}

// Blog post types
export interface Post {
  id: string;
  title: string;
  slug: string;
  excerpt: string;
  content: string;
  author: Author;
  category: Category;
  tags: string[];
  publishedDate: string;
  updatedDate?: string;
  readTime: number; // in minutes
  views: number;
  likes: number;
  featured: boolean;
  coverImage: string;
}

// Category types
export interface Category {
  id: string;
  name: string;
  slug: string;
  description: string;
  postCount: number;
  color: string;
}

// Comment types (for future use)
export interface Comment {
  id: string;
  postId: string;
  author: {
    name: string;
    avatar: string;
  };
  content: string;
  createdDate: string;
  likes: number;
}

// Authentication types
export interface User {
  id: string;
  email: string;
  name: string;
  avatar: string;
  role: 'admin' | 'author' | 'reader';
}

export interface AuthState {
  isAuthenticated: boolean;
  user: User | null;
}

// Form types
export interface LoginFormData {
  email: string;
  password: string;
  rememberMe: boolean;
}

export interface RegisterFormData {
  name: string;
  email: string;
  password: string;
  confirmPassword: string;
}

export interface PostFormData {
  title: string;
  excerpt: string;
  content: string;
  categoryId: string;
  tags: string[];
  coverImage: string;
  featured: boolean;
}

// Filter and search types
export interface BlogFilters {
  search?: string;
  category?: string;
  author?: string;
  tag?: string;
  sortBy?: 'date' | 'views' | 'likes';
  sortOrder?: 'asc' | 'desc';
}

// Statistics types
export interface UserStats {
  totalPosts: number;
  totalViews: number;
  totalLikes: number;
  draftPosts: number;
  publishedPosts: number;
}

πŸ’‘ Why These Types Matter

These TypeScript interfaces provide:

  • Type safety - Catch errors at compile time
  • Autocomplete - VS Code will suggest properties
  • Documentation - Types show what data looks like
  • Refactoring safety - Rename properties with confidence

Step 2: Create Mock Categories

Create src/data/categories.ts:

// src/data/categories.ts
import { Category } from '../types';

export const categories: Category[] = [
  {
    id: 'cat-1',
    name: 'Web Development',
    slug: 'web-development',
    description: 'Articles about HTML, CSS, JavaScript, and web frameworks',
    postCount: 15,
    color: '#3B82F6', // Blue
  },
  {
    id: 'cat-2',
    name: 'React',
    slug: 'react',
    description: 'Deep dives into React, hooks, and the React ecosystem',
    postCount: 12,
    color: '#06B6D4', // Cyan
  },
  {
    id: 'cat-3',
    name: 'TypeScript',
    slug: 'typescript',
    description: 'Type-safe JavaScript development with TypeScript',
    postCount: 8,
    color: '#3178C6', // TypeScript Blue
  },
  {
    id: 'cat-4',
    name: 'Career',
    slug: 'career',
    description: 'Tips for developers to grow their careers',
    postCount: 6,
    color: '#10B981', // Green
  },
  {
    id: 'cat-5',
    name: 'Tutorials',
    slug: 'tutorials',
    description: 'Step-by-step guides and how-to articles',
    postCount: 10,
    color: '#8B5CF6', // Purple
  },
  {
    id: 'cat-6',
    name: 'Best Practices',
    slug: 'best-practices',
    description: 'Industry standards and coding best practices',
    postCount: 7,
    color: '#EF4444', // Red
  },
];

// Helper function to find category by slug
export function getCategoryBySlug(slug: string): Category | undefined {
  return categories.find(cat => cat.slug === slug);
}

// Helper function to find category by ID
export function getCategoryById(id: string): Category | undefined {
  return categories.find(cat => cat.id === id);
}

Step 3: Create Mock Authors

Create src/data/authors.ts:

// src/data/authors.ts
import { Author } from '../types';

export const authors: Author[] = [
  {
    id: 'author-1',
    name: 'Sarah Johnson',
    email: 'sarah@example.com',
    avatar: 'https://i.pravatar.cc/150?img=1',
    bio: 'Full-stack developer with a passion for React and TypeScript. Love teaching others through writing.',
    website: 'https://sarahjohnson.dev',
    social: {
      twitter: '@sarahj_dev',
      github: 'sarahjohnson',
      linkedin: 'sarah-johnson-dev',
    },
    postCount: 12,
    joinedDate: '2023-01-15',
  },
  {
    id: 'author-2',
    name: 'Michael Chen',
    email: 'michael@example.com',
    avatar: 'https://i.pravatar.cc/150?img=12',
    bio: 'Senior software engineer specializing in frontend architecture and performance optimization.',
    website: 'https://michaelchen.tech',
    social: {
      twitter: '@mchen_tech',
      github: 'michaelchen',
    },
    postCount: 8,
    joinedDate: '2023-03-20',
  },
  {
    id: 'author-3',
    name: 'Emily Rodriguez',
    email: 'emily@example.com',
    avatar: 'https://i.pravatar.cc/150?img=5',
    bio: 'JavaScript enthusiast and tech writer. Making complex concepts simple and accessible.',
    social: {
      twitter: '@emily_codes',
      github: 'emilyrodriguez',
      linkedin: 'emily-rodriguez',
    },
    postCount: 15,
    joinedDate: '2022-11-10',
  },
  {
    id: 'author-4',
    name: 'David Kim',
    email: 'david@example.com',
    avatar: 'https://i.pravatar.cc/150?img=8',
    bio: 'React consultant and open source contributor. Building better web experiences one component at a time.',
    website: 'https://davidkim.io',
    social: {
      github: 'davidkim',
      linkedin: 'david-kim-dev',
    },
    postCount: 6,
    joinedDate: '2023-05-01',
  },
];

// Helper function to find author by ID
export function getAuthorById(id: string): Author | undefined {
  return authors.find(author => author.id === id);
}

// Helper function to get author by name (for search)
export function getAuthorByName(name: string): Author | undefined {
  return authors.find(
    author => author.name.toLowerCase() === name.toLowerCase()
  );
}

Step 4: Create Mock Posts

Create src/data/posts.ts with a comprehensive set of blog posts:

// src/data/posts.ts
import { Post } from '../types';
import { authors } from './authors';
import { categories } from './categories';

export const posts: Post[] = [
  {
    id: 'post-1',
    title: 'Getting Started with React Router v6',
    slug: 'getting-started-react-router-v6',
    excerpt: 'Learn the fundamentals of React Router v6 and how to implement routing in your React applications.',
    content: `# Getting Started with React Router v6

React Router is the standard routing library for React applications. In this comprehensive guide, we'll explore the fundamentals of React Router v6 and learn how to implement routing in your applications.

## What is React Router?

React Router is a collection of navigational components that compose declaratively with your application. Whether you want to have bookmarkable URLs for your web app or a composable way to navigate in React Native, React Router works wherever React is rendering.

## Installation

First, install React Router in your project:

\`\`\`bash
npm install react-router-dom
\`\`\`

## Basic Setup

Here's a simple example of setting up React Router:

\`\`\`tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </BrowserRouter>
  );
}
\`\`\`

## Navigation

Use the Link component to navigate between routes:

\`\`\`tsx
import { Link } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Link to="/contact">Contact</Link>
    </nav>
  );
}
\`\`\`

## Conclusion

React Router v6 provides a powerful and flexible way to handle routing in your React applications. Start with these basics and explore more advanced features as you build!`,
    author: authors[0],
    category: categories[1], // React
    tags: ['react', 'routing', 'tutorial'],
    publishedDate: '2024-01-15',
    readTime: 8,
    views: 1250,
    likes: 89,
    featured: true,
    coverImage: 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=800',
  },
  {
    id: 'post-2',
    title: 'TypeScript Best Practices for 2024',
    slug: 'typescript-best-practices-2024',
    excerpt: 'Discover the latest TypeScript best practices to write cleaner, more maintainable code.',
    content: `# TypeScript Best Practices for 2024

TypeScript has become an essential tool for modern web development. Here are the best practices you should follow in 2024.

## Use Strict Mode

Always enable strict mode in your \`tsconfig.json\`:

\`\`\`json
{
  "compilerOptions": {
    "strict": true
  }
}
\`\`\`

## Avoid the 'any' Type

The \`any\` type defeats the purpose of TypeScript. Instead, use proper types or \`unknown\` when the type is truly unknown.

## Leverage Utility Types

TypeScript provides powerful utility types:

\`\`\`typescript
type Partial<T> // Makes all properties optional
type Required<T> // Makes all properties required
type Pick<T, K> // Pick specific properties
type Omit<T, K> // Omit specific properties
\`\`\`

## Conclusion

Following these best practices will help you write more robust TypeScript code!`,
    author: authors[1],
    category: categories[2], // TypeScript
    tags: ['typescript', 'best-practices', 'coding'],
    publishedDate: '2024-01-20',
    readTime: 6,
    views: 980,
    likes: 67,
    featured: true,
    coverImage: 'https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=800',
  },
  {
    id: 'post-3',
    title: 'Building Responsive Layouts with CSS Grid',
    slug: 'responsive-layouts-css-grid',
    excerpt: 'Master CSS Grid to create beautiful, responsive layouts for modern web applications.',
    content: `# Building Responsive Layouts with CSS Grid

CSS Grid is a powerful layout system that makes creating complex, responsive designs much easier. Let's explore how to use it effectively.

## Grid Basics

Define a grid container:

\`\`\`css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
\`\`\`

## Responsive Grids

Use \`auto-fit\` and \`minmax\` for responsive layouts:

\`\`\`css
.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
}
\`\`\`

This creates a grid that automatically adjusts the number of columns based on available space!

## Grid Areas

Name your grid areas for cleaner code:

\`\`\`css
.layout {
  display: grid;
  grid-template-areas:
    "header header header"
    "sidebar main main"
    "footer footer footer";
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
\`\`\`

## Conclusion

CSS Grid simplifies responsive layout creation. Start using it in your projects today!`,
    author: authors[2],
    category: categories[0], // Web Development
    tags: ['css', 'grid', 'responsive', 'layout'],
    publishedDate: '2024-01-18',
    readTime: 10,
    views: 1420,
    likes: 102,
    featured: false,
    coverImage: 'https://images.unsplash.com/photo-1507721999472-8ed4421c4af2?w=800',
  },
  {
    id: 'post-4',
    title: 'React Hooks: A Complete Guide',
    slug: 'react-hooks-complete-guide',
    excerpt: 'Everything you need to know about React Hooks, from basics to advanced patterns.',
    content: `# React Hooks: A Complete Guide

React Hooks revolutionized how we write React components. This guide covers everything from basics to advanced patterns.

## useState

The most basic hook for managing state:

\`\`\`tsx
const [count, setCount] = useState(0);
\`\`\`

## useEffect

Handle side effects in your components:

\`\`\`tsx
useEffect(() => {
  document.title = \`Count: \${count}\`;
}, [count]);
\`\`\`

## Custom Hooks

Create reusable logic:

\`\`\`tsx
function useFetch(url: string) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}
\`\`\`

## Conclusion

Hooks make React code more reusable and easier to understand. Master them to become a better React developer!`,
    author: authors[0],
    category: categories[1], // React
    tags: ['react', 'hooks', 'tutorial', 'javascript'],
    publishedDate: '2024-01-22',
    readTime: 12,
    views: 2100,
    likes: 156,
    featured: true,
    coverImage: 'https://images.unsplash.com/photo-1633356122102-3fe601e05bd2?w=800',
  },
  {
    id: 'post-5',
    title: 'Career Tips for Junior Developers',
    slug: 'career-tips-junior-developers',
    excerpt: 'Essential advice for junior developers starting their career in tech.',
    content: `# Career Tips for Junior Developers

Starting your career as a developer can be overwhelming. Here are essential tips to help you succeed.

## 1. Focus on Fundamentals

Master JavaScript, HTML, and CSS before jumping to frameworks. A solid foundation will serve you throughout your career.

## 2. Build Projects

Create real projects, not just tutorial follow-alongs. Put them on GitHub and deploy them.

## 3. Learn to Debug

Debugging is a crucial skill. Get comfortable with browser DevTools and learn to read error messages.

## 4. Ask Questions

Don't be afraid to ask for help. Every senior developer was once a junior.

## 5. Contribute to Open Source

Start with small contributions. It's great for learning and building your resume.

## 6. Network

Attend meetups, join online communities, and connect with other developers.

## Conclusion

Your first year as a developer is about learning and growth. Be patient with yourself and keep coding!`,
    author: authors[3],
    category: categories[3], // Career
    tags: ['career', 'beginners', 'advice'],
    publishedDate: '2024-01-25',
    readTime: 7,
    views: 890,
    likes: 54,
    featured: false,
    coverImage: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800',
  },
  {
    id: 'post-6',
    title: 'Advanced TypeScript: Generics',
    slug: 'advanced-typescript-generics',
    excerpt: 'Deep dive into TypeScript generics and how to use them effectively.',
    content: `# Advanced TypeScript: Generics

Generics are one of TypeScript's most powerful features. They allow you to write reusable, type-safe code.

## Basic Generics

A simple generic function:

\`\`\`typescript
function identity<T>(arg: T): T {
  return arg;
}

const result = identity<string>("hello"); // Type is string
\`\`\`

## Generic Constraints

Constrain what types can be used:

\`\`\`typescript
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length);
}
\`\`\`

## Generic Interfaces

\`\`\`typescript
interface Container<T> {
  value: T;
  getValue: () => T;
  setValue: (value: T) => void;
}
\`\`\`

## Conclusion

Mastering generics will make you a much more effective TypeScript developer!`,
    author: authors[1],
    category: categories[2], // TypeScript
    tags: ['typescript', 'generics', 'advanced'],
    publishedDate: '2024-01-28',
    readTime: 9,
    views: 1150,
    likes: 78,
    featured: false,
    coverImage: 'https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800',
  },
  {
    id: 'post-7',
    title: 'Modern JavaScript Features You Should Know',
    slug: 'modern-javascript-features',
    excerpt: 'Explore the latest JavaScript features that will improve your code.',
    content: `# Modern JavaScript Features You Should Know

JavaScript continues to evolve. Here are modern features every developer should know.

## Optional Chaining

Safely access nested properties:

\`\`\`javascript
const name = user?.profile?.name;
\`\`\`

## Nullish Coalescing

Use ?? instead of ||:

\`\`\`javascript
const value = input ?? 'default';
\`\`\`

## Array Methods

Powerful array manipulation:

\`\`\`javascript
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
\`\`\`

## Async/Await

Clean asynchronous code:

\`\`\`javascript
async function fetchData() {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}
\`\`\`

## Conclusion

These features make JavaScript code cleaner and more maintainable. Use them in your projects!`,
    author: authors[2],
    category: categories[0], // Web Development
    tags: ['javascript', 'es6', 'modern', 'features'],
    publishedDate: '2024-02-01',
    readTime: 8,
    views: 1680,
    likes: 112,
    featured: true,
    coverImage: 'https://images.unsplash.com/photo-1579468118864-1b9ea3c0db4a?w=800',
  },
  {
    id: 'post-8',
    title: 'Testing React Components with Testing Library',
    slug: 'testing-react-components',
    excerpt: 'Learn how to write effective tests for your React components.',
    content: `# Testing React Components with Testing Library

Testing is crucial for maintainable applications. Learn how to test React components effectively.

## Setup

Install the necessary packages:

\`\`\`bash
npm install --save-dev @testing-library/react @testing-library/jest-dom
\`\`\`

## Basic Test

Test a simple component:

\`\`\`tsx
import { render, screen } from '@testing-library/react';
import Button from './Button';

test('renders button with text', () => {
  render(<Button>Click me</Button>);
  expect(screen.getByText('Click me')).toBeInTheDocument();
});
\`\`\`

## Testing User Interactions

\`\`\`tsx
import { render, screen, fireEvent } from '@testing-library/react';

test('button click calls handler', () => {
  const handleClick = jest.fn();
  render(<Button onClick={handleClick}>Click</Button>);
  
  fireEvent.click(screen.getByText('Click'));
  expect(handleClick).toHaveBeenCalledTimes(1);
});
\`\`\`

## Conclusion

Good tests give you confidence to refactor and add features. Make testing a habit!`,
    author: authors[0],
    category: categories[4], // Tutorials
    tags: ['testing', 'react', 'tutorial', 'quality'],
    publishedDate: '2024-02-03',
    readTime: 11,
    views: 950,
    likes: 71,
    featured: false,
    coverImage: 'https://images.unsplash.com/photo-1516116216624-53e697fedbea?w=800',
  },
];

// Helper functions for working with posts
export function getPostBySlug(slug: string): Post | undefined {
  return posts.find(post => post.slug === slug);
}

export function getPostById(id: string): Post | undefined {
  return posts.find(post => post.id === id);
}

export function getPostsByCategory(categorySlug: string): Post[] {
  return posts.filter(post => post.category.slug === categorySlug);
}

export function getPostsByAuthor(authorId: string): Post[] {
  return posts.filter(post => post.author.id === authorId);
}

export function getFeaturedPosts(): Post[] {
  return posts.filter(post => post.featured);
}

export function searchPosts(query: string): Post[] {
  const lowerQuery = query.toLowerCase();
  return posts.filter(
    post =>
      post.title.toLowerCase().includes(lowerQuery) ||
      post.excerpt.toLowerCase().includes(lowerQuery) ||
      post.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
  );
}

export function getRecentPosts(limit: number = 5): Post[] {
  return [...posts]
    .sort((a, b) => 
      new Date(b.publishedDate).getTime() - new Date(a.publishedDate).getTime()
    )
    .slice(0, limit);
}

export function getPopularPosts(limit: number = 5): Post[] {
  return [...posts]
    .sort((a, b) => b.views - a.views)
    .slice(0, limit);
}

βœ… Data Structure Benefits

Notice how our mock data provides:

  • Rich content - Realistic blog posts with full content
  • Relationships - Posts linked to authors and categories
  • Variety - Different featured status, dates, and popularity
  • Helper functions - Easy ways to query and filter data
  • Type safety - All data matches our TypeScript interfaces

Step 5: Create Authentication Utility

Create src/utils/auth.ts for authentication helpers:

// src/utils/auth.ts
import { User, AuthState } from '../types';

const AUTH_KEY = 'blog_auth';
const USER_KEY = 'blog_user';

// Mock user for authentication
const mockUser: User = {
  id: 'user-1',
  email: 'demo@example.com',
  name: 'Demo User',
  avatar: 'https://i.pravatar.cc/150?img=68',
  role: 'author',
};

// Get current authentication state
export function getAuthState(): AuthState {
  const isAuthenticated = localStorage.getItem(AUTH_KEY) === 'true';
  const userJson = localStorage.getItem(USER_KEY);
  const user = userJson ? JSON.parse(userJson) : null;

  return {
    isAuthenticated,
    user,
  };
}

// Check if user is authenticated
export function isAuthenticated(): boolean {
  return localStorage.getItem(AUTH_KEY) === 'true';
}

// Get current user
export function getCurrentUser(): User | null {
  const userJson = localStorage.getItem(USER_KEY);
  return userJson ? JSON.parse(userJson) : null;
}

// Login function (simulated)
export function login(email: string, password: string): boolean {
  // In a real app, this would call an API
  // For demo purposes, accept any email/password
  if (email && password) {
    localStorage.setItem(AUTH_KEY, 'true');
    localStorage.setItem(USER_KEY, JSON.stringify(mockUser));
    return true;
  }
  return false;
}

// Register function (simulated)
export function register(name: string, email: string, password: string): boolean {
  // In a real app, this would call an API
  if (name && email && password) {
    const newUser: User = {
      ...mockUser,
      id: `user-${Date.now()}`,
      name,
      email,
    };
    localStorage.setItem(AUTH_KEY, 'true');
    localStorage.setItem(USER_KEY, JSON.stringify(newUser));
    return true;
  }
  return false;
}

// Logout function
export function logout(): void {
  localStorage.removeItem(AUTH_KEY);
  localStorage.removeItem(USER_KEY);
}

// Update user profile
export function updateUser(updates: Partial<User>): void {
  const currentUser = getCurrentUser();
  if (currentUser) {
    const updatedUser = { ...currentUser, ...updates };
    localStorage.setItem(USER_KEY, JSON.stringify(updatedUser));
  }
}

⚠️ Authentication Notice

This authentication system is for demonstration only. It uses localStorage and accepts any credentials. In a production app, you would:

  • Use a real authentication API
  • Validate credentials securely on the server
  • Use JWT tokens or session cookies
  • Implement proper security measures
  • Handle token refresh and expiration

For this project, we're focusing on routing patterns, not authentication implementation.

Testing Your Data

Before moving forward, let's verify your data is set up correctly. Create a temporary test file:

// src/test-data.ts (temporary file for testing)
import { posts, getPostBySlug, getFeaturedPosts } from './data/posts';
import { authors, getAuthorById } from './data/authors';
import { categories, getCategoryBySlug } from './data/categories';

console.log('Total posts:', posts.length);
console.log('Total authors:', authors.length);
console.log('Total categories:', categories.length);
console.log('\nFeatured posts:', getFeaturedPosts().length);
console.log('\nFirst post:', getPostBySlug('getting-started-react-router-v6'));
console.log('\nReact category:', getCategoryBySlug('react'));
console.log('\nFirst author:', getAuthorById('author-1'));

Import this in your App.tsx temporarily to verify the data loads correctly in the browser console.

πŸŽ‰ Data Foundation Complete!

You now have:

  • βœ… TypeScript interfaces for all data types
  • βœ… Mock categories (6 categories)
  • βœ… Mock authors (4 authors)
  • βœ… Mock posts (8 detailed blog posts)
  • βœ… Helper functions for querying data
  • βœ… Authentication utilities

With this foundation in place, we're ready to start building our routing architecture!

πŸ—ΊοΈ Route Architecture Design

Before writing any routing code, let's design our complete route structure. A well-planned architecture makes implementation much smoother and creates a maintainable application.

Complete Route Structure

Here's our full route hierarchy visualized:

graph TB A[App Root /] --> B[Public Layout] A --> C[Auth Layout] A --> D[Dashboard Layout] A --> E[404 Page] B --> B1[Home /] B --> B2[Blog List /blog] B --> B3[Post Detail /blog/:postId] B --> B4[Category /category/:categoryName] B --> B5[Author /author/:authorId] B --> B6[About /about] C --> C1[Login /login] C --> C2[Register /register] C --> C3[Forgot Password /forgot-password] D --> D1[Dashboard Home /dashboard] D --> D2[My Posts /dashboard/posts] D --> D3[Create Post /dashboard/posts/new] D --> D4[Edit Post /dashboard/posts/:postId/edit] D --> D5[Settings Layout /dashboard/settings] D5 --> D5A[Settings Index] D5 --> D5B[Profile /dashboard/settings/profile] D5 --> D5C[Account /dashboard/settings/account] D5 --> D5D[Preferences /dashboard/settings/preferences] style A fill:#667eea,color:#fff style B fill:#48bb78,color:#fff style C fill:#ed8936,color:#fff style D fill:#9f7aea,color:#fff style E fill:#ef4444,color:#fff

Route Configuration Table

Path Component Layout Protected Description
🌐 Public Routes
/ HomePage PublicLayout ❌ Landing page with featured posts
/blog BlogListPage PublicLayout ❌ List all posts with search
/blog/:postId PostDetailPage PublicLayout ❌ Individual post view
/category/:categoryName CategoryPage PublicLayout ❌ Posts filtered by category
/author/:authorId AuthorProfilePage PublicLayout ❌ Author bio and their posts
/about AboutPage PublicLayout ❌ About the blog
πŸ” Authentication Routes
/login LoginPage AuthLayout ❌ User login form
/register RegisterPage AuthLayout ❌ User registration form
/forgot-password ForgotPasswordPage AuthLayout ❌ Password reset
πŸ“Š Protected Dashboard Routes
/dashboard DashboardHomePage DashboardLayout βœ… Dashboard overview
/dashboard/posts MyPostsPage DashboardLayout βœ… User's posts list
/dashboard/posts/new CreatePostPage DashboardLayout βœ… Create new post
/dashboard/posts/:postId/edit EditPostPage DashboardLayout βœ… Edit existing post
/dashboard/settings SettingsLayout (index) DashboardLayout βœ… Settings overview
/dashboard/settings/profile ProfileSettingsPage DashboardLayout βœ… Edit profile
/dashboard/settings/account AccountSettingsPage DashboardLayout βœ… Account settings
/dashboard/settings/preferences PreferencesPage DashboardLayout βœ… User preferences
❌ Error Routes
* NotFoundPage PublicLayout ❌ 404 Not Found

Layout Responsibilities

1. PublicLayout

Purpose: General public pages accessible to everyone

Features:

  • Main navigation bar with links to Home, Blog, About
  • Category menu
  • Search bar in header
  • Login/Register buttons (if not authenticated)
  • User menu (if authenticated)
  • Breadcrumb navigation
  • Footer with links and information

2. AuthLayout

Purpose: Authentication pages (login, register)

Features:

  • Minimal header with logo
  • Centered form container
  • No navigation or footer
  • Clean, focused design
  • Links to switch between login/register

3. DashboardLayout

Purpose: Protected pages for authenticated users

Features:

  • Sidebar navigation with dashboard links
  • Top bar with user info and logout
  • Breadcrumb navigation
  • Main content area with Outlet
  • Mobile-responsive sidebar (collapsible)
  • Settings sub-navigation (for nested settings routes)

Routing Strategy

🎯 Key Routing Decisions

1. Layout Routes

We use layout routes to wrap groups of pages with consistent UI:

<Route element={<PublicLayout />}>
  <Route path="/" element={<HomePage />} />
  <Route path="/blog" element={<BlogListPage />} />
  {/* More public routes */}
</Route>

2. Protected Routes

We create a ProtectedRoute component that checks authentication:

<Route element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
  <Route path="/dashboard" element={<DashboardHome />} />
  {/* More protected routes */}
</Route>

3. Nested Routes

Settings uses nested routing with its own layout:

<Route path="/dashboard/settings" element={<SettingsLayout />}>
  <Route index element={<SettingsIndex />} />
  <Route path="profile" element={<ProfileSettings />} />
  <Route path="account" element={<AccountSettings />} />
</Route>

4. Dynamic Routes

We use URL parameters for dynamic content:

<Route path="/blog/:postId" element={<PostDetail />} />
<Route path="/author/:authorId" element={<AuthorProfile />} />
<Route path="/category/:categoryName" element={<Category />} />

βœ… Architecture Checklist

Before coding, verify you understand:

  • ☐ The three main layouts and their purposes
  • ☐ Which routes belong in each layout
  • ☐ Which routes require authentication
  • ☐ How nested routes work (settings example)
  • ☐ Which routes use dynamic parameters
  • ☐ How breadcrumbs will work across layouts
  • ☐ Where the 404 page fits in

πŸš€ Ready to Code!

You have a complete route architecture designed. You understand:

  • βœ… All routes and their paths
  • βœ… Three distinct layouts
  • βœ… Protected vs public routes
  • βœ… Nested routing structure
  • βœ… Dynamic route parameters

In the next section, we'll start implementing this architecture with React Router!

βš™οΈ Setting Up React Router

Now that we have our data and architecture planned, let's set up React Router and create the foundation of our routing system. We'll start with the main App component and configure all our routes.

Step 1: Update main.tsx

First, make sure your entry point is set up correctly. Update src/main.tsx:

// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './index.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

πŸ’‘ BrowserRouter Placement

We wrap our entire app with BrowserRouter in main.tsx so that routing is available everywhere. This is the standard pattern for React Router applications.

Step 2: Create ProtectedRoute Component

Before building our route structure, we need a component to protect authenticated routes. Create src/components/ProtectedRoute.tsx:

// src/components/ProtectedRoute.tsx
import { Navigate, useLocation } from 'react-router-dom';
import { isAuthenticated } from '../utils/auth';

interface ProtectedRouteProps {
  children: React.ReactNode;
}

export function ProtectedRoute({ children }: ProtectedRouteProps) {
  const location = useLocation();
  const isAuth = isAuthenticated();

  if (!isAuth) {
    // Redirect to login but save the location they were trying to go to
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return <>{children}</>;
}

βœ… How ProtectedRoute Works

  1. Check if the user is authenticated using our isAuthenticated() function
  2. If not authenticated, redirect to /login
  3. Save the current location in state.from so we can redirect back after login
  4. If authenticated, render the children (the protected content)

Step 3: Configure Routes in App.tsx

Now let's build our complete route configuration. Update src/App.tsx:

// src/App.tsx
import { Routes, Route } from 'react-router-dom';
import { ProtectedRoute } from './components/ProtectedRoute';

// Layout components (we'll create these next)
import { PublicLayout } from './layouts/PublicLayout';
import { AuthLayout } from './layouts/AuthLayout';
import { DashboardLayout } from './layouts/DashboardLayout';

// Public pages (we'll create these later)
import { HomePage } from './pages/public/HomePage';
import { BlogListPage } from './pages/public/BlogListPage';
import { PostDetailPage } from './pages/public/PostDetailPage';
import { CategoryPage } from './pages/public/CategoryPage';
import { AuthorProfilePage } from './pages/public/AuthorProfilePage';
import { AboutPage } from './pages/public/AboutPage';

// Auth pages
import { LoginPage } from './pages/auth/LoginPage';
import { RegisterPage } from './pages/auth/RegisterPage';
import { ForgotPasswordPage } from './pages/auth/ForgotPasswordPage';

// Dashboard pages
import { DashboardHomePage } from './pages/dashboard/DashboardHomePage';
import { MyPostsPage } from './pages/dashboard/MyPostsPage';
import { CreatePostPage } from './pages/dashboard/CreatePostPage';
import { EditPostPage } from './pages/dashboard/EditPostPage';

// Settings pages and layout
import { SettingsLayout } from './pages/dashboard/settings/SettingsLayout';
import { SettingsIndexPage } from './pages/dashboard/settings/SettingsIndexPage';
import { ProfileSettingsPage } from './pages/dashboard/settings/ProfileSettingsPage';
import { AccountSettingsPage } from './pages/dashboard/settings/AccountSettingsPage';
import { PreferencesPage } from './pages/dashboard/settings/PreferencesPage';

// Error page
import { NotFoundPage } from './pages/NotFoundPage';

function App() {
  return (
    <Routes>
      {/* Public routes with PublicLayout */}
      <Route element={<PublicLayout />}>
        <Route index element={<HomePage />} />
        <Route path="/blog" element={<BlogListPage />} />
        <Route path="/blog/:postId" element={<PostDetailPage />} />
        <Route path="/category/:categoryName" element={<CategoryPage />} />
        <Route path="/author/:authorId" element={<AuthorProfilePage />} />
        <Route path="/about" element={<AboutPage />} />
      </Route>

      {/* Auth routes with AuthLayout */}
      <Route element={<AuthLayout />}>
        <Route path="/login" element={<LoginPage />} />
        <Route path="/register" element={<RegisterPage />} />
        <Route path="/forgot-password" element={<ForgotPasswordPage />} />
      </Route>

      {/* Protected dashboard routes with DashboardLayout */}
      <Route
        element={
          <ProtectedRoute>
            <DashboardLayout />
          </ProtectedRoute>
        }
      >
        <Route path="/dashboard" element={<DashboardHomePage />} />
        <Route path="/dashboard/posts" element={<MyPostsPage />} />
        <Route path="/dashboard/posts/new" element={<CreatePostPage />} />
        <Route path="/dashboard/posts/:postId/edit" element={<EditPostPage />} />
        
        {/* Nested settings routes */}
        <Route path="/dashboard/settings" element={<SettingsLayout />}>
          <Route index element={<SettingsIndexPage />} />
          <Route path="profile" element={<ProfileSettingsPage />} />
          <Route path="account" element={<AccountSettingsPage />} />
          <Route path="preferences" element={<PreferencesPage />} />
        </Route>
      </Route>

      {/* 404 Not Found - catch all */}
      <Route path="*" element={<NotFoundPage />} />
    </Routes>
  );
}

export default App;

🎯 Route Structure Highlights

  • Layout grouping - Routes are grouped by their layout component
  • Index route - The homepage uses index instead of path="/"
  • Protected wrapper - Dashboard routes wrapped in ProtectedRoute
  • Nested settings - Settings has its own layout with nested routes
  • Catch-all - path="*" catches any unmatched routes (404)

Step 4: Add Basic Global Styles

Update src/index.css with base styles for our application:

/* src/index.css */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  /* Colors */
  --primary: #667eea;
  --primary-dark: #5568d3;
  --secondary: #764ba2;
  --success: #48bb78;
  --danger: #f56565;
  --warning: #ed8936;
  --info: #4299e1;
  
  /* Neutrals */
  --gray-50: #f9fafb;
  --gray-100: #f3f4f6;
  --gray-200: #e5e7eb;
  --gray-300: #d1d5db;
  --gray-400: #9ca3af;
  --gray-500: #6b7280;
  --gray-600: #4b5563;
  --gray-700: #374151;
  --gray-800: #1f2937;
  --gray-900: #111827;
  
  /* Layout */
  --max-width: 1200px;
  --sidebar-width: 250px;
  --header-height: 64px;
  
  /* Typography */
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace;
  
  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  /* Border radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  
  /* Shadows */
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

body {
  font-family: var(--font-sans);
  line-height: 1.6;
  color: var(--gray-900);
  background-color: var(--gray-50);
}

/* Typography */
h1, h2, h3, h4, h5, h6 {
  line-height: 1.2;
  font-weight: 700;
  margin-bottom: 1rem;
}

h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.5rem; }
h4 { font-size: 1.25rem; }
h5 { font-size: 1.125rem; }
h6 { font-size: 1rem; }

p {
  margin-bottom: 1rem;
}

a {
  color: var(--primary);
  text-decoration: none;
  transition: color 0.2s;
}

a:hover {
  color: var(--primary-dark);
}

/* Buttons */
button {
  font-family: inherit;
  font-size: 1rem;
  cursor: pointer;
  border: none;
  outline: none;
  transition: all 0.2s;
}

.btn {
  padding: 0.5rem 1rem;
  border-radius: var(--radius-md);
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.btn-primary {
  background: var(--primary);
  color: white;
}

.btn-primary:hover {
  background: var(--primary-dark);
}

.btn-secondary {
  background: var(--gray-200);
  color: var(--gray-800);
}

.btn-secondary:hover {
  background: var(--gray-300);
}

/* Forms */
input, textarea, select {
  font-family: inherit;
  font-size: 1rem;
  padding: 0.5rem;
  border: 1px solid var(--gray-300);
  border-radius: var(--radius-md);
  width: 100%;
  transition: border-color 0.2s;
}

input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--primary);
}

label {
  display: block;
  margin-bottom: 0.25rem;
  font-weight: 500;
  color: var(--gray-700);
}

/* Cards */
.card {
  background: white;
  border-radius: var(--radius-lg);
  padding: var(--spacing-lg);
  box-shadow: var(--shadow-sm);
  margin-bottom: var(--spacing-lg);
}

/* Container */
.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--spacing-lg);
}

/* Utility classes */
.text-center { text-align: center; }
.text-right { text-align: right; }
.mt-1 { margin-top: var(--spacing-sm); }
.mt-2 { margin-top: var(--spacing-md); }
.mt-3 { margin-top: var(--spacing-lg); }
.mb-1 { margin-bottom: var(--spacing-sm); }
.mb-2 { margin-bottom: var(--spacing-md); }
.mb-3 { margin-bottom: var(--spacing-lg); }

/* Responsive */
@media (max-width: 768px) {
  h1 { font-size: 2rem; }
  h2 { font-size: 1.75rem; }
  h3 { font-size: 1.25rem; }
  
  .container {
    padding: 0 var(--spacing-md);
  }
}

βœ… CSS Variables Benefits

Using CSS custom properties gives us:

  • Consistency - Colors and spacing are uniform across the app
  • Easy theming - Change variables to update the entire app
  • Maintainability - Update values in one place
  • Readability - var(--primary) is clearer than #667eea

Step 5: Create Placeholder Components

Before we can test our routes, we need placeholder components for each page. Let's create simple placeholders that we'll build out later.

Create a helper component for placeholders in src/components/PagePlaceholder.tsx:

// src/components/PagePlaceholder.tsx
interface PagePlaceholderProps {
  title: string;
  description?: string;
}

export function PagePlaceholder({ title, description }: PagePlaceholderProps) {
  return (
    <div className="container" style={{ padding: '2rem', textAlign: 'center' }}>
      <h1>{title}</h1>
      {description && <p style={{ color: 'var(--gray-600)' }}>{description}</p>}
      <p style={{ 
        marginTop: '2rem', 
        padding: '1rem', 
        background: 'var(--gray-100)', 
        borderRadius: 'var(--radius-md)' 
      }}>
        This page is under construction. We'll build it in the next sections!
      </p>
    </div>
  );
}

⚠️ Don't Create All Pages Yet!

We'll create placeholder files now just to test routing, but we'll build the actual page content in later sections. This lets us verify our routing structure works before investing time in building pages.

Testing Your Route Setup

After creating the layouts and placeholder pages (which we'll do in the next section), you can test your routes by:

  1. Start the dev server: npm run dev
  2. Visit different URLs manually to test routing
  3. Try public routes: /, /blog, /about
  4. Try auth routes: /login, /register
  5. Try protected routes (should redirect): /dashboard
  6. Try invalid routes (should show 404): /random-page

πŸŽ‰ Route Configuration Complete!

You've set up:

  • βœ… React Router in main.tsx
  • βœ… ProtectedRoute component for auth
  • βœ… Complete route structure in App.tsx
  • βœ… Global CSS variables and base styles
  • βœ… PagePlaceholder utility component

Next, we'll build the three layout components that wrap our pages!

πŸ—οΈ Creating Layout Components

Layouts are the backbone of our application's structure. Each layout provides consistent navigation, headers, and structure for its group of pages. Let's build all three layouts: PublicLayout, AuthLayout, and DashboardLayout.

Layout 1: PublicLayout

The PublicLayout wraps all public pages and provides the main navigation. Create src/layouts/PublicLayout.tsx:

// src/layouts/PublicLayout.tsx
import { Outlet, Link, NavLink } from 'react-router-dom';
import { isAuthenticated, getCurrentUser, logout } from '../utils/auth';
import { categories } from '../data/categories';
import './PublicLayout.css';

export function PublicLayout() {
  const isAuth = isAuthenticated();
  const user = getCurrentUser();

  const handleLogout = () => {
    logout();
    window.location.href = '/'; // Reload to update auth state
  };

  return (
    <div className="public-layout">
      {/* Header */}
      <header className="public-header">
        <div className="container">
          <div className="header-content">
            {/* Logo */}
            <Link to="/" className="logo">
              <span className="logo-icon">πŸ“</span>
              <span className="logo-text">DevBlog</span>
            </Link>

            {/* Navigation */}
            <nav className="main-nav">
              <NavLink 
                to="/" 
                className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
              >
                Home
              </NavLink>
              <NavLink 
                to="/blog" 
                className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
              >
                Blog
              </NavLink>
              
              {/* Categories dropdown */}
              <div className="dropdown">
                <button className="nav-link">
                  Categories β–Ύ
                </button>
                <div className="dropdown-content">
                  {categories.map(cat => (
                    <Link 
                      key={cat.id} 
                      to={`/category/${cat.slug}`}
                      className="dropdown-item"
                    >
                      {cat.name}
                    </Link>
                  ))}
                </div>
              </div>

              <NavLink 
                to="/about" 
                className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link'}
              >
                About
              </NavLink>
            </nav>

            {/* Auth section */}
            <div className="auth-section">
              {isAuth && user ? (
                <div className="user-menu">
                  <Link to="/dashboard" className="btn btn-primary">
                    Dashboard
                  </Link>
                  <span className="user-name">{user.name}</span>
                  <button onClick={handleLogout} className="btn btn-secondary">
                    Logout
                  </button>
                </div>
              ) : (
                <div className="auth-buttons">
                  <Link to="/login" className="btn btn-secondary">
                    Login
                  </Link>
                  <Link to="/register" className="btn btn-primary">
                    Sign Up
                  </Link>
                </div>
              )}
            </div>
          </div>
        </div>
      </header>

      {/* Main content area */}
      <main className="public-main">
        <Outlet />
      </main>

      {/* Footer */}
      <footer className="public-footer">
        <div className="container">
          <div className="footer-content">
            <div className="footer-section">
              <h4>DevBlog</h4>
              <p>Sharing knowledge about web development, React, and TypeScript.</p>
            </div>
            <div className="footer-section">
              <h4>Quick Links</h4>
              <Link to="/blog">All Posts</Link>
              <Link to="/about">About Us</Link>
            </div>
            <div className="footer-section">
              <h4>Categories</h4>
              {categories.slice(0, 4).map(cat => (
                <Link key={cat.id} to={`/category/${cat.slug}`}>
                  {cat.name}
                </Link>
              ))}
            </div>
          </div>
          <div className="footer-bottom">
            <p>© 2024 DevBlog. Built with React and TypeScript.</p>
          </div>
        </div>
      </footer>
    </div>
  );
}

PublicLayout Styles

Create src/layouts/PublicLayout.css:

/* src/layouts/PublicLayout.css */
.public-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Header */
.public-header {
  background: white;
  border-bottom: 1px solid var(--gray-200);
  padding: 1rem 0;
  position: sticky;
  top: 0;
  z-index: 100;
  box-shadow: var(--shadow-sm);
}

.header-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

/* Logo */
.logo {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--primary);
  text-decoration: none;
}

.logo-icon {
  font-size: 2rem;
}

/* Navigation */
.main-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex: 1;
}

.nav-link {
  color: var(--gray-700);
  font-weight: 500;
  padding: 0.5rem 1rem;
  border-radius: var(--radius-md);
  transition: all 0.2s;
  background: transparent;
  border: none;
  cursor: pointer;
}

.nav-link:hover {
  color: var(--primary);
  background: var(--gray-100);
}

.nav-link.active {
  color: var(--primary);
  background: var(--gray-100);
}

/* Dropdown */
.dropdown {
  position: relative;
}

.dropdown-content {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background: white;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 0.5rem;
  min-width: 200px;
  margin-top: 0.5rem;
}

.dropdown:hover .dropdown-content {
  display: block;
}

.dropdown-item {
  display: block;
  padding: 0.5rem 1rem;
  color: var(--gray-700);
  border-radius: var(--radius-sm);
  transition: all 0.2s;
}

.dropdown-item:hover {
  background: var(--gray-100);
  color: var(--primary);
}

/* Auth section */
.auth-section {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.user-menu {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.user-name {
  color: var(--gray-700);
  font-weight: 500;
}

.auth-buttons {
  display: flex;
  gap: 0.5rem;
}

/* Main content */
.public-main {
  flex: 1;
  padding: 2rem 0;
}

/* Footer */
.public-footer {
  background: var(--gray-900);
  color: white;
  padding: 3rem 0 1rem;
  margin-top: 4rem;
}

.footer-content {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 2rem;
  margin-bottom: 2rem;
}

.footer-section h4 {
  margin-bottom: 1rem;
  color: white;
}

.footer-section a {
  display: block;
  color: var(--gray-400);
  margin-bottom: 0.5rem;
}

.footer-section a:hover {
  color: white;
}

.footer-section p {
  color: var(--gray-400);
  margin-bottom: 0;
}

.footer-bottom {
  text-align: center;
  padding-top: 2rem;
  border-top: 1px solid var(--gray-800);
  color: var(--gray-500);
}

/* Responsive */
@media (max-width: 768px) {
  .header-content {
    flex-direction: column;
    gap: 1rem;
  }

  .main-nav {
    flex-wrap: wrap;
    justify-content: center;
  }

  .auth-section {
    width: 100%;
    justify-content: center;
  }

  .footer-content {
    grid-template-columns: 1fr;
  }
}

πŸ’‘ PublicLayout Features

  • Sticky header - Stays at top when scrolling
  • Dynamic auth UI - Shows different buttons based on login state
  • Category dropdown - Hover to see all categories
  • Active link highlighting - Current page is highlighted
  • Responsive design - Works on mobile and desktop
  • Footer with links - Consistent across all public pages

Layout 2: AuthLayout

The AuthLayout is minimal and focused, perfect for login and registration forms. Create src/layouts/AuthLayout.tsx:

// src/layouts/AuthLayout.tsx
import { Outlet, Link } from 'react-router-dom';
import './AuthLayout.css';

export function AuthLayout() {
  return (
    <div className="auth-layout">
      {/* Simple header with logo */}
      <header className="auth-header">
        <Link to="/" className="auth-logo">
          <span className="logo-icon">πŸ“</span>
          <span className="logo-text">DevBlog</span>
        </Link>
      </header>

      {/* Centered content area */}
      <main className="auth-main">
        <div className="auth-container">
          <Outlet />
        </div>
      </main>

      {/* Simple footer */}
      <footer className="auth-footer">
        <p>© 2024 DevBlog. All rights reserved.</p>
      </footer>
    </div>
  );
}

AuthLayout Styles

Create src/layouts/AuthLayout.css:

/* src/layouts/AuthLayout.css */
.auth-layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

/* Header */
.auth-header {
  padding: 2rem;
  text-align: center;
}

.auth-logo {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 2rem;
  font-weight: 700;
  color: white;
  text-decoration: none;
}

/* Main content */
.auth-main {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.auth-container {
  width: 100%;
  max-width: 450px;
  background: white;
  border-radius: var(--radius-lg);
  padding: 2rem;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
}

/* Footer */
.auth-footer {
  text-align: center;
  padding: 2rem;
  color: white;
  opacity: 0.9;
}

Layout 3: DashboardLayout

The DashboardLayout provides a sidebar navigation for authenticated users. Create src/layouts/DashboardLayout.tsx:

// src/layouts/DashboardLayout.tsx
import { Outlet, Link, NavLink, useNavigate } from 'react-router-dom';
import { getCurrentUser, logout } from '../utils/auth';
import { useState } from 'react';
import './DashboardLayout.css';

export function DashboardLayout() {
  const user = getCurrentUser();
  const navigate = useNavigate();
  const [sidebarOpen, setSidebarOpen] = useState(true);

  const handleLogout = () => {
    logout();
    navigate('/');
  };

  return (
    <div className={`dashboard-layout ${sidebarOpen ? 'sidebar-open' : 'sidebar-closed'}`}>
      {/* Sidebar */}
      <aside className="dashboard-sidebar">
        <div className="sidebar-header">
          <Link to="/" className="sidebar-logo">
            <span className="logo-icon">πŸ“</span>
            <span className="logo-text">DevBlog</span>
          </Link>
          <button 
            className="sidebar-toggle"
            onClick={() => setSidebarOpen(!sidebarOpen)}
            aria-label="Toggle sidebar"
          >
            {sidebarOpen ? 'β—€' : 'β–Ά'}
          </button>
        </div>

        <nav className="sidebar-nav">
          <NavLink 
            to="/dashboard" 
            end
            className={({ isActive }) => `sidebar-link ${isActive ? 'active' : ''}`}
          >
            <span className="link-icon">πŸ“Š</span>
            <span className="link-text">Dashboard</span>
          </NavLink>

          <NavLink 
            to="/dashboard/posts"
            className={({ isActive }) => `sidebar-link ${isActive ? 'active' : ''}`}
          >
            <span className="link-icon">πŸ“</span>
            <span className="link-text">My Posts</span>
          </NavLink>

          <NavLink 
            to="/dashboard/posts/new"
            className={({ isActive }) => `sidebar-link ${isActive ? 'active' : ''}`}
          >
            <span className="link-icon">βž•</span>
            <span className="link-text">New Post</span>
          </NavLink>

          <NavLink 
            to="/dashboard/settings"
            className={({ isActive }) => `sidebar-link ${isActive ? 'active' : ''}`}
          >
            <span className="link-icon">βš™οΈ</span>
            <span className="link-text">Settings</span>
          </NavLink>

          <div className="sidebar-divider"></div>

          <Link to="/blog" className="sidebar-link">
            <span className="link-icon">🌐</span>
            <span className="link-text">View Blog</span>
          </Link>
        </nav>
      </aside>

      {/* Main content area */}
      <div className="dashboard-main">
        {/* Top bar */}
        <header className="dashboard-header">
          <div className="header-left">
            <h2>Welcome back, {user?.name}!</h2>
          </div>
          <div className="header-right">
            <div className="user-info">
              <img 
                src={user?.avatar} 
                alt={user?.name}
                className="user-avatar"
              />
              <span className="user-name">{user?.name}</span>
            </div>
            <button onClick={handleLogout} className="btn btn-secondary">
              Logout
            </button>
          </div>
        </header>

        {/* Page content */}
        <main className="dashboard-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

DashboardLayout Styles

Create src/layouts/DashboardLayout.css:

/* src/layouts/DashboardLayout.css */
.dashboard-layout {
  display: flex;
  min-height: 100vh;
  background: var(--gray-50);
}

/* Sidebar */
.dashboard-sidebar {
  width: var(--sidebar-width);
  background: var(--gray-900);
  color: white;
  display: flex;
  flex-direction: column;
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  transition: width 0.3s;
  z-index: 100;
}

.sidebar-closed .dashboard-sidebar {
  width: 60px;
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem;
  border-bottom: 1px solid var(--gray-800);
}

.sidebar-logo {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: white;
  font-weight: 700;
  font-size: 1.25rem;
  text-decoration: none;
}

.sidebar-closed .logo-text,
.sidebar-closed .link-text {
  display: none;
}

.sidebar-toggle {
  background: transparent;
  color: white;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1rem;
  transition: transform 0.3s;
}

.sidebar-toggle:hover {
  transform: scale(1.1);
}

/* Sidebar navigation */
.sidebar-nav {
  flex: 1;
  padding: 1rem 0;
  overflow-y: auto;
}

.sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  color: var(--gray-400);
  text-decoration: none;
  transition: all 0.2s;
  border-left: 3px solid transparent;
}

.sidebar-link:hover {
  background: var(--gray-800);
  color: white;
}

.sidebar-link.active {
  background: var(--gray-800);
  color: white;
  border-left-color: var(--primary);
}

.link-icon {
  font-size: 1.25rem;
  flex-shrink: 0;
}

.sidebar-divider {
  height: 1px;
  background: var(--gray-800);
  margin: 1rem 0;
}

/* Main area */
.dashboard-main {
  flex: 1;
  margin-left: var(--sidebar-width);
  transition: margin-left 0.3s;
}

.sidebar-closed .dashboard-main {
  margin-left: 60px;
}

/* Header */
.dashboard-header {
  background: white;
  border-bottom: 1px solid var(--gray-200);
  padding: 1rem 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: sticky;
  top: 0;
  z-index: 50;
}

.header-left h2 {
  margin: 0;
  font-size: 1.5rem;
  color: var(--gray-900);
}

.header-right {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.user-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.user-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
}

.user-name {
  font-weight: 500;
  color: var(--gray-700);
}

/* Content */
.dashboard-content {
  padding: 2rem;
}

/* Responsive */
@media (max-width: 768px) {
  .dashboard-sidebar {
    width: 60px;
  }

  .sidebar-closed .dashboard-sidebar {
    width: 0;
  }

  .dashboard-main {
    margin-left: 60px;
  }

  .sidebar-closed .dashboard-main {
    margin-left: 0;
  }

  .sidebar-closed .logo-text,
  .sidebar-closed .link-text {
    display: none;
  }

  .logo-text,
  .link-text {
    display: none;
  }

  .dashboard-header {
    padding: 1rem;
  }

  .header-left h2 {
    font-size: 1.25rem;
  }

  .user-name {
    display: none;
  }

  .dashboard-content {
    padding: 1rem;
  }
}

βœ… DashboardLayout Features

  • Collapsible sidebar - Toggle button to show/hide labels
  • Fixed sidebar - Stays in place while scrolling content
  • Active link highlighting - Current page clearly marked
  • Sticky top bar - Header stays visible when scrolling
  • User info display - Shows avatar and name
  • Mobile responsive - Collapses on small screens

Testing All Three Layouts

Now that all three layouts are complete, let's create simple placeholder pages to test them:

πŸ“ Quick Test Checklist

Create these placeholder files using the PagePlaceholder component:

  1. Public pages: HomePage, BlogListPage, PostDetailPage, CategoryPage, AuthorProfilePage, AboutPage
  2. Auth pages: LoginPage, RegisterPage, ForgotPasswordPage
  3. Dashboard pages: DashboardHomePage, MyPostsPage, CreatePostPage, EditPostPage
  4. Settings pages: SettingsLayout, SettingsIndexPage, ProfileSettingsPage, AccountSettingsPage, PreferencesPage
  5. Error page: NotFoundPage

Example Placeholder Page

Here's a quick example of how to create a placeholder page (create similar files for all pages):

// src/pages/public/HomePage.tsx
import { PagePlaceholder } from '../../components/PagePlaceholder';

export function HomePage() {
  return (
    <PagePlaceholder 
      title="Home Page"
      description="Featured posts and latest content will appear here"
    />
  );
}

// src/pages/auth/LoginPage.tsx
import { PagePlaceholder } from '../../components/PagePlaceholder';

export function LoginPage() {
  return (
    <PagePlaceholder 
      title="Login Page"
      description="Login form will appear here"
    />
  );
}

// src/pages/dashboard/DashboardHomePage.tsx
import { PagePlaceholder } from '../../components/PagePlaceholder';

export function DashboardHomePage() {
  return (
    <PagePlaceholder 
      title="Dashboard Home"
      description="User statistics and overview will appear here"
    />
  );
}

// src/pages/NotFoundPage.tsx
export function NotFoundPage() {
  return (
    <div style={{ textAlign: 'center', padding: '4rem 2rem' }}>
      <h1 style={{ fontSize: '4rem', marginBottom: '1rem' }}>404</h1>
      <h2>Page Not Found</h2>
      <p>The page you're looking for doesn't exist.</p>
      <a href="/" style={{ 
        display: 'inline-block',
        marginTop: '2rem',
        padding: '0.75rem 1.5rem',
        background: 'var(--primary)',
        color: 'white',
        borderRadius: 'var(--radius-md)',
        textDecoration: 'none'
      }}>
        Go Home
      </a>
    </div>
  );
}

⚠️ Settings Layout Note

The SettingsLayout is special because it's a nested layout within the DashboardLayout. Here's a simple version:

// src/pages/dashboard/settings/SettingsLayout.tsx
import { Outlet, NavLink } from 'react-router-dom';

export function SettingsLayout() {
  return (
    <div>
      <h1>Settings</h1>
      <nav style={{ 
        display: 'flex', 
        gap: '1rem', 
        marginBottom: '2rem',
        borderBottom: '1px solid var(--gray-200)',
        paddingBottom: '1rem'
      }}>
        <NavLink 
          to="/dashboard/settings/profile"
          style={({ isActive }) => ({
            padding: '0.5rem 1rem',
            borderRadius: 'var(--radius-md)',
            background: isActive ? 'var(--primary)' : 'transparent',
            color: isActive ? 'white' : 'var(--gray-700)'
          })}
        >
          Profile
        </NavLink>
        <NavLink 
          to="/dashboard/settings/account"
          style={({ isActive }) => ({
            padding: '0.5rem 1rem',
            borderRadius: 'var(--radius-md)',
            background: isActive ? 'var(--primary)' : 'transparent',
            color: isActive ? 'white' : 'var(--gray-700)'
          })}
        >
          Account
        </NavLink>
        <NavLink 
          to="/dashboard/settings/preferences"
          style={({ isActive }) => ({
            padding: '0.5rem 1rem',
            borderRadius: 'var(--radius-md)',
            background: isActive ? 'var(--primary)' : 'transparent',
            color: isActive ? 'white' : 'var(--gray-700)'
          })}
        >
          Preferences
        </NavLink>
      </nav>
      <Outlet />
    </div>
  );
}

Verification Steps

After creating all placeholder pages, test your layouts:

  1. Start dev server: npm run dev
  2. Test PublicLayout:
    • Visit / - Should see HomePage with header/footer
    • Click "Blog" in nav - Should navigate to BlogListPage
    • Hover "Categories" - Dropdown should appear
    • Check responsive - Resize browser window
  3. Test AuthLayout:
    • Visit /login - Should see centered card with gradient background
    • Visit /register - Same layout
    • Notice minimal header (just logo)
  4. Test DashboardLayout:
    • Visit /dashboard - Should redirect to login (not authenticated)
    • After "logging in" (we'll build this next), dashboard should show sidebar
    • Click sidebar toggle - Sidebar should collapse/expand
    • Click different sidebar links - Active state should change
  5. Test 404:
    • Visit /random-page - Should show NotFoundPage

πŸŽ‰ All Three Layouts Complete!

You now have:

  • βœ… PublicLayout with full navigation, categories, and footer
  • βœ… AuthLayout with minimal, centered design
  • βœ… DashboardLayout with collapsible sidebar
  • βœ… ProtectedRoute guarding dashboard pages
  • βœ… Complete routing structure in App.tsx
  • βœ… PagePlaceholder for quick testing

The foundation is solid! Next, we'll build out the actual page content, starting with the public pages.

🌐 Building Public Pages

Now that our layouts are working, let's build the public pages with real content. We'll start with the HomePage, then create the blog listing, post detail pages, and more. These pages will use our mock data to display realistic content.

Page 1: HomePage

The HomePage is the landing page for our blog. It showcases featured posts, recent posts, and categories. Update src/pages/public/HomePage.tsx:

// src/pages/public/HomePage.tsx
import { Link } from 'react-router-dom';
import { getFeaturedPosts, getRecentPosts } from '../../data/posts';
import { categories } from '../../data/categories';
import './HomePage.css';

export function HomePage() {
  const featuredPosts = getFeaturedPosts();
  const recentPosts = getRecentPosts(6);

  return (
    <div className="home-page">
      {/* Hero section */}
      <section className="hero">
        <div className="container">
          <h1 className="hero-title">Welcome to DevBlog</h1>
          <p className="hero-subtitle">
            Exploring web development, React, TypeScript, and modern JavaScript
          </p>
          <div className="hero-actions">
            <Link to="/blog" className="btn btn-primary btn-large">
              Browse Articles
            </Link>
            <Link to="/about" className="btn btn-secondary btn-large">
              Learn More
            </Link>
          </div>
        </div>
      </section>

      {/* Featured posts */}
      <section className="featured-section">
        <div className="container">
          <h2 className="section-title">Featured Posts</h2>
          <div className="featured-grid">
            {featuredPosts.map(post => (
              <article key={post.id} className="featured-card">
                <img 
                  src={post.coverImage} 
                  alt={post.title}
                  className="featured-image"
                />
                <div className="featured-content">
                  <span 
                    className="post-category"
                    style={{ background: post.category.color }}
                  >
                    {post.category.name}
                  </span>
                  <h3 className="post-title">
                    <Link to={`/blog/${post.id}`}>{post.title}</Link>
                  </h3>
                  <p className="post-excerpt">{post.excerpt}</p>
                  <div className="post-meta">
                    <Link to={`/author/${post.author.id}`} className="author-link">
                      <img 
                        src={post.author.avatar} 
                        alt={post.author.name}
                        className="author-avatar"
                      />
                      {post.author.name}
                    </Link>
                    <span className="post-date">
                      {new Date(post.publishedDate).toLocaleDateString()}
                    </span>
                  </div>
                </div>
              </article>
            ))}
          </div>
        </div>
      </section>

      {/* Categories section */}
      <section className="categories-section">
        <div className="container">
          <h2 className="section-title">Browse by Category</h2>
          <div className="categories-grid">
            {categories.map(category => (
              <Link 
                key={category.id}
                to={`/category/${category.slug}`}
                className="category-card"
                style={{ borderTopColor: category.color }}
              >
                <h3>{category.name}</h3>
                <p>{category.description}</p>
                <span className="category-count">
                  {category.postCount} articles
                </span>
              </Link>
            ))}
          </div>
        </div>
      </section>

      {/* Recent posts */}
      <section className="recent-section">
        <div className="container">
          <div className="section-header">
            <h2 className="section-title">Recent Posts</h2>
            <Link to="/blog" className="view-all">View all β†’</Link>
          </div>
          <div className="recent-grid">
            {recentPosts.map(post => (
              <article key={post.id} className="post-card">
                <img 
                  src={post.coverImage} 
                  alt={post.title}
                  className="post-image"
                />
                <div className="post-body">
                  <span 
                    className="post-category small"
                    style={{ background: post.category.color }}
                  >
                    {post.category.name}
                  </span>
                  <h3 className="post-title small">
                    <Link to={`/blog/${post.id}`}>{post.title}</Link>
                  </h3>
                  <p className="post-excerpt small">{post.excerpt}</p>
                  <div className="post-footer">
                    <span>{post.readTime} min read</span>
                    <span>{post.views} views</span>
                  </div>
                </div>
              </article>
            ))}
          </div>
        </div>
      </section>
    </div>
  );
}

HomePage Styles

Create src/pages/public/HomePage.css:

/* src/pages/public/HomePage.css */

/* Hero section */
.hero {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 6rem 0;
  text-align: center;
}

.hero-title {
  font-size: 3rem;
  margin-bottom: 1rem;
  color: white;
}

.hero-subtitle {
  font-size: 1.25rem;
  margin-bottom: 2rem;
  opacity: 0.95;
}

.hero-actions {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.btn-large {
  padding: 0.75rem 2rem;
  font-size: 1.125rem;
}

/* Section styling */
.featured-section,
.categories-section,
.recent-section {
  padding: 4rem 0;
}

.section-title {
  font-size: 2rem;
  margin-bottom: 2rem;
  color: var(--gray-900);
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.view-all {
  color: var(--primary);
  font-weight: 500;
}

/* Featured grid */
.featured-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
  gap: 2rem;
}

.featured-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-md);
  transition: transform 0.2s, box-shadow 0.2s;
}

.featured-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.featured-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.featured-content {
  padding: 1.5rem;
}

.post-category {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-sm);
  color: white;
  font-size: 0.875rem;
  font-weight: 500;
  margin-bottom: 1rem;
}

.post-category.small {
  padding: 0.2rem 0.5rem;
  font-size: 0.75rem;
}

.post-title a {
  color: var(--gray-900);
  text-decoration: none;
}

.post-title a:hover {
  color: var(--primary);
}

.post-excerpt {
  color: var(--gray-600);
  line-height: 1.6;
  margin: 1rem 0;
}

.post-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 1rem;
  border-top: 1px solid var(--gray-200);
}

.author-link {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--gray-700);
  text-decoration: none;
}

.author-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
}

.post-date {
  color: var(--gray-500);
  font-size: 0.875rem;
}

/* Categories grid */
.categories-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.5rem;
}

.category-card {
  background: white;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  border-top: 4px solid var(--primary);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
}

.category-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.category-card h3 {
  color: var(--gray-900);
  margin-bottom: 0.5rem;
}

.category-card p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin-bottom: 1rem;
}

.category-count {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  background: var(--gray-100);
  color: var(--gray-700);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
}

/* Recent posts grid */
.recent-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.5rem;
}

.post-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s, box-shadow 0.2s;
}

.post-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.post-image {
  width: 100%;
  height: 180px;
  object-fit: cover;
}

.post-body {
  padding: 1.25rem;
}

.post-title.small {
  font-size: 1.125rem;
  margin: 0.5rem 0;
}

.post-excerpt.small {
  font-size: 0.875rem;
  margin: 0.75rem 0;
}

.post-footer {
  display: flex;
  justify-content: space-between;
  padding-top: 1rem;
  border-top: 1px solid var(--gray-200);
  font-size: 0.875rem;
  color: var(--gray-500);
}

/* Responsive */
@media (max-width: 768px) {
  .hero {
    padding: 4rem 0;
  }

  .hero-title {
    font-size: 2rem;
  }

  .hero-subtitle {
    font-size: 1rem;
  }

  .hero-actions {
    flex-direction: column;
    align-items: stretch;
  }

  .featured-grid,
  .categories-grid,
  .recent-grid {
    grid-template-columns: 1fr;
  }
}

βœ… HomePage Features

  • Hero section - Eye-catching gradient with CTAs
  • Featured posts - Showcases important articles
  • Category grid - Browse by topic
  • Recent posts - Latest content at a glance
  • Responsive design - Works on all screen sizes
  • Hover effects - Interactive and engaging

Page 2: BlogListPage

The BlogListPage displays all blog posts with search and filtering capabilities. Create src/pages/public/BlogListPage.tsx:

// src/pages/public/BlogListPage.tsx
import { useState, useMemo } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import { posts, searchPosts } from '../../data/posts';
import { categories } from '../../data/categories';
import './BlogListPage.css';

export function BlogListPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '');
  
  // Get filter values from URL
  const categoryFilter = searchParams.get('category');
  const sortBy = searchParams.get('sort') || 'date';

  // Filter and sort posts
  const filteredPosts = useMemo(() => {
    let result = [...posts];

    // Apply search
    if (searchQuery) {
      result = searchPosts(searchQuery);
    }

    // Apply category filter
    if (categoryFilter) {
      result = result.filter(post => post.category.slug === categoryFilter);
    }

    // Sort posts
    switch (sortBy) {
      case 'views':
        result.sort((a, b) => b.views - a.views);
        break;
      case 'likes':
        result.sort((a, b) => b.likes - a.likes);
        break;
      case 'date':
      default:
        result.sort((a, b) => 
          new Date(b.publishedDate).getTime() - new Date(a.publishedDate).getTime()
        );
    }

    return result;
  }, [searchQuery, categoryFilter, sortBy]);

  const handleSearch = (e: React.FormEvent) => {
    e.preventDefault();
    if (searchQuery) {
      setSearchParams({ q: searchQuery, ...(categoryFilter && { category: categoryFilter }) });
    } else {
      setSearchParams(categoryFilter ? { category: categoryFilter } : {});
    }
  };

  const handleCategoryFilter = (slug: string) => {
    if (slug === categoryFilter) {
      // Remove filter if clicking same category
      setSearchParams(searchQuery ? { q: searchQuery } : {});
    } else {
      setSearchParams({ 
        ...(searchQuery && { q: searchQuery }),
        category: slug 
      });
    }
  };

  const handleSortChange = (newSort: string) => {
    setSearchParams({
      ...(searchQuery && { q: searchQuery }),
      ...(categoryFilter && { category: categoryFilter }),
      sort: newSort
    });
  };

  return (
    <div className="blog-list-page">
      <div className="container">
        {/* Header */}
        <header className="page-header">
          <h1>All Blog Posts</h1>
          <p>Explore articles about web development, React, TypeScript, and more</p>
        </header>

        {/* Search and filters */}
        <div className="filters-section">
          {/* Search bar */}
          <form onSubmit={handleSearch} className="search-form">
            <input
              type="text"
              placeholder="Search posts..."
              value={searchQuery}
              onChange={(e) => setSearchQuery(e.target.value)}
              className="search-input"
            />
            <button type="submit" className="btn btn-primary">
              Search
            </button>
          </form>

          {/* Category filters */}
          <div className="category-filters">
            <button
              onClick={() => handleCategoryFilter('')}
              className={`filter-btn ${!categoryFilter ? 'active' : ''}`}
            >
              All
            </button>
            {categories.map(cat => (
              <button
                key={cat.id}
                onClick={() => handleCategoryFilter(cat.slug)}
                className={`filter-btn ${categoryFilter === cat.slug ? 'active' : ''}`}
                style={{
                  borderColor: categoryFilter === cat.slug ? cat.color : undefined,
                  color: categoryFilter === cat.slug ? cat.color : undefined
                }}
              >
                {cat.name}
              </button>
            ))}
          </div>

          {/* Sort options */}
          <div className="sort-section">
            <label>Sort by:</label>
            <select 
              value={sortBy} 
              onChange={(e) => handleSortChange(e.target.value)}
              className="sort-select"
            >
              <option value="date">Latest</option>
              <option value="views">Most Viewed</option>
              <option value="likes">Most Liked</option>
            </select>
          </div>
        </div>

        {/* Results info */}
        <div className="results-info">
          <p>
            Showing {filteredPosts.length} {filteredPosts.length === 1 ? 'post' : 'posts'}
            {searchQuery && <> for "{searchQuery}"</>}
            {categoryFilter && <> in {categories.find(c => c.slug === categoryFilter)?.name}</>}
          </p>
          {(searchQuery || categoryFilter) && (
            <button 
              onClick={() => {
                setSearchQuery('');
                setSearchParams({});
              }}
              className="clear-filters"
            >
              Clear filters
            </button>
          )}
        </div>

        {/* Posts grid */}
        {filteredPosts.length > 0 ? (
          <div className="posts-grid">
            {filteredPosts.map(post => (
              <article key={post.id} className="blog-post-card">
                <Link to={`/blog/${post.id}`}>
                  <img 
                    src={post.coverImage} 
                    alt={post.title}
                    className="post-image"
                  />
                </Link>
                <div className="post-content">
                  <Link 
                    to={`/category/${post.category.slug}`}
                    className="post-category"
                    style={{ background: post.category.color }}
                  >
                    {post.category.name}
                  </Link>
                  <h2 className="post-title">
                    <Link to={`/blog/${post.id}`}>{post.title}</Link>
                  </h2>
                  <p className="post-excerpt">{post.excerpt}</p>
                  <div className="post-meta">
                    <Link to={`/author/${post.author.id}`} className="author-info">
                      <img 
                        src={post.author.avatar} 
                        alt={post.author.name}
                        className="author-avatar"
                      />
                      <span>{post.author.name}</span>
                    </Link>
                    <div className="post-stats">
                      <span>πŸ“… {new Date(post.publishedDate).toLocaleDateString()}</span>
                      <span>⏱️ {post.readTime} min</span>
                      <span>πŸ‘οΈ {post.views}</span>
                    </div>
                  </div>
                </div>
              </article>
            ))}
          </div>
        ) : (
          <div className="no-results">
            <h3>No posts found</h3>
            <p>Try adjusting your search or filters</p>
          </div>
        )}
      </div>
    </div>
  );
}

BlogListPage Styles

Create src/pages/public/BlogListPage.css:

/* src/pages/public/BlogListPage.css */
.blog-list-page {
  padding: 2rem 0;
}

/* Page header */
.page-header {
  text-align: center;
  margin-bottom: 3rem;
}

.page-header h1 {
  font-size: 2.5rem;
  margin-bottom: 0.5rem;
}

.page-header p {
  color: var(--gray-600);
  font-size: 1.125rem;
}

/* Filters section */
.filters-section {
  background: white;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  margin-bottom: 2rem;
}

/* Search form */
.search-form {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.search-input {
  flex: 1;
  padding: 0.75rem 1rem;
  border: 1px solid var(--gray-300);
  border-radius: var(--radius-md);
  font-size: 1rem;
}

.search-input:focus {
  outline: none;
  border-color: var(--primary);
}

/* Category filters */
.category-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.filter-btn {
  padding: 0.5rem 1rem;
  background: var(--gray-100);
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  color: var(--gray-700);
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.filter-btn:hover {
  background: var(--gray-200);
}

.filter-btn.active {
  background: white;
  border-color: var(--primary);
  color: var(--primary);
}

/* Sort section */
.sort-section {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.sort-section label {
  margin: 0;
  color: var(--gray-700);
  font-weight: 500;
}

.sort-select {
  padding: 0.5rem 1rem;
  border: 1px solid var(--gray-300);
  border-radius: var(--radius-md);
  background: white;
  cursor: pointer;
}

/* Results info */
.results-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 2px solid var(--gray-200);
}

.results-info p {
  margin: 0;
  color: var(--gray-600);
}

.clear-filters {
  padding: 0.5rem 1rem;
  background: transparent;
  border: 1px solid var(--gray-300);
  border-radius: var(--radius-md);
  color: var(--gray-700);
  cursor: pointer;
  transition: all 0.2s;
}

.clear-filters:hover {
  background: var(--gray-100);
}

/* Posts grid */
.posts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
  gap: 2rem;
}

.blog-post-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s, box-shadow 0.2s;
}

.blog-post-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.blog-post-card .post-image {
  width: 100%;
  height: 220px;
  object-fit: cover;
}

.blog-post-card .post-content {
  padding: 1.5rem;
}

.blog-post-card .post-category {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-sm);
  color: white;
  font-size: 0.875rem;
  font-weight: 500;
  margin-bottom: 1rem;
  text-decoration: none;
}

.blog-post-card .post-title {
  font-size: 1.5rem;
  margin: 0.5rem 0 1rem;
}

.blog-post-card .post-title a {
  color: var(--gray-900);
  text-decoration: none;
}

.blog-post-card .post-title a:hover {
  color: var(--primary);
}

.blog-post-card .post-excerpt {
  color: var(--gray-600);
  line-height: 1.6;
  margin-bottom: 1rem;
}

.blog-post-card .post-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 1rem;
  border-top: 1px solid var(--gray-200);
}

.author-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--gray-700);
  text-decoration: none;
  font-weight: 500;
}

.author-info:hover {
  color: var(--primary);
}

.author-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
}

.post-stats {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: var(--gray-500);
}

/* No results */
.no-results {
  text-align: center;
  padding: 4rem 2rem;
  background: white;
  border-radius: var(--radius-lg);
}

.no-results h3 {
  color: var(--gray-700);
  margin-bottom: 0.5rem;
}

.no-results p {
  color: var(--gray-500);
}

/* Responsive */
@media (max-width: 768px) {
  .page-header h1 {
    font-size: 2rem;
  }

  .search-form {
    flex-direction: column;
  }

  .results-info {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }

  .posts-grid {
    grid-template-columns: 1fr;
  }
}

πŸ’‘ BlogListPage Features

  • Search functionality - Search posts by title or content
  • Category filtering - Filter by specific categories
  • Multiple sort options - Sort by date, views, or likes
  • URL state management - Filters stored in query parameters
  • Clear filters button - Easy reset to default view
  • Results count - Shows how many posts match
  • Responsive grid - Adapts to screen size

Page 3: PostDetailPage

The PostDetailPage shows the full blog post content. Create src/pages/public/PostDetailPage.tsx:

// src/pages/public/PostDetailPage.tsx
import { useParams, Link, useNavigate } from 'react-router-dom';
import { getPostById, getPostsByCategory } from '../../data/posts';
import './PostDetailPage.css';

export function PostDetailPage() {
  const { postId } = useParams<{ postId: string }>();
  const navigate = useNavigate();
  
  const post = postId ? getPostById(postId) : undefined;

  if (!post) {
    return (
      <div className="container" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
        <h1>Post Not Found</h1>
        <p>The post you're looking for doesn't exist.</p>
        <button onClick={() => navigate('/blog')} className="btn btn-primary">
          Back to Blog
        </button>
      </div>
    );
  }

  // Get related posts from the same category
  const relatedPosts = getPostsByCategory(post.category.slug)
    .filter(p => p.id !== post.id)
    .slice(0, 3);

  return (
    <div className="post-detail-page">
      {/* Post header */}
      <header className="post-header">
        <div className="container">
          <Link 
            to={`/category/${post.category.slug}`}
            className="post-category"
            style={{ background: post.category.color }}
          >
            {post.category.name}
          </Link>
          <h1 className="post-title">{post.title}</h1>
          <p className="post-excerpt">{post.excerpt}</p>
          
          <div className="post-meta">
            <Link to={`/author/${post.author.id}`} className="author-section">
              <img 
                src={post.author.avatar} 
                alt={post.author.name}
                className="author-avatar-large"
              />
              <div>
                <div className="author-name">{post.author.name}</div>
                <div className="post-date">
                  {new Date(post.publishedDate).toLocaleDateString('en-US', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric'
                  })}
                </div>
              </div>
            </Link>
            
            <div className="post-stats">
              <span>⏱️ {post.readTime} min read</span>
              <span>πŸ‘οΈ {post.views} views</span>
              <span>❀️ {post.likes} likes</span>
            </div>
          </div>
        </div>
      </header>

      {/* Featured image */}
      <div className="featured-image-container">
        <img 
          src={post.coverImage} 
          alt={post.title}
          className="featured-image"
        />
      </div>

      {/* Post content */}
      <article className="post-content">
        <div className="container">
          <div className="content-wrapper">
            <div className="post-body">
              {/* Render markdown-style content */}
              <div dangerouslySetInnerHTML={{ 
                __html: post.content
                  .replace(/\n\n/g, '</p><p>')
                  .replace(/^# (.+)$/gm, '<h2>$1</h2>')
                  .replace(/^## (.+)$/gm, '<h3>$1</h3>')
                  .replace(/```(\w+)?\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
                  .replace(/`([^`]+)`/g, '<code>$1</code>')
              }} />
            </div>

            {/* Sidebar */}
            <aside className="post-sidebar">
              {/* Tags */}
              <div className="sidebar-section">
                <h3>Tags</h3>
                <div className="tags">
                  {post.tags.map(tag => (
                    <span key={tag} className="tag">
                      #{tag}
                    </span>
                  ))}
                </div>
              </div>

              {/* Share */}
              <div className="sidebar-section">
                <h3>Share</h3>
                <div className="share-buttons">
                  <button className="share-btn">🐦 Twitter</button>
                  <button className="share-btn">πŸ“˜ Facebook</button>
                  <button className="share-btn">πŸ”— LinkedIn</button>
                </div>
              </div>
            </aside>
          </div>
        </div>
      </article>

      {/* Author bio */}
      <section className="author-bio-section">
        <div className="container">
          <div className="author-bio-card">
            <img 
              src={post.author.avatar} 
              alt={post.author.name}
              className="author-avatar-large"
            />
            <div className="author-bio-content">
              <h3>About {post.author.name}</h3>
              <p>{post.author.bio}</p>
              <Link to={`/author/${post.author.id}`} className="btn btn-secondary">
                View Profile
              </Link>
            </div>
          </div>
        </div>
      </section>

      {/* Related posts */}
      {relatedPosts.length > 0 && (
        <section className="related-posts-section">
          <div className="container">
            <h2>Related Posts</h2>
            <div className="related-posts-grid">
              {relatedPosts.map(relatedPost => (
                <article key={relatedPost.id} className="related-post-card">
                  <Link to={`/blog/${relatedPost.id}`}>
                    <img 
                      src={relatedPost.coverImage} 
                      alt={relatedPost.title}
                      className="related-post-image"
                    />
                  </Link>
                  <div className="related-post-content">
                    <h3>
                      <Link to={`/blog/${relatedPost.id}`}>
                        {relatedPost.title}
                      </Link>
                    </h3>
                    <p>{relatedPost.excerpt}</p>
                  </div>
                </article>
              ))}
            </div>
          </div>
        </section>
      )}
    </div>
  );
}

PostDetailPage Styles

Create src/pages/public/PostDetailPage.css:

/* src/pages/public/PostDetailPage.css */

/* Post header */
.post-header {
  background: white;
  padding: 3rem 0 2rem;
  border-bottom: 1px solid var(--gray-200);
}

.post-header .post-category {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-sm);
  color: white;
  font-size: 0.875rem;
  font-weight: 500;
  margin-bottom: 1rem;
  text-decoration: none;
}

.post-header .post-title {
  font-size: 3rem;
  line-height: 1.2;
  margin-bottom: 1rem;
}

.post-header .post-excerpt {
  font-size: 1.25rem;
  color: var(--gray-600);
  margin-bottom: 2rem;
}

.post-header .post-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.author-section {
  display: flex;
  align-items: center;
  gap: 1rem;
  text-decoration: none;
}

.author-section:hover .author-name {
  color: var(--primary);
}

.author-avatar-large {
  width: 60px;
  height: 60px;
  border-radius: 50%;
}

.author-name {
  font-weight: 600;
  color: var(--gray-900);
  font-size: 1.125rem;
}

.post-date {
  color: var(--gray-500);
  font-size: 0.875rem;
}

.post-stats {
  display: flex;
  gap: 1.5rem;
  color: var(--gray-600);
}

/* Featured image */
.featured-image-container {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 2rem;
}

.featured-image {
  width: 100%;
  height: 500px;
  object-fit: cover;
  border-radius: var(--radius-lg);
}

/* Post content */
.post-content {
  padding: 2rem 0;
}

.content-wrapper {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 3rem;
}

.post-body {
  background: white;
  padding: 2rem;
  border-radius: var(--radius-lg);
  line-height: 1.8;
}

.post-body h2 {
  margin-top: 2rem;
  margin-bottom: 1rem;
  color: var(--gray-900);
}

.post-body h3 {
  margin-top: 1.5rem;
  margin-bottom: 0.75rem;
  color: var(--gray-800);
}

.post-body p {
  margin-bottom: 1.25rem;
  color: var(--gray-700);
}

.post-body code {
  background: var(--gray-100);
  padding: 0.2rem 0.4rem;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.post-body pre {
  background: var(--gray-900);
  color: var(--gray-100);
  padding: 1.5rem;
  border-radius: var(--radius-md);
  overflow-x: auto;
  margin: 1.5rem 0;
}

.post-body pre code {
  background: none;
  padding: 0;
  color: inherit;
}

/* Sidebar */
.post-sidebar {
  position: sticky;
  top: 100px;
  height: fit-content;
}

.sidebar-section {
  background: white;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  margin-bottom: 1.5rem;
}

.sidebar-section h3 {
  margin-bottom: 1rem;
  font-size: 1.125rem;
}

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.tag {
  padding: 0.25rem 0.75rem;
  background: var(--gray-100);
  color: var(--gray-700);
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
}

.share-buttons {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.share-btn {
  padding: 0.5rem;
  background: var(--gray-100);
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.2s;
}

.share-btn:hover {
  background: var(--gray-200);
}

/* Author bio section */
.author-bio-section {
  background: var(--gray-100);
  padding: 3rem 0;
  margin-top: 3rem;
}

.author-bio-card {
  background: white;
  padding: 2rem;
  border-radius: var(--radius-lg);
  display: flex;
  gap: 2rem;
  align-items: center;
}

.author-bio-content h3 {
  margin-bottom: 0.5rem;
}

.author-bio-content p {
  color: var(--gray-600);
  margin-bottom: 1rem;
}

/* Related posts */
.related-posts-section {
  padding: 3rem 0;
}

.related-posts-section h2 {
  margin-bottom: 2rem;
  font-size: 2rem;
}

.related-posts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
}

.related-post-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s;
}

.related-post-card:hover {
  transform: translateY(-4px);
}

.related-post-image {
  width: 100%;
  height: 180px;
  object-fit: cover;
}

.related-post-content {
  padding: 1.5rem;
}

.related-post-content h3 {
  font-size: 1.25rem;
  margin-bottom: 0.5rem;
}

.related-post-content h3 a {
  color: var(--gray-900);
  text-decoration: none;
}

.related-post-content h3 a:hover {
  color: var(--primary);
}

.related-post-content p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin: 0;
}

/* Responsive */
@media (max-width: 768px) {
  .post-header .post-title {
    font-size: 2rem;
  }

  .post-header .post-meta {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }

  .featured-image {
    height: 300px;
  }

  .content-wrapper {
    grid-template-columns: 1fr;
  }

  .post-sidebar {
    position: static;
  }

  .author-bio-card {
    flex-direction: column;
    text-align: center;
  }

  .related-posts-grid {
    grid-template-columns: 1fr;
  }
}

βœ… PostDetailPage Features

  • Full post content - Displays complete article with formatting
  • Author information - Bio section with link to profile
  • Related posts - Shows other posts from same category
  • Sticky sidebar - Tags and share buttons stay visible
  • Markdown rendering - Basic markdown-to-HTML conversion
  • Not found handling - Graceful error for missing posts
  • Rich metadata - Views, likes, read time displayed

πŸŽ‰ Three Major Pages Complete!

You've built:

  • βœ… HomePage with hero, featured posts, categories, and recent posts
  • βœ… BlogListPage with search, filters, and sorting
  • βœ… PostDetailPage with full content, author bio, and related posts

These are the core pages of your blog! In the next part, we'll build CategoryPage, AuthorProfilePage, AboutPage, and start on the authentication pages.

πŸ—‚οΈ More Public Pages

Let's complete the remaining public pages: CategoryPage, AuthorProfilePage, and AboutPage. These pages round out the public-facing portion of our blog application.

Page 4: CategoryPage

The CategoryPage shows all posts from a specific category. Create src/pages/public/CategoryPage.tsx:

// src/pages/public/CategoryPage.tsx
import { useParams, Link, useNavigate } from 'react-router-dom';
import { getCategoryBySlug } from '../../data/categories';
import { getPostsByCategory } from '../../data/posts';
import './CategoryPage.css';

export function CategoryPage() {
  const { categoryName } = useParams<{ categoryName: string }>();
  const navigate = useNavigate();
  
  const category = categoryName ? getCategoryBySlug(categoryName) : undefined;
  const posts = category ? getPostsByCategory(category.slug) : [];

  if (!category) {
    return (
      <div className="container" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
        <h1>Category Not Found</h1>
        <p>The category you're looking for doesn't exist.</p>
        <button onClick={() => navigate('/blog')} className="btn btn-primary">
          Back to Blog
        </button>
      </div>
    );
  }

  return (
    <div className="category-page">
      {/* Category header */}
      <header 
        className="category-header"
        style={{ borderTopColor: category.color }}
      >
        <div className="container">
          <div className="breadcrumb-nav">
            <Link to="/blog">Blog</Link>
            <span> / </span>
            <span>{category.name}</span>
          </div>
          <h1 className="category-title">{category.name}</h1>
          <p className="category-description">{category.description}</p>
          <div className="category-stats">
            <span style={{ color: category.color }}>
              {posts.length} {posts.length === 1 ? 'post' : 'posts'}
            </span>
          </div>
        </div>
      </header>

      {/* Posts grid */}
      <section className="category-posts">
        <div className="container">
          {posts.length > 0 ? (
            <div className="posts-grid">
              {posts.map(post => (
                <article key={post.id} className="post-card">
                  <Link to={`/blog/${post.id}`}>
                    <img 
                      src={post.coverImage} 
                      alt={post.title}
                      className="post-image"
                    />
                  </Link>
                  <div className="post-content">
                    {post.featured && (
                      <span className="featured-badge">⭐ Featured</span>
                    )}
                    <h2 className="post-title">
                      <Link to={`/blog/${post.id}`}>{post.title}</Link>
                    </h2>
                    <p className="post-excerpt">{post.excerpt}</p>
                    <div className="post-meta">
                      <Link to={`/author/${post.author.id}`} className="author-info">
                        <img 
                          src={post.author.avatar} 
                          alt={post.author.name}
                          className="author-avatar"
                        />
                        <span>{post.author.name}</span>
                      </Link>
                      <div className="post-stats">
                        <span>πŸ“… {new Date(post.publishedDate).toLocaleDateString()}</span>
                        <span>⏱️ {post.readTime} min</span>
                      </div>
                    </div>
                  </div>
                </article>
              ))}
            </div>
          ) : (
            <div className="no-posts">
              <h3>No posts yet</h3>
              <p>Check back later for content in this category.</p>
            </div>
          )}
        </div>
      </section>
    </div>
  );
}

CategoryPage Styles

Create src/pages/public/CategoryPage.css:

/* src/pages/public/CategoryPage.css */

/* Category header */
.category-header {
  background: white;
  padding: 3rem 0 2rem;
  border-top: 6px solid var(--primary);
  margin-bottom: 3rem;
}

.breadcrumb-nav {
  color: var(--gray-600);
  margin-bottom: 1rem;
  font-size: 0.875rem;
}

.breadcrumb-nav a {
  color: var(--primary);
  text-decoration: none;
}

.breadcrumb-nav a:hover {
  text-decoration: underline;
}

.category-title {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.category-description {
  font-size: 1.25rem;
  color: var(--gray-600);
  margin-bottom: 1.5rem;
}

.category-stats {
  font-size: 1.125rem;
  font-weight: 600;
}

/* Posts section */
.category-posts {
  padding: 2rem 0;
}

.posts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
  gap: 2rem;
}

.post-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s, box-shadow 0.2s;
}

.post-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.post-image {
  width: 100%;
  height: 220px;
  object-fit: cover;
}

.post-content {
  padding: 1.5rem;
}

.featured-badge {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  background: linear-gradient(135deg, #ffd700, #ffed4e);
  color: var(--gray-900);
  font-size: 0.875rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  margin-bottom: 1rem;
}

.post-title {
  font-size: 1.5rem;
  margin: 0.5rem 0 1rem;
}

.post-title a {
  color: var(--gray-900);
  text-decoration: none;
}

.post-title a:hover {
  color: var(--primary);
}

.post-excerpt {
  color: var(--gray-600);
  line-height: 1.6;
  margin-bottom: 1rem;
}

.post-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 1rem;
  border-top: 1px solid var(--gray-200);
}

.author-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--gray-700);
  text-decoration: none;
  font-weight: 500;
}

.author-info:hover {
  color: var(--primary);
}

.author-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
}

.post-stats {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: var(--gray-500);
}

/* No posts */
.no-posts {
  text-align: center;
  padding: 4rem 2rem;
  background: white;
  border-radius: var(--radius-lg);
}

.no-posts h3 {
  color: var(--gray-700);
  margin-bottom: 0.5rem;
}

.no-posts p {
  color: var(--gray-500);
}

/* Responsive */
@media (max-width: 768px) {
  .category-title {
    font-size: 2rem;
  }

  .posts-grid {
    grid-template-columns: 1fr;
  }
}

Page 5: AuthorProfilePage

The AuthorProfilePage displays an author's bio and their posts. Create src/pages/public/AuthorProfilePage.tsx:

// src/pages/public/AuthorProfilePage.tsx
import { useParams, Link, useNavigate } from 'react-router-dom';
import { getAuthorById } from '../../data/authors';
import { getPostsByAuthor } from '../../data/posts';
import './AuthorProfilePage.css';

export function AuthorProfilePage() {
  const { authorId } = useParams<{ authorId: string }>();
  const navigate = useNavigate();
  
  const author = authorId ? getAuthorById(authorId) : undefined;
  const posts = author ? getPostsByAuthor(author.id) : [];

  if (!author) {
    return (
      <div className="container" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
        <h1>Author Not Found</h1>
        <p>The author you're looking for doesn't exist.</p>
        <button onClick={() => navigate('/blog')} className="btn btn-primary">
          Back to Blog
        </button>
      </div>
    );
  }

  return (
    <div className="author-profile-page">
      {/* Author hero */}
      <section className="author-hero">
        <div className="container">
          <div className="author-card">
            <img 
              src={author.avatar} 
              alt={author.name}
              className="author-avatar-xlarge"
            />
            <div className="author-info">
              <h1 className="author-name">{author.name}</h1>
              <p className="author-bio">{author.bio}</p>
              
              {/* Social links */}
              {(author.social.twitter || author.social.github || author.social.linkedin) && (
                <div className="social-links">
                  {author.social.twitter && (
                    <a 
                      href={`https://twitter.com/${author.social.twitter.replace('@', '')}`}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="social-link"
                    >
                      🐦 Twitter
                    </a>
                  )}
                  {author.social.github && (
                    <a 
                      href={`https://github.com/${author.social.github}`}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="social-link"
                    >
                      πŸ’» GitHub
                    </a>
                  )}
                  {author.social.linkedin && (
                    <a 
                      href={`https://linkedin.com/in/${author.social.linkedin}`}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="social-link"
                    >
                      πŸ’Ό LinkedIn
                    </a>
                  )}
                </div>
              )}

              {author.website && (
                <a 
                  href={author.website}
                  target="_blank"
                  rel="noopener noreferrer"
                  className="author-website"
                >
                  🌐 {author.website}
                </a>
              )}
            </div>
          </div>

          {/* Author stats */}
          <div className="author-stats">
            <div className="stat-item">
              <div className="stat-value">{author.postCount}</div>
              <div className="stat-label">Posts</div>
            </div>
            <div className="stat-item">
              <div className="stat-value">
                {posts.reduce((sum, post) => sum + post.views, 0).toLocaleString()}
              </div>
              <div className="stat-label">Total Views</div>
            </div>
            <div className="stat-item">
              <div className="stat-value">
                {posts.reduce((sum, post) => sum + post.likes, 0)}
              </div>
              <div className="stat-label">Total Likes</div>
            </div>
            <div className="stat-item">
              <div className="stat-value">
                {new Date(author.joinedDate).toLocaleDateString('en-US', { 
                  year: 'numeric', 
                  month: 'short' 
                })}
              </div>
              <div className="stat-label">Joined</div>
            </div>
          </div>
        </div>
      </section>

      {/* Author's posts */}
      <section className="author-posts">
        <div className="container">
          <h2>Posts by {author.name}</h2>
          
          {posts.length > 0 ? (
            <div className="posts-grid">
              {posts.map(post => (
                <article key={post.id} className="post-card">
                  <Link to={`/blog/${post.id}`}>
                    <img 
                      src={post.coverImage} 
                      alt={post.title}
                      className="post-image"
                    />
                  </Link>
                  <div className="post-content">
                    <Link 
                      to={`/category/${post.category.slug}`}
                      className="post-category"
                      style={{ background: post.category.color }}
                    >
                      {post.category.name}
                    </Link>
                    <h3 className="post-title">
                      <Link to={`/blog/${post.id}`}>{post.title}</Link>
                    </h3>
                    <p className="post-excerpt">{post.excerpt}</p>
                    <div className="post-footer">
                      <span>πŸ“… {new Date(post.publishedDate).toLocaleDateString()}</span>
                      <span>⏱️ {post.readTime} min</span>
                      <span>πŸ‘οΈ {post.views}</span>
                    </div>
                  </div>
                </article>
              ))}
            </div>
          ) : (
            <div className="no-posts">
              <h3>No posts yet</h3>
              <p>This author hasn't published any posts.</p>
            </div>
          )}
        </div>
      </section>
    </div>
  );
}

AuthorProfilePage Styles

Create src/pages/public/AuthorProfilePage.css:

/* src/pages/public/AuthorProfilePage.css */

/* Author hero */
.author-hero {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 4rem 0;
}

.author-card {
  display: flex;
  gap: 2rem;
  align-items: center;
  margin-bottom: 3rem;
}

.author-avatar-xlarge {
  width: 150px;
  height: 150px;
  border-radius: 50%;
  border: 4px solid white;
  box-shadow: var(--shadow-lg);
}

.author-info {
  flex: 1;
}

.author-name {
  font-size: 2.5rem;
  margin-bottom: 1rem;
  color: white;
}

.author-bio {
  font-size: 1.25rem;
  margin-bottom: 1.5rem;
  opacity: 0.95;
}

.social-links {
  display: flex;
  gap: 1rem;
  margin-bottom: 1rem;
}

.social-link {
  padding: 0.5rem 1rem;
  background: rgba(255, 255, 255, 0.2);
  color: white;
  text-decoration: none;
  border-radius: var(--radius-md);
  transition: background 0.2s;
}

.social-link:hover {
  background: rgba(255, 255, 255, 0.3);
  color: white;
}

.author-website {
  display: inline-block;
  color: white;
  text-decoration: underline;
  opacity: 0.9;
}

.author-website:hover {
  opacity: 1;
  color: white;
}

/* Author stats */
.author-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1.5rem;
}

.stat-item {
  background: rgba(255, 255, 255, 0.2);
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  text-align: center;
}

.stat-value {
  font-size: 2rem;
  font-weight: 700;
  margin-bottom: 0.25rem;
}

.stat-label {
  opacity: 0.9;
  font-size: 0.875rem;
}

/* Author's posts */
.author-posts {
  padding: 4rem 0;
}

.author-posts h2 {
  font-size: 2rem;
  margin-bottom: 2rem;
}

.posts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
  gap: 2rem;
}

.post-card {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.2s, box-shadow 0.2s;
}

.post-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.post-image {
  width: 100%;
  height: 220px;
  object-fit: cover;
}

.post-content {
  padding: 1.5rem;
}

.post-category {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-sm);
  color: white;
  font-size: 0.875rem;
  font-weight: 500;
  margin-bottom: 1rem;
  text-decoration: none;
}

.post-title {
  font-size: 1.5rem;
  margin: 0.5rem 0 1rem;
}

.post-title a {
  color: var(--gray-900);
  text-decoration: none;
}

.post-title a:hover {
  color: var(--primary);
}

.post-excerpt {
  color: var(--gray-600);
  line-height: 1.6;
  margin-bottom: 1rem;
}

.post-footer {
  display: flex;
  justify-content: space-between;
  padding-top: 1rem;
  border-top: 1px solid var(--gray-200);
  font-size: 0.875rem;
  color: var(--gray-500);
}

/* No posts */
.no-posts {
  text-align: center;
  padding: 4rem 2rem;
  background: white;
  border-radius: var(--radius-lg);
}

.no-posts h3 {
  color: var(--gray-700);
  margin-bottom: 0.5rem;
}

.no-posts p {
  color: var(--gray-500);
}

/* Responsive */
@media (max-width: 768px) {
  .author-card {
    flex-direction: column;
    text-align: center;
  }

  .author-name {
    font-size: 2rem;
  }

  .social-links {
    justify-content: center;
  }

  .author-stats {
    grid-template-columns: repeat(2, 1fr);
  }

  .posts-grid {
    grid-template-columns: 1fr;
  }
}

Page 6: AboutPage

Finally, let's create a simple AboutPage. Create src/pages/public/AboutPage.tsx:

// src/pages/public/AboutPage.tsx
import { Link } from 'react-router-dom';
import { authors } from '../../data/authors';
import { categories } from '../../data/categories';
import { posts } from '../../data/posts';
import './AboutPage.css';

export function AboutPage() {
  const totalViews = posts.reduce((sum, post) => sum + post.views, 0);
  const totalLikes = posts.reduce((sum, post) => sum + post.likes, 0);

  return (
    <div className="about-page">
      {/* Hero section */}
      <section className="about-hero">
        <div className="container">
          <h1>About DevBlog</h1>
          <p className="hero-subtitle">
            A platform for developers to learn, share, and grow together
          </p>
        </div>
      </section>

      {/* Mission section */}
      <section className="mission-section">
        <div className="container">
          <div className="mission-content">
            <h2>Our Mission</h2>
            <p>
              DevBlog was created to be a hub for web developers to share knowledge, 
              learn new skills, and stay up-to-date with the latest in React, TypeScript, 
              and modern web development.
            </p>
            <p>
              We believe in the power of community and the importance of accessible, 
              high-quality technical content. Whether you're just starting your coding 
              journey or you're a seasoned professional, there's something here for you.
            </p>
          </div>
        </div>
      </section>

      {/* Stats section */}
      <section className="stats-section">
        <div className="container">
          <h2>By the Numbers</h2>
          <div className="stats-grid">
            <div className="stat-card">
              <div className="stat-number">{posts.length}</div>
              <div className="stat-label">Articles Published</div>
            </div>
            <div className="stat-card">
              <div className="stat-number">{authors.length}</div>
              <div className="stat-label">Contributing Authors</div>
            </div>
            <div className="stat-card">
              <div className="stat-number">{totalViews.toLocaleString()}</div>
              <div className="stat-label">Total Views</div>
            </div>
            <div className="stat-card">
              <div className="stat-number">{categories.length}</div>
              <div className="stat-label">Categories</div>
            </div>
          </div>
        </div>
      </section>

      {/* Authors section */}
      <section className="authors-section">
        <div className="container">
          <h2>Meet Our Authors</h2>
          <div className="authors-grid">
            {authors.map(author => (
              <Link 
                key={author.id} 
                to={`/author/${author.id}`}
                className="author-card-link"
              >
                <img 
                  src={author.avatar} 
                  alt={author.name}
                  className="author-avatar"
                />
                <h3>{author.name}</h3>
                <p>{author.bio}</p>
                <div className="author-stats">
                  {author.postCount} {author.postCount === 1 ? 'post' : 'posts'}
                </div>
              </Link>
            ))}
          </div>
        </div>
      </section>

      {/* Topics section */}
      <section className="topics-section">
        <div className="container">
          <h2>What We Write About</h2>
          <div className="topics-grid">
            {categories.map(category => (
              <Link 
                key={category.id}
                to={`/category/${category.slug}`}
                className="topic-card"
                style={{ borderLeftColor: category.color }}
              >
                <h3>{category.name}</h3>
                <p>{category.description}</p>
              </Link>
            ))}
          </div>
        </div>
      </section>

      {/* CTA section */}
      <section className="cta-section">
        <div className="container">
          <h2>Ready to Start Reading?</h2>
          <p>Explore our collection of articles and tutorials</p>
          <div className="cta-buttons">
            <Link to="/blog" className="btn btn-primary btn-large">
              Browse Articles
            </Link>
            <Link to="/register" className="btn btn-secondary btn-large">
              Create Account
            </Link>
          </div>
        </div>
      </section>
    </div>
  );
}

AboutPage Styles

Create src/pages/public/AboutPage.css:

/* src/pages/public/AboutPage.css */

/* Hero section */
.about-hero {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 5rem 0;
  text-align: center;
}

.about-hero h1 {
  font-size: 3rem;
  margin-bottom: 1rem;
  color: white;
}

.hero-subtitle {
  font-size: 1.5rem;
  opacity: 0.95;
}

/* Mission section */
.mission-section {
  padding: 4rem 0;
  background: white;
}

.mission-content {
  max-width: 800px;
  margin: 0 auto;
}

.mission-content h2 {
  font-size: 2.5rem;
  margin-bottom: 2rem;
  text-align: center;
}

.mission-content p {
  font-size: 1.125rem;
  line-height: 1.8;
  color: var(--gray-700);
  margin-bottom: 1.5rem;
}

/* Stats section */
.stats-section {
  padding: 4rem 0;
  background: var(--gray-50);
}

.stats-section h2 {
  font-size: 2.5rem;
  text-align: center;
  margin-bottom: 3rem;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 2rem;
}

.stat-card {
  background: white;
  padding: 2rem;
  border-radius: var(--radius-lg);
  text-align: center;
  box-shadow: var(--shadow-sm);
}

.stat-number {
  font-size: 3rem;
  font-weight: 700;
  color: var(--primary);
  margin-bottom: 0.5rem;
}

.stat-label {
  color: var(--gray-600);
  font-size: 1rem;
}

/* Authors section */
.authors-section {
  padding: 4rem 0;
  background: white;
}

.authors-section h2 {
  font-size: 2.5rem;
  text-align: center;
  margin-bottom: 3rem;
}

.authors-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 2rem;
}

.author-card-link {
  background: var(--gray-50);
  padding: 2rem;
  border-radius: var(--radius-lg);
  text-align: center;
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
  display: block;
}

.author-card-link:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}

.author-card-link .author-avatar {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  margin-bottom: 1rem;
}

.author-card-link h3 {
  color: var(--gray-900);
  margin-bottom: 0.5rem;
}

.author-card-link p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin-bottom: 1rem;
}

.author-card-link .author-stats {
  color: var(--primary);
  font-weight: 600;
}

/* Topics section */
.topics-section {
  padding: 4rem 0;
  background: var(--gray-50);
}

.topics-section h2 {
  font-size: 2.5rem;
  text-align: center;
  margin-bottom: 3rem;
}

.topics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

.topic-card {
  background: white;
  padding: 1.5rem;
  border-left: 4px solid var(--primary);
  border-radius: var(--radius-md);
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
  display: block;
}

.topic-card:hover {
  transform: translateX(4px);
  box-shadow: var(--shadow-md);
}

.topic-card h3 {
  color: var(--gray-900);
  margin-bottom: 0.5rem;
}

.topic-card p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin: 0;
}

/* CTA section */
.cta-section {
  padding: 5rem 0;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  text-align: center;
}

.cta-section h2 {
  font-size: 2.5rem;
  margin-bottom: 1rem;
  color: white;
}

.cta-section p {
  font-size: 1.25rem;
  margin-bottom: 2rem;
  opacity: 0.95;
}

.cta-buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.btn-large {
  padding: 1rem 2rem;
  font-size: 1.125rem;
}

/* Responsive */
@media (max-width: 768px) {
  .about-hero h1 {
    font-size: 2rem;
  }

  .hero-subtitle {
    font-size: 1.125rem;
  }

  .mission-content h2,
  .stats-section h2,
  .authors-section h2,
  .topics-section h2,
  .cta-section h2 {
    font-size: 2rem;
  }

  .stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .authors-grid,
  .topics-grid {
    grid-template-columns: 1fr;
  }

  .cta-buttons {
    flex-direction: column;
    align-items: stretch;
  }
}

βœ… All Public Pages Complete!

  • HomePage - Featured posts, categories, recent content βœ…
  • BlogListPage - Search, filter, sort functionality βœ…
  • PostDetailPage - Full article view βœ…
  • CategoryPage - Category-filtered posts βœ…
  • AuthorProfilePage - Author bio and their posts βœ…
  • AboutPage - Platform information βœ…

πŸŽ‰ Public Section Complete!

All six public pages are now built and styled! You have a fully functional blog front-end with:

  • βœ… Beautiful, responsive layouts
  • βœ… Dynamic routing with URL parameters
  • βœ… Search and filtering
  • βœ… Author profiles and category pages
  • βœ… Related posts and social links

Next, we'll build the authentication pages (Login, Register, Forgot Password) and then move on to the protected dashboard pages!

πŸ” Authentication Pages

Now let's build the authentication pages that use our AuthLayout. These pages handle user login, registration, and password recovery. Remember, our authentication is simulated for demo purposes.

Page 7: LoginPage

The LoginPage allows users to log in and redirects them back to where they were trying to go. Create src/pages/auth/LoginPage.tsx:

// src/pages/auth/LoginPage.tsx
import { useState, FormEvent } from 'react';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import { login } from '../../utils/auth';
import './AuthPages.css';

export function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();
  
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [rememberMe, setRememberMe] = useState(false);
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  // Get the page they were trying to visit
  const from = (location.state as any)?.from?.pathname || '/dashboard';

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setError('');
    setLoading(true);

    // Simulate API call delay
    setTimeout(() => {
      const success = login(email, password);
      
      if (success) {
        // Redirect to the page they were trying to visit
        navigate(from, { replace: true });
      } else {
        setError('Invalid email or password');
        setLoading(false);
      }
    }, 1000);
  };

  return (
    <div className="auth-page">
      <div className="auth-card">
        <h1>Welcome Back</h1>
        <p className="auth-subtitle">Sign in to your account</p>

        {error && (
          <div className="error-message">
            ⚠️ {error}
          </div>
        )}

        <form onSubmit={handleSubmit} className="auth-form">
          <div className="form-group">
            <label htmlFor="email">Email Address</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@example.com"
              required
              disabled={loading}
            />
          </div>

          <div className="form-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              id="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="Enter your password"
              required
              disabled={loading}
            />
          </div>

          <div className="form-row">
            <label className="checkbox-label">
              <input
                type="checkbox"
                checked={rememberMe}
                onChange={(e) => setRememberMe(e.target.checked)}
                disabled={loading}
              />
              <span>Remember me</span>
            </label>
            <Link to="/forgot-password" className="forgot-link">
              Forgot password?
            </Link>
          </div>

          <button 
            type="submit" 
            className="btn btn-primary btn-full"
            disabled={loading}
          >
            {loading ? 'Signing in...' : 'Sign In'}
          </button>
        </form>

        <div className="auth-divider">
          <span>or</span>
        </div>

        <div className="auth-footer">
          <p>
            Don't have an account?{' '}
            <Link to="/register" className="auth-link">
              Sign up
            </Link>
          </p>
        </div>

        <div className="demo-info">
          <p>πŸ’‘ Demo mode: Any email/password will work!</p>
        </div>
      </div>
    </div>
  );
}

Page 8: RegisterPage

The RegisterPage allows new users to create an account. Create src/pages/auth/RegisterPage.tsx:

// src/pages/auth/RegisterPage.tsx
import { useState, FormEvent } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { register } from '../../utils/auth';
import './AuthPages.css';

export function RegisterPage() {
  const navigate = useNavigate();
  
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [agreeToTerms, setAgreeToTerms] = useState(false);
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setError('');

    // Validation
    if (password !== confirmPassword) {
      setError('Passwords do not match');
      return;
    }

    if (password.length < 6) {
      setError('Password must be at least 6 characters');
      return;
    }

    if (!agreeToTerms) {
      setError('You must agree to the terms and conditions');
      return;
    }

    setLoading(true);

    // Simulate API call delay
    setTimeout(() => {
      const success = register(name, email, password);
      
      if (success) {
        // Redirect to dashboard after registration
        navigate('/dashboard', { replace: true });
      } else {
        setError('Registration failed. Please try again.');
        setLoading(false);
      }
    }, 1000);
  };

  return (
    <div className="auth-page">
      <div className="auth-card">
        <h1>Create Account</h1>
        <p className="auth-subtitle">Join DevBlog and start writing</p>

        {error && (
          <div className="error-message">
            ⚠️ {error}
          </div>
        )}

        <form onSubmit={handleSubmit} className="auth-form">
          <div className="form-group">
            <label htmlFor="name">Full Name</label>
            <input
              type="text"
              id="name"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="John Doe"
              required
              disabled={loading}
            />
          </div>

          <div className="form-group">
            <label htmlFor="email">Email Address</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@example.com"
              required
              disabled={loading}
            />
          </div>

          <div className="form-group">
            <label htmlFor="password">Password</label>
            <input
              type="password"
              id="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="At least 6 characters"
              required
              disabled={loading}
              minLength={6}
            />
          </div>

          <div className="form-group">
            <label htmlFor="confirmPassword">Confirm Password</label>
            <input
              type="password"
              id="confirmPassword"
              value={confirmPassword}
              onChange={(e) => setConfirmPassword(e.target.value)}
              placeholder="Repeat your password"
              required
              disabled={loading}
              minLength={6}
            />
          </div>

          <div className="form-group">
            <label className="checkbox-label">
              <input
                type="checkbox"
                checked={agreeToTerms}
                onChange={(e) => setAgreeToTerms(e.target.checked)}
                disabled={loading}
              />
              <span>
                I agree to the{' '}
                <a href="#" onClick={(e) => e.preventDefault()}>
                  Terms and Conditions
                </a>
              </span>
            </label>
          </div>

          <button 
            type="submit" 
            className="btn btn-primary btn-full"
            disabled={loading}
          >
            {loading ? 'Creating account...' : 'Create Account'}
          </button>
        </form>

        <div className="auth-divider">
          <span>or</span>
        </div>

        <div className="auth-footer">
          <p>
            Already have an account?{' '}
            <Link to="/login" className="auth-link">
              Sign in
            </Link>
          </p>
        </div>
      </div>
    </div>
  );
}

Page 9: ForgotPasswordPage

The ForgotPasswordPage simulates password recovery. Create src/pages/auth/ForgotPasswordPage.tsx:

// src/pages/auth/ForgotPasswordPage.tsx
import { useState, FormEvent } from 'react';
import { Link } from 'react-router-dom';
import './AuthPages.css';

export function ForgotPasswordPage() {
  const [email, setEmail] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);

    // Simulate API call delay
    setTimeout(() => {
      setSubmitted(true);
      setLoading(false);
    }, 1000);
  };

  if (submitted) {
    return (
      <div className="auth-page">
        <div className="auth-card">
          <div className="success-message">
            <div className="success-icon">βœ…</div>
            <h1>Check Your Email</h1>
            <p>
              We've sent a password reset link to <strong>{email}</strong>.
              Please check your inbox and follow the instructions.
            </p>
            <p className="text-muted">
              Didn't receive the email? Check your spam folder or try again.
            </p>
            <Link to="/login" className="btn btn-primary btn-full">
              Back to Login
            </Link>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="auth-page">
      <div className="auth-card">
        <h1>Reset Password</h1>
        <p className="auth-subtitle">
          Enter your email address and we'll send you a link to reset your password.
        </p>

        <form onSubmit={handleSubmit} className="auth-form">
          <div className="form-group">
            <label htmlFor="email">Email Address</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@example.com"
              required
              disabled={loading}
            />
          </div>

          <button 
            type="submit" 
            className="btn btn-primary btn-full"
            disabled={loading}
          >
            {loading ? 'Sending...' : 'Send Reset Link'}
          </button>
        </form>

        <div className="auth-divider">
          <span>or</span>
        </div>

        <div className="auth-footer">
          <p>
            Remember your password?{' '}
            <Link to="/login" className="auth-link">
              Sign in
            </Link>
          </p>
        </div>
      </div>
    </div>
  );
}

Shared Authentication Styles

Create src/pages/auth/AuthPages.css for all auth pages:

/* src/pages/auth/AuthPages.css */

.auth-page {
  padding: 2rem;
}

.auth-card {
  max-width: 450px;
  margin: 0 auto;
}

.auth-card h1 {
  font-size: 2rem;
  text-align: center;
  margin-bottom: 0.5rem;
  color: var(--gray-900);
}

.auth-subtitle {
  text-align: center;
  color: var(--gray-600);
  margin-bottom: 2rem;
}

/* Form styles */
.auth-form {
  margin-bottom: 1.5rem;
}

.form-group {
  margin-bottom: 1.5rem;
}

.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: var(--gray-700);
  font-weight: 500;
}

.form-group input {
  width: 100%;
  padding: 0.75rem;
  border: 2px solid var(--gray-300);
  border-radius: var(--radius-md);
  font-size: 1rem;
  transition: border-color 0.2s;
}

.form-group input:focus {
  outline: none;
  border-color: var(--primary);
}

.form-group input:disabled {
  background: var(--gray-100);
  cursor: not-allowed;
}

/* Form row (for remember me / forgot password) */
.form-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}

/* Checkbox label */
.checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  margin: 0;
  font-weight: normal;
}

.checkbox-label input[type="checkbox"] {
  width: auto;
  cursor: pointer;
}

.checkbox-label span {
  color: var(--gray-700);
}

.checkbox-label a {
  color: var(--primary);
  text-decoration: none;
}

.checkbox-label a:hover {
  text-decoration: underline;
}

/* Links */
.forgot-link,
.auth-link {
  color: var(--primary);
  text-decoration: none;
  font-weight: 500;
}

.forgot-link:hover,
.auth-link:hover {
  text-decoration: underline;
}

/* Button */
.btn-full {
  width: 100%;
  padding: 0.875rem;
  font-size: 1rem;
  font-weight: 600;
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* Divider */
.auth-divider {
  text-align: center;
  margin: 1.5rem 0;
  position: relative;
}

.auth-divider::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: var(--gray-300);
}

.auth-divider span {
  position: relative;
  background: white;
  padding: 0 1rem;
  color: var(--gray-500);
  font-size: 0.875rem;
}

/* Footer */
.auth-footer {
  text-align: center;
}

.auth-footer p {
  color: var(--gray-600);
  margin: 0;
}

/* Messages */
.error-message {
  background: #fee;
  color: #c33;
  padding: 1rem;
  border-radius: var(--radius-md);
  margin-bottom: 1.5rem;
  border-left: 4px solid #c33;
}

.success-message {
  text-align: center;
}

.success-icon {
  font-size: 4rem;
  margin-bottom: 1rem;
}

.success-message h1 {
  color: var(--success);
  margin-bottom: 1rem;
}

.success-message p {
  color: var(--gray-700);
  margin-bottom: 1rem;
  line-height: 1.6;
}

.text-muted {
  color: var(--gray-500);
  font-size: 0.875rem;
}

/* Demo info */
.demo-info {
  background: var(--gray-100);
  padding: 1rem;
  border-radius: var(--radius-md);
  margin-top: 1.5rem;
  text-align: center;
}

.demo-info p {
  margin: 0;
  color: var(--gray-600);
  font-size: 0.875rem;
}

/* Responsive */
@media (max-width: 480px) {
  .auth-card h1 {
    font-size: 1.5rem;
  }

  .form-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }
}

πŸ’‘ Authentication Pages Features

  • LoginPage - Remembers where user was trying to go and redirects back
  • RegisterPage - Validates password match and length
  • ForgotPasswordPage - Two-step flow with success confirmation
  • Form validation - Client-side validation with error messages
  • Loading states - Disabled inputs during submission
  • Simulated delays - Realistic API call experience
  • Demo indicator - Reminds users this is demo mode

Testing Authentication Flow

Let's verify the authentication system works correctly:

Test Scenarios

  1. Protected route redirect:
    • Navigate to /dashboard while logged out
    • Should redirect to /login
    • After logging in, should redirect back to /dashboard
  2. Registration flow:
    • Click "Sign up" from login page
    • Fill out registration form
    • Submit with non-matching passwords (should error)
    • Fix and submit successfully
    • Should redirect to dashboard and be logged in
  3. Forgot password:
    • Click "Forgot password?" from login
    • Enter email and submit
    • Should show success message
    • Click "Back to Login" to return
  4. Navigation while authenticated:
    • Log in successfully
    • Navigate to public pages (should show dashboard link)
    • Click logout (should return to home)
    • Try accessing dashboard (should redirect to login)

⚠️ Authentication Reminder

Remember, this authentication is for demonstration purposes only:

  • Any email/password combination will work
  • Data is stored in localStorage (not secure)
  • No password hashing or encryption
  • No real API calls
  • No token management

In a production app, you would use a real authentication service like Auth0, Firebase Auth, or a backend API with JWT tokens.

βœ… Authentication Pages Complete!

  • βœ… LoginPage with remember me and redirect logic
  • βœ… RegisterPage with validation
  • βœ… ForgotPasswordPage with success state
  • βœ… Shared styling for all auth pages
  • βœ… Error handling and loading states
  • βœ… AuthLayout providing centered, gradient design

🎊 Major Milestone Reached!

You've completed:

  • βœ… All 6 public pages
  • βœ… All 3 authentication pages
  • βœ… Complete routing structure
  • βœ… Protected route system
  • βœ… Three distinct layouts

Project is approximately 70% complete! Next up: Dashboard pages where authenticated users can manage their content.

πŸ“Š Dashboard Pages

Now let's build the protected dashboard pages where authenticated users can manage their content. These pages use the DashboardLayout and demonstrate nested routing with the settings section.

Page 10: DashboardHomePage

The dashboard home shows an overview and statistics. Create src/pages/dashboard/DashboardHomePage.tsx:

// src/pages/dashboard/DashboardHomePage.tsx
import { Link } from 'react-router-dom';
import { getCurrentUser } from '../../utils/auth';
import { posts } from '../../data/posts';
import './DashboardPages.css';

export function DashboardHomePage() {
  const user = getCurrentUser();
  
  // In a real app, these would be filtered by the current user
  const userPosts = posts.slice(0, 3);
  const totalViews = userPosts.reduce((sum, post) => sum + post.views, 0);
  const totalLikes = userPosts.reduce((sum, post) => sum + post.likes, 0);

  return (
    <div className="dashboard-home">
      {/* Welcome section */}
      <div className="dashboard-header">
        <div>
          <h1>Dashboard</h1>
          <p>Welcome back, {user?.name}! Here's what's happening with your blog.</p>
        </div>
        <Link to="/dashboard/posts/new" className="btn btn-primary">
          βž• New Post
        </Link>
      </div>

      {/* Stats cards */}
      <div className="stats-grid">
        <div className="stat-card">
          <div className="stat-icon" style={{ background: '#667eea' }}>πŸ“</div>
          <div className="stat-content">
            <div className="stat-value">{userPosts.length}</div>
            <div className="stat-label">Total Posts</div>
          </div>
        </div>

        <div className="stat-card">
          <div className="stat-icon" style={{ background: '#48bb78' }}>πŸ‘οΈ</div>
          <div className="stat-content">
            <div className="stat-value">{totalViews.toLocaleString()}</div>
            <div className="stat-label">Total Views</div>
          </div>
        </div>

        <div className="stat-card">
          <div className="stat-icon" style={{ background: '#ed8936' }}>❀️</div>
          <div className="stat-content">
            <div className="stat-value">{totalLikes}</div>
            <div className="stat-label">Total Likes</div>
          </div>
        </div>

        <div className="stat-card">
          <div className="stat-icon" style={{ background: '#9f7aea' }}>πŸ“ˆ</div>
          <div className="stat-content">
            <div className="stat-value">
              {Math.round(totalViews / userPosts.length || 0)}
            </div>
            <div className="stat-label">Avg. Views/Post</div>
          </div>
        </div>
      </div>

      {/* Recent posts */}
      <div className="dashboard-section">
        <div className="section-header">
          <h2>Recent Posts</h2>
          <Link to="/dashboard/posts" className="view-all-link">
            View all β†’
          </Link>
        </div>

        <div className="posts-list">
          {userPosts.map(post => (
            <div key={post.id} className="post-item">
              <img 
                src={post.coverImage} 
                alt={post.title}
                className="post-thumbnail"
              />
              <div className="post-info">
                <h3>
                  <Link to={`/blog/${post.id}`}>{post.title}</Link>
                </h3>
                <p>{post.excerpt}</p>
                <div className="post-meta">
                  <span>πŸ“… {new Date(post.publishedDate).toLocaleDateString()}</span>
                  <span>πŸ‘οΈ {post.views} views</span>
                  <span>❀️ {post.likes} likes</span>
                </div>
              </div>
              <div className="post-actions">
                <Link 
                  to={`/dashboard/posts/${post.id}/edit`}
                  className="btn btn-secondary btn-sm"
                >
                  Edit
                </Link>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Quick actions */}
      <div className="dashboard-section">
        <h2>Quick Actions</h2>
        <div className="quick-actions">
          <Link to="/dashboard/posts/new" className="action-card">
            <div className="action-icon">✍️</div>
            <h3>Write New Post</h3>
            <p>Share your knowledge with the community</p>
          </Link>

          <Link to="/dashboard/posts" className="action-card">
            <div className="action-icon">πŸ“š</div>
            <h3>Manage Posts</h3>
            <p>Edit or delete your existing posts</p>
          </Link>

          <Link to="/dashboard/settings" className="action-card">
            <div className="action-icon">βš™οΈ</div>
            <h3>Settings</h3>
            <p>Update your profile and preferences</p>
          </Link>

          <Link to="/blog" className="action-card">
            <div className="action-icon">🌐</div>
            <h3>View Blog</h3>
            <p>See how your blog looks to visitors</p>
          </Link>
        </div>
      </div>
    </div>
  );
}

Page 11: MyPostsPage

This page lists all of the user's posts. Create src/pages/dashboard/MyPostsPage.tsx:

// src/pages/dashboard/MyPostsPage.tsx
import { Link } from 'react-router-dom';
import { posts } from '../../data/posts';
import './DashboardPages.css';

export function MyPostsPage() {
  // In a real app, filter by current user
  const userPosts = posts;

  return (
    <div className="my-posts-page">
      <div className="dashboard-header">
        <div>
          <h1>My Posts</h1>
          <p>Manage all your published articles</p>
        </div>
        <Link to="/dashboard/posts/new" className="btn btn-primary">
          βž• New Post
        </Link>
      </div>

      <div className="posts-table">
        <table>
          <thead>
            <tr>
              <th>Post</th>
              <th>Category</th>
              <th>Published</th>
              <th>Views</th>
              <th>Likes</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {userPosts.map(post => (
              <tr key={post.id}>
                <td>
                  <div className="post-cell">
                    <img 
                      src={post.coverImage} 
                      alt={post.title}
                      className="post-thumb"
                    />
                    <div>
                      <Link to={`/blog/${post.id}`} className="post-title-link">
                        {post.title}
                      </Link>
                      {post.featured && (
                        <span className="featured-badge-sm">⭐</span>
                      )}
                    </div>
                  </div>
                </td>
                <td>
                  <span 
                    className="category-badge"
                    style={{ background: post.category.color }}
                  >
                    {post.category.name}
                  </span>
                </td>
                <td>{new Date(post.publishedDate).toLocaleDateString()}</td>
                <td>{post.views}</td>
                <td>{post.likes}</td>
                <td>
                  <div className="table-actions">
                    <Link 
                      to={`/dashboard/posts/${post.id}/edit`}
                      className="btn btn-secondary btn-sm"
                    >
                      Edit
                    </Link>
                    <button className="btn btn-danger btn-sm">
                      Delete
                    </button>
                  </div>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Page 12: CreatePostPage

This page provides a form to create new posts. Create src/pages/dashboard/CreatePostPage.tsx:

// src/pages/dashboard/CreatePostPage.tsx
import { useState, FormEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import { categories } from '../../data/categories';
import './DashboardPages.css';

export function CreatePostPage() {
  const navigate = useNavigate();
  
  const [title, setTitle] = useState('');
  const [excerpt, setExcerpt] = useState('');
  const [content, setContent] = useState('');
  const [categoryId, setCategoryId] = useState('');
  const [coverImage, setCoverImage] = useState('');
  const [tags, setTags] = useState('');
  const [featured, setFeatured] = useState(false);

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    
    // In a real app, send to API
    console.log('Creating post:', {
      title,
      excerpt,
      content,
      categoryId,
      coverImage,
      tags: tags.split(',').map(t => t.trim()),
      featured
    });

    // Redirect to posts list
    navigate('/dashboard/posts');
  };

  return (
    <div className="create-post-page">
      <div className="dashboard-header">
        <div>
          <h1>Create New Post</h1>
          <p>Share your knowledge with the community</p>
        </div>
      </div>

      <form onSubmit={handleSubmit} className="post-form">
        <div className="form-grid">
          <div className="form-main">
            <div className="form-group">
              <label htmlFor="title">Post Title</label>
              <input
                type="text"
                id="title"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                placeholder="Enter a catchy title..."
                required
              />
            </div>

            <div className="form-group">
              <label htmlFor="excerpt">Excerpt</label>
              <textarea
                id="excerpt"
                value={excerpt}
                onChange={(e) => setExcerpt(e.target.value)}
                placeholder="Brief description of your post..."
                rows={3}
                required
              />
            </div>

            <div className="form-group">
              <label htmlFor="content">Content</label>
              <textarea
                id="content"
                value={content}
                onChange={(e) => setContent(e.target.value)}
                placeholder="Write your post content here... (Markdown supported)"
                rows={15}
                required
              />
            </div>
          </div>

          <div className="form-sidebar">
            <div className="sidebar-card">
              <h3>Post Settings</h3>

              <div className="form-group">
                <label htmlFor="category">Category</label>
                <select
                  id="category"
                  value={categoryId}
                  onChange={(e) => setCategoryId(e.target.value)}
                  required
                >
                  <option value="">Select category...</option>
                  {categories.map(cat => (
                    <option key={cat.id} value={cat.id}>
                      {cat.name}
                    </option>
                  ))}
                </select>
              </div>

              <div className="form-group">
                <label htmlFor="coverImage">Cover Image URL</label>
                <input
                  type="url"
                  id="coverImage"
                  value={coverImage}
                  onChange={(e) => setCoverImage(e.target.value)}
                  placeholder="https://..."
                />
              </div>

              <div className="form-group">
                <label htmlFor="tags">Tags</label>
                <input
                  type="text"
                  id="tags"
                  value={tags}
                  onChange={(e) => setTags(e.target.value)}
                  placeholder="react, typescript, tutorial"
                />
                <small>Separate tags with commas</small>
              </div>

              <div className="form-group">
                <label className="checkbox-label">
                  <input
                    type="checkbox"
                    checked={featured}
                    onChange={(e) => setFeatured(e.target.checked)}
                  />
                  <span>Mark as featured</span>
                </label>
              </div>
            </div>

            <div className="form-actions">
              <button type="submit" className="btn btn-primary btn-full">
                Publish Post
              </button>
              <button 
                type="button" 
                className="btn btn-secondary btn-full"
                onClick={() => navigate('/dashboard/posts')}
              >
                Cancel
              </button>
            </div>
          </div>
        </div>
      </form>
    </div>
  );
}

Page 13: EditPostPage

Similar to create, but pre-populated. Create src/pages/dashboard/EditPostPage.tsx:

// src/pages/dashboard/EditPostPage.tsx
import { useState, useEffect, FormEvent } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { getPostById } from '../../data/posts';
import { categories } from '../../data/categories';
import './DashboardPages.css';

export function EditPostPage() {
  const { postId } = useParams<{ postId: string }>();
  const navigate = useNavigate();
  const post = postId ? getPostById(postId) : undefined;

  const [title, setTitle] = useState('');
  const [excerpt, setExcerpt] = useState('');
  const [content, setContent] = useState('');
  const [categoryId, setCategoryId] = useState('');
  const [coverImage, setCoverImage] = useState('');
  const [tags, setTags] = useState('');
  const [featured, setFeatured] = useState(false);

  useEffect(() => {
    if (post) {
      setTitle(post.title);
      setExcerpt(post.excerpt);
      setContent(post.content);
      setCategoryId(post.category.id);
      setCoverImage(post.coverImage);
      setTags(post.tags.join(', '));
      setFeatured(post.featured);
    }
  }, [post]);

  if (!post) {
    return (
      <div className="container" style={{ padding: '2rem', textAlign: 'center' }}>
        <h1>Post Not Found</h1>
        <button onClick={() => navigate('/dashboard/posts')} className="btn btn-primary">
          Back to Posts
        </button>
      </div>
    );
  }

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    
    // In a real app, send to API
    console.log('Updating post:', {
      id: postId,
      title,
      excerpt,
      content,
      categoryId,
      coverImage,
      tags: tags.split(',').map(t => t.trim()),
      featured
    });

    navigate('/dashboard/posts');
  };

  return (
    <div className="edit-post-page">
      <div className="dashboard-header">
        <div>
          <h1>Edit Post</h1>
          <p>Update your post content</p>
        </div>
      </div>

      <form onSubmit={handleSubmit} className="post-form">
        <div className="form-grid">
          <div className="form-main">
            <div className="form-group">
              <label htmlFor="title">Post Title</label>
              <input
                type="text"
                id="title"
                value={title}
                onChange={(e) => setTitle(e.target.value)}
                required
              />
            </div>

            <div className="form-group">
              <label htmlFor="excerpt">Excerpt</label>
              <textarea
                id="excerpt"
                value={excerpt}
                onChange={(e) => setExcerpt(e.target.value)}
                rows={3}
                required
              />
            </div>

            <div className="form-group">
              <label htmlFor="content">Content</label>
              <textarea
                id="content"
                value={content}
                onChange={(e) => setContent(e.target.value)}
                rows={15}
                required
              />
            </div>
          </div>

          <div className="form-sidebar">
            <div className="sidebar-card">
              <h3>Post Settings</h3>

              <div className="form-group">
                <label htmlFor="category">Category</label>
                <select
                  id="category"
                  value={categoryId}
                  onChange={(e) => setCategoryId(e.target.value)}
                  required
                >
                  {categories.map(cat => (
                    <option key={cat.id} value={cat.id}>
                      {cat.name}
                    </option>
                  ))}
                </select>
              </div>

              <div className="form-group">
                <label htmlFor="coverImage">Cover Image URL</label>
                <input
                  type="url"
                  id="coverImage"
                  value={coverImage}
                  onChange={(e) => setCoverImage(e.target.value)}
                />
              </div>

              <div className="form-group">
                <label htmlFor="tags">Tags</label>
                <input
                  type="text"
                  id="tags"
                  value={tags}
                  onChange={(e) => setTags(e.target.value)}
                />
              </div>

              <div className="form-group">
                <label className="checkbox-label">
                  <input
                    type="checkbox"
                    checked={featured}
                    onChange={(e) => setFeatured(e.target.checked)}
                  />
                  <span>Mark as featured</span>
                </label>
              </div>
            </div>

            <div className="form-actions">
              <button type="submit" className="btn btn-primary btn-full">
                Update Post
              </button>
              <button 
                type="button" 
                className="btn btn-secondary btn-full"
                onClick={() => navigate('/dashboard/posts')}
              >
                Cancel
              </button>
            </div>
          </div>
        </div>
      </form>
    </div>
  );
}

Dashboard Pages Styles

Create src/pages/dashboard/DashboardPages.css:

/* src/pages/dashboard/DashboardPages.css */

/* Dashboard header */
.dashboard-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.dashboard-header h1 {
  font-size: 2rem;
  margin-bottom: 0.25rem;
}

.dashboard-header p {
  color: var(--gray-600);
  margin: 0;
}

/* Stats grid */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
  margin-bottom: 2rem;
}

.stat-card {
  background: white;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  gap: 1rem;
}

.stat-icon {
  width: 60px;
  height: 60px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  color: white;
}

.stat-value {
  font-size: 2rem;
  font-weight: 700;
  color: var(--gray-900);
}

.stat-label {
  color: var(--gray-600);
  font-size: 0.875rem;
}

/* Dashboard section */
.dashboard-section {
  background: white;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  margin-bottom: 2rem;
}

.dashboard-section h2 {
  margin-bottom: 1.5rem;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}

.section-header h2 {
  margin: 0;
}

.view-all-link {
  color: var(--primary);
  font-weight: 500;
  text-decoration: none;
}

.view-all-link:hover {
  text-decoration: underline;
}

/* Posts list */
.posts-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.post-item {
  display: flex;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--gray-200);
  border-radius: var(--radius-md);
  transition: box-shadow 0.2s;
}

.post-item:hover {
  box-shadow: var(--shadow-sm);
}

.post-thumbnail {
  width: 120px;
  height: 80px;
  object-fit: cover;
  border-radius: var(--radius-md);
  flex-shrink: 0;
}

.post-info {
  flex: 1;
  min-width: 0;
}

.post-info h3 {
  margin: 0 0 0.5rem;
  font-size: 1.125rem;
}

.post-info h3 a {
  color: var(--gray-900);
  text-decoration: none;
}

.post-info h3 a:hover {
  color: var(--primary);
}

.post-info p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin: 0 0 0.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.post-meta {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: var(--gray-500);
}

.post-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  flex-shrink: 0;
}

.btn-sm {
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
}

.btn-danger {
  background: var(--danger);
  color: white;
}

.btn-danger:hover {
  background: #e53e3e;
}

/* Quick actions */
.quick-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
}

.action-card {
  background: var(--gray-50);
  padding: 2rem;
  border-radius: var(--radius-lg);
  text-align: center;
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
}

.action-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

.action-icon {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.action-card h3 {
  color: var(--gray-900);
  margin-bottom: 0.5rem;
}

.action-card p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin: 0;
}

/* Posts table */
.posts-table {
  background: white;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.posts-table table {
  width: 100%;
  border-collapse: collapse;
}

.posts-table th {
  background: var(--gray-100);
  padding: 1rem;
  text-align: left;
  font-weight: 600;
  color: var(--gray-700);
  border-bottom: 2px solid var(--gray-200);
}

.posts-table td {
  padding: 1rem;
  border-bottom: 1px solid var(--gray-200);
}

.posts-table tbody tr:hover {
  background: var(--gray-50);
}

.post-cell {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.post-thumb {
  width: 60px;
  height: 40px;
  object-fit: cover;
  border-radius: var(--radius-sm);
}

.post-title-link {
  color: var(--gray-900);
  text-decoration: none;
  font-weight: 500;
}

.post-title-link:hover {
  color: var(--primary);
}

.featured-badge-sm {
  margin-left: 0.5rem;
  font-size: 0.875rem;
}

.category-badge {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-sm);
  color: white;
  font-size: 0.875rem;
  font-weight: 500;
}

.table-actions {
  display: flex;
  gap: 0.5rem;
}

/* Post form */
.post-form {
  background: white;
  padding: 2rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.form-grid {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 2rem;
}

.form-main {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.form-sidebar {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.sidebar-card {
  background: var(--gray-50);
  padding: 1.5rem;
  border-radius: var(--radius-lg);
}

.sidebar-card h3 {
  margin: 0 0 1.5rem;
  font-size: 1.125rem;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.form-group label {
  font-weight: 500;
  color: var(--gray-700);
}

.form-group input,
.form-group textarea,
.form-group select {
  padding: 0.75rem;
  border: 1px solid var(--gray-300);
  border-radius: var(--radius-md);
  font-family: inherit;
}

.form-group textarea {
  resize: vertical;
  font-family: var(--font-mono);
}

.form-group small {
  color: var(--gray-500);
  font-size: 0.875rem;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
  width: auto;
  cursor: pointer;
}

.form-actions {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.btn-full {
  width: 100%;
}

/* Responsive */
@media (max-width: 1024px) {
  .form-grid {
    grid-template-columns: 1fr;
  }

  .form-sidebar {
    order: -1;
  }
}

@media (max-width: 768px) {
  .dashboard-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }

  .stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .post-item {
    flex-direction: column;
  }

  .post-thumbnail {
    width: 100%;
    height: 160px;
  }

  .posts-table {
    overflow-x: auto;
  }

  .quick-actions {
    grid-template-columns: 1fr;
  }
}

βœ… Dashboard Pages Features

  • DashboardHome - Stats overview, recent posts, quick actions
  • MyPosts - Table view of all posts with edit/delete
  • CreatePost - Full post creation form with sidebar settings
  • EditPost - Pre-populated form for editing
  • Responsive tables - Mobile-friendly data display
  • Form validation - Required fields and proper inputs

🎊 Dashboard Core Complete!

You've built the main dashboard functionality! Only one section remaining: the nested Settings pages.

βš™οΈ Settings Pages (Nested Routing)

Now let's implement the settings section with nested routing. This demonstrates how to have a layout within a layout, with its own navigation tabs. The settings section already has its SettingsLayout created earlier, now we'll build the actual settings pages.

Settings Index Page

The settings index shows an overview. Create src/pages/dashboard/settings/SettingsIndexPage.tsx:

// src/pages/dashboard/settings/SettingsIndexPage.tsx
import { Link } from 'react-router-dom';
import './SettingsPages.css';

export function SettingsIndexPage() {
  return (
    <div className="settings-index">
      <h2>Account Settings</h2>
      <p>Manage your account preferences and settings.</p>

      <div className="settings-cards">
        <Link to="/dashboard/settings/profile" className="settings-card">
          <div className="settings-icon">πŸ‘€</div>
          <h3>Profile Settings</h3>
          <p>Update your name, bio, and profile picture</p>
        </Link>

        <Link to="/dashboard/settings/account" className="settings-card">
          <div className="settings-icon">πŸ”</div>
          <h3>Account Settings</h3>
          <p>Change your email, password, and security options</p>
        </Link>

        <Link to="/dashboard/settings/preferences" className="settings-card">
          <div className="settings-icon">🎨</div>
          <h3>Preferences</h3>
          <p>Customize your experience with theme and notifications</p>
        </Link>
      </div>
    </div>
  );
}

Profile Settings Page

Edit profile information. Create src/pages/dashboard/settings/ProfileSettingsPage.tsx:

// src/pages/dashboard/settings/ProfileSettingsPage.tsx
import { useState, FormEvent } from 'react';
import { getCurrentUser, updateUser } from '../../../utils/auth';
import './SettingsPages.css';

export function ProfileSettingsPage() {
  const user = getCurrentUser();
  
  const [name, setName] = useState(user?.name || '');
  const [bio, setBio] = useState('Full-stack developer passionate about React and TypeScript');
  const [website, setWebsite] = useState('https://example.com');
  const [twitter, setTwitter] = useState('@username');
  const [github, setGithub] = useState('username');
  const [saved, setSaved] = useState(false);

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    
    updateUser({ name });
    
    setSaved(true);
    setTimeout(() => setSaved(false), 3000);
  };

  return (
    <div className="settings-page">
      <h2>Profile Settings</h2>
      <p>Update your public profile information</p>

      {saved && (
        <div className="success-banner">
          βœ… Profile updated successfully!
        </div>
      )}

      <form onSubmit={handleSubmit} className="settings-form">
        <div className="form-section">
          <h3>Basic Information</h3>

          <div className="form-group">
            <label htmlFor="name">Display Name</label>
            <input
              type="text"
              id="name"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="Your name"
            />
          </div>

          <div className="form-group">
            <label htmlFor="bio">Bio</label>
            <textarea
              id="bio"
              value={bio}
              onChange={(e) => setBio(e.target.value)}
              placeholder="Tell us about yourself"
              rows={4}
            />
          </div>

          <div className="form-group">
            <label htmlFor="website">Website</label>
            <input
              type="url"
              id="website"
              value={website}
              onChange={(e) => setWebsite(e.target.value)}
              placeholder="https://yourwebsite.com"
            />
          </div>
        </div>

        <div className="form-section">
          <h3>Social Links</h3>

          <div className="form-group">
            <label htmlFor="twitter">Twitter</label>
            <input
              type="text"
              id="twitter"
              value={twitter}
              onChange={(e) => setTwitter(e.target.value)}
              placeholder="@username"
            />
          </div>

          <div className="form-group">
            <label htmlFor="github">GitHub</label>
            <input
              type="text"
              id="github"
              value={github}
              onChange={(e) => setGithub(e.target.value)}
              placeholder="username"
            />
          </div>
        </div>

        <div className="form-actions">
          <button type="submit" className="btn btn-primary">
            Save Changes
          </button>
        </div>
      </form>
    </div>
  );
}

Account Settings Page

Manage email and password. Create src/pages/dashboard/settings/AccountSettingsPage.tsx:

// src/pages/dashboard/settings/AccountSettingsPage.tsx
import { useState, FormEvent } from 'react';
import { getCurrentUser } from '../../../utils/auth';
import './SettingsPages.css';

export function AccountSettingsPage() {
  const user = getCurrentUser();
  
  const [email, setEmail] = useState(user?.email || '');
  const [currentPassword, setCurrentPassword] = useState('');
  const [newPassword, setNewPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [saved, setSaved] = useState(false);
  const [error, setError] = useState('');

  const handleEmailSubmit = (e: FormEvent) => {
    e.preventDefault();
    setSaved(true);
    setTimeout(() => setSaved(false), 3000);
  };

  const handlePasswordSubmit = (e: FormEvent) => {
    e.preventDefault();
    setError('');

    if (newPassword !== confirmPassword) {
      setError('New passwords do not match');
      return;
    }

    if (newPassword.length < 6) {
      setError('Password must be at least 6 characters');
      return;
    }

    // Simulate password update
    setCurrentPassword('');
    setNewPassword('');
    setConfirmPassword('');
    setSaved(true);
    setTimeout(() => setSaved(false), 3000);
  };

  return (
    <div className="settings-page">
      <h2>Account Settings</h2>
      <p>Manage your email and password</p>

      {saved && (
        <div className="success-banner">
          βœ… Settings updated successfully!
        </div>
      )}

      {error && (
        <div className="error-banner">
          ⚠️ {error}
        </div>
      )}

      {/* Email section */}
      <form onSubmit={handleEmailSubmit} className="settings-form">
        <div className="form-section">
          <h3>Email Address</h3>

          <div className="form-group">
            <label htmlFor="email">Email</label>
            <input
              type="email"
              id="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="you@example.com"
            />
          </div>

          <div className="form-actions">
            <button type="submit" className="btn btn-primary">
              Update Email
            </button>
          </div>
        </div>
      </form>

      {/* Password section */}
      <form onSubmit={handlePasswordSubmit} className="settings-form">
        <div className="form-section">
          <h3>Change Password</h3>

          <div className="form-group">
            <label htmlFor="currentPassword">Current Password</label>
            <input
              type="password"
              id="currentPassword"
              value={currentPassword}
              onChange={(e) => setCurrentPassword(e.target.value)}
              placeholder="Enter current password"
            />
          </div>

          <div className="form-group">
            <label htmlFor="newPassword">New Password</label>
            <input
              type="password"
              id="newPassword"
              value={newPassword}
              onChange={(e) => setNewPassword(e.target.value)}
              placeholder="Enter new password"
              minLength={6}
            />
          </div>

          <div className="form-group">
            <label htmlFor="confirmPassword">Confirm New Password</label>
            <input
              type="password"
              id="confirmPassword"
              value={confirmPassword}
              onChange={(e) => setConfirmPassword(e.target.value)}
              placeholder="Confirm new password"
              minLength={6}
            />
          </div>

          <div className="form-actions">
            <button type="submit" className="btn btn-primary">
              Change Password
            </button>
          </div>
        </div>
      </form>

      {/* Danger zone */}
      <div className="form-section danger-zone">
        <h3>Danger Zone</h3>
        <p>These actions are permanent and cannot be undone.</p>
        <button className="btn btn-danger">
          Delete Account
        </button>
      </div>
    </div>
  );
}

Preferences Page

UI preferences and notifications. Create src/pages/dashboard/settings/PreferencesPage.tsx:

// src/pages/dashboard/settings/PreferencesPage.tsx
import { useState, FormEvent } from 'react';
import './SettingsPages.css';

export function PreferencesPage() {
  const [theme, setTheme] = useState('light');
  const [emailNotifications, setEmailNotifications] = useState(true);
  const [commentNotifications, setCommentNotifications] = useState(true);
  const [weeklyDigest, setWeeklyDigest] = useState(false);
  const [saved, setSaved] = useState(false);

  const handleSubmit = (e: FormEvent) => {
    e.preventDefault();
    setSaved(true);
    setTimeout(() => setSaved(false), 3000);
  };

  return (
    <div className="settings-page">
      <h2>Preferences</h2>
      <p>Customize your experience</p>

      {saved && (
        <div className="success-banner">
          βœ… Preferences saved successfully!
        </div>
      )}

      <form onSubmit={handleSubmit} className="settings-form">
        <div className="form-section">
          <h3>Appearance</h3>

          <div className="form-group">
            <label htmlFor="theme">Theme</label>
            <select
              id="theme"
              value={theme}
              onChange={(e) => setTheme(e.target.value)}
            >
              <option value="light">Light</option>
              <option value="dark">Dark</option>
              <option value="auto">Auto (system default)</option>
            </select>
          </div>
        </div>

        <div className="form-section">
          <h3>Notifications</h3>

          <div className="checkbox-group">
            <label className="checkbox-label">
              <input
                type="checkbox"
                checked={emailNotifications}
                onChange={(e) => setEmailNotifications(e.target.checked)}
              />
              <div>
                <div className="checkbox-title">Email Notifications</div>
                <div className="checkbox-description">
                  Receive email updates about your account activity
                </div>
              </div>
            </label>

            <label className="checkbox-label">
              <input
                type="checkbox"
                checked={commentNotifications}
                onChange={(e) => setCommentNotifications(e.target.checked)}
              />
              <div>
                <div className="checkbox-title">Comment Notifications</div>
                <div className="checkbox-description">
                  Get notified when someone comments on your posts
                </div>
              </div>
            </label>

            <label className="checkbox-label">
              <input
                type="checkbox"
                checked={weeklyDigest}
                onChange={(e) => setWeeklyDigest(e.target.checked)}
              />
              <div>
                <div className="checkbox-title">Weekly Digest</div>
                <div className="checkbox-description">
                  Receive a weekly summary of popular posts
                </div>
              </div>
            </label>
          </div>
        </div>

        <div className="form-actions">
          <button type="submit" className="btn btn-primary">
            Save Preferences
          </button>
        </div>
      </form>
    </div>
  );
}

Settings Pages Styles

Create src/pages/dashboard/settings/SettingsPages.css:

/* src/pages/dashboard/settings/SettingsPages.css */

.settings-index {
  padding: 1rem 0;
}

.settings-index h2 {
  margin-bottom: 0.5rem;
}

.settings-index p {
  color: var(--gray-600);
  margin-bottom: 2rem;
}

/* Settings cards */
.settings-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

.settings-card {
  background: white;
  padding: 2rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  transition: transform 0.2s, box-shadow 0.2s;
  display: block;
}

.settings-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

.settings-icon {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.settings-card h3 {
  color: var(--gray-900);
  margin-bottom: 0.5rem;
}

.settings-card p {
  color: var(--gray-600);
  font-size: 0.875rem;
  margin: 0;
}

/* Settings page */
.settings-page {
  padding: 1rem 0;
}

.settings-page h2 {
  margin-bottom: 0.5rem;
}

.settings-page > p {
  color: var(--gray-600);
  margin-bottom: 2rem;
}

/* Banners */
.success-banner {
  background: #e8f5e9;
  color: #2e7d32;
  padding: 1rem;
  border-radius: var(--radius-md);
  margin-bottom: 1.5rem;
  border-left: 4px solid #4caf50;
}

.error-banner {
  background: #ffebee;
  color: #c62828;
  padding: 1rem;
  border-radius: var(--radius-md);
  margin-bottom: 1.5rem;
  border-left: 4px solid #f44336;
}

/* Settings form */
.settings-form {
  background: white;
  padding: 2rem;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  margin-bottom: 2rem;
}

.form-section {
  margin-bottom: 2rem;
}

.form-section:last-child {
  margin-bottom: 0;
}

.form-section h3 {
  margin-bottom: 1.5rem;
  padding-bottom: 0.75rem;
  border-bottom: 2px solid var(--gray-200);
}

.form-group {
  margin-bottom: 1.5rem;
}

.form-group label {
  display: block;
  margin-bottom: 0.5rem;
  font-weight: 500;
  color: var(--gray-700);
}

.form-group input,
.form-group textarea,
.form-group select {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--gray-300);
  border-radius: var(--radius-md);
  font-family: inherit;
}

.form-group textarea {
  resize: vertical;
}

/* Checkbox group */
.checkbox-group {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.checkbox-label {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding: 1rem;
  background: var(--gray-50);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.2s;
}

.checkbox-label:hover {
  background: var(--gray-100);
}

.checkbox-label input[type="checkbox"] {
  width: auto;
  margin-top: 0.25rem;
  cursor: pointer;
}

.checkbox-title {
  font-weight: 500;
  color: var(--gray-900);
  margin-bottom: 0.25rem;
}

.checkbox-description {
  font-size: 0.875rem;
  color: var(--gray-600);
}

/* Form actions */
.form-actions {
  display: flex;
  gap: 1rem;
  padding-top: 1rem;
}

/* Danger zone */
.danger-zone {
  background: #fff5f5;
  border: 2px solid #feb2b2;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
}

.danger-zone h3 {
  color: var(--danger);
  border-bottom-color: #feb2b2;
}

.danger-zone p {
  color: var(--gray-700);
  margin-bottom: 1rem;
}

/* Responsive */
@media (max-width: 768px) {
  .settings-cards {
    grid-template-columns: 1fr;
  }

  .settings-form {
    padding: 1.5rem;
  }

  .checkbox-label {
    flex-direction: column;
    align-items: flex-start;
  }
}

βœ… Settings Pages Features

  • SettingsIndex - Overview cards linking to each settings section
  • ProfileSettings - Edit name, bio, website, social links
  • AccountSettings - Change email, password, delete account
  • Preferences - Theme selection, notification settings
  • Nested routing - All settings share the SettingsLayout navigation
  • Success/error messages - Temporary banners for feedback
  • Form validation - Password matching, length requirements

Testing Nested Routing

Verify the settings nested routing works correctly:

  1. Navigate to /dashboard/settings - should show index with cards
  2. Click "Profile Settings" - URL changes to /dashboard/settings/profile
  3. Notice the settings navigation tabs update (active state)
  4. Click "Account" tab - navigates to /dashboard/settings/account
  5. Click "Preferences" tab - navigates to /dashboard/settings/preferences
  6. Click dashboard sidebar "Settings" - goes back to index
  7. Notice SettingsLayout's Outlet switches between pages

🎊 ALL PAGES COMPLETE!

Congratulations! You've successfully built:

  • βœ… 3 layouts (Public, Auth, Dashboard)
  • βœ… 6 public pages
  • βœ… 3 authentication pages
  • βœ… 4 dashboard pages
  • βœ… 4 settings pages (nested routing)
  • βœ… Complete routing system with 20+ routes

Total: 20 pages across 3 layouts with sophisticated routing! πŸš€

🎯 Project Summary & Next Steps

Congratulations on completing this comprehensive React Router project! Let's review what you've built and explore ways to enhance it further.

What You've Accomplished

mindmap root((Blog App)) Public Section Home Page Blog List Post Detail Category Pages Author Profiles About Page Authentication Login Register Forgot Password Dashboard Overview My Posts Create Post Edit Post Settings Profile Account Preferences

πŸ“Š Project Statistics

Total Pages: 20 unique pages
Routes: 24+ defined routes
Layouts: 4 layouts (Public, Auth, Dashboard, Settings)
Components: 25+ components created
Lines of Code: ~3,000+ lines
Features: Search, filters, auth, CRUD, nested routing

Key Concepts Mastered

βœ… React Router Concepts

  • Basic Routing - Routes, Route, BrowserRouter
  • Navigation - Link, NavLink with active states
  • Layout Routes - Outlet for persistent UI
  • Dynamic Routes - URL parameters (postId, authorId, categoryName)
  • Protected Routes - Authentication guards with redirects
  • Query Parameters - Search and filter state in URL
  • Nested Routes - Routes within routes (Settings)
  • Programmatic Navigation - useNavigate for redirects
  • Location State - Passing data between routes
  • Index Routes - Default child routes
  • 404 Handling - Catch-all route for not found
  • useParams Hook - Extracting URL parameters
  • useSearchParams Hook - Reading/writing query strings
  • useLocation Hook - Accessing current location

Enhancement Ideas

πŸš€ Take It Further

Here are ways to expand this project:

1. Real Backend Integration
  • Replace mock data with real API calls
  • Implement proper authentication with JWT
  • Add actual database persistence
  • Handle loading and error states
2. Advanced Features
  • Add comments system on posts
  • Implement post drafts vs published
  • Add rich text editor (TipTap, Quill)
  • Image upload functionality
  • Post scheduling
  • Analytics dashboard with charts
3. State Management
  • Add Zustand or Redux for global state
  • Implement React Query for data fetching
  • Add optimistic updates
  • Cache invalidation strategies
4. Performance Optimizations
  • Code splitting with React.lazy()
  • Image optimization and lazy loading
  • Virtual scrolling for long lists
  • Memoization with useMemo/useCallback
5. User Experience
  • Add loading skeletons
  • Implement toast notifications
  • Add confirmation dialogs
  • Keyboard shortcuts
  • Dark mode toggle
  • Animations and transitions
6. Testing
  • Unit tests with React Testing Library
  • Integration tests for user flows
  • E2E tests with Playwright or Cypress
  • Test routing logic and protected routes

Common Routing Patterns Reference

Quick Reference Guide

// 1. Basic Route
<Route path="/about" element={<AboutPage />} />

// 2. Dynamic Route
<Route path="/blog/:postId" element={<PostDetail />} />

// 3. Layout Route
<Route element={<PublicLayout />}>
  <Route path="/" element={<HomePage />} />
</Route>

// 4. Protected Route
<Route element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
  <Route path="/dashboard" element={<Dashboard />} />
</Route>

// 5. Nested Routes
<Route path="/dashboard/settings" element={<SettingsLayout />}>
  <Route index element={<SettingsIndex />} />
  <Route path="profile" element={<ProfileSettings />} />
</Route>

// 6. Index Route
<Route index element={<HomePage />} />

// 7. Catch-all (404)
<Route path="*" element={<NotFoundPage />} />

// 8. Programmatic Navigation
const navigate = useNavigate();
navigate('/dashboard');

// 9. Get URL Parameters
const { postId } = useParams();

// 10. Query Parameters
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
setSearchParams({ q: 'react' });

Deployment Checklist

πŸ“¦ Before Deploying

  • ☐ Replace mock data with real API
  • ☐ Implement proper authentication
  • ☐ Add environment variables
  • ☐ Set up error boundaries
  • ☐ Add analytics tracking
  • ☐ Optimize images and assets
  • ☐ Test on multiple browsers
  • ☐ Test on mobile devices
  • ☐ Configure 404 redirects on server
  • ☐ Set up CI/CD pipeline
  • ☐ Add meta tags for SEO
  • ☐ Configure CORS if needed

Resources for Continued Learning

πŸŽ‰ Congratulations!

You've successfully completed the Multi-Page Blog Application project!

You've mastered React Router v6, built a production-quality application with 20+ pages, implemented nested routing, protected routes, and sophisticated navigation patterns. This project demonstrates real-world routing architecture that you can use as a foundation for any React application.

Keep building, keep learning, and keep pushing forward! πŸš€