add react components and pages

This commit is contained in:
Kyle Belanger 2025-02-28 13:29:10 -05:00
parent b786c86cf4
commit 259005c833
9 changed files with 423 additions and 35 deletions

View file

@ -1,35 +1,29 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import { Routes, Route } from 'react-router-dom'
import Header from './components/Header'
import Footer from './components/Footer'
import About from './pages/About'
import Blog from './pages/Blog'
import Post from './pages/Post'
import Projects from './pages/Projects'
import NotFound from './pages/NotFound'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
<div className="app">
<Header />
<main className="content">
<Routes>
<Route path="/" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<Post />} />
<Route path="/projects" element={<Projects />} />
<Route path="*" element={<NotFound />} />
</Routes>
</main>
<Footer />
</div>
)
}
export default App
export default App

View file

@ -0,0 +1,20 @@
import './Footer.css'
function Footer() {
const currentYear = new Date().getFullYear()
return (
<footer className="footer">
<div className="container">
<p>© {currentYear} Your Name. All rights reserved.</p>
<div className="social-links">
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer">GitHub</a>
<a href="https://linkedin.com/in/yourusername" target="_blank" rel="noopener noreferrer">LinkedIn</a>
<a href="https://twitter.com/yourusername" target="_blank" rel="noopener noreferrer">Twitter</a>
</div>
</div>
</footer>
)
}
export default Footer

View file

@ -0,0 +1,21 @@
import { NavLink } from 'react-router-dom'
import './Header.css'
function Header() {
return (
<header className="header">
<div className="container">
<h1 className="logo">Your Name</h1>
<nav className="nav">
<ul>
<li><NavLink to="/" end>About</NavLink></li>
<li><NavLink to="/blog">Blog</NavLink></li>
<li><NavLink to="/projects">Projects</NavLink></li>
</ul>
</nav>
</div>
</header>
)
}
export default Header

View file

@ -1,10 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
import './index.css'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)

View file

@ -0,0 +1,44 @@
import './About.css'
function About() {
return (
<div className="about-page">
<div className="container">
<h1>About Me</h1>
<div className="about-content">
<div className="about-image">
<img src="/profile-picture.jpg" alt="Your Name" />
</div>
<div className="about-text">
<p>
Hello, I'm [Your Name]. I'm a [Your Profession] based in [Your Location].
I specialize in [Your Skills/Specialties].
</p>
<p>
I've worked on various projects, from [Type of Project] to [Type of Project].
My goal is to [Your Professional Goal].
</p>
<p>
When I'm not coding, I enjoy [Your Hobbies/Interests].
</p>
<div className="skills">
<h2>Skills</h2>
<ul>
<li>JavaScript (React, Node.js)</li>
<li>HTML & CSS</li>
<li>Python</li>
{/* Add more skills as needed */}
</ul>
</div>
<div className="contact">
<h2>Contact</h2>
<p>Feel free to reach out to me at <a href="mailto:your.email@example.com">your.email@example.com</a></p>
</div>
</div>
</div>
</div>
</div>
)
}
export default About

109
client/src/pages/Blog.jsx Normal file
View file

@ -0,0 +1,109 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import { format } from 'date-fns'
import './Blog.css'
function Blog() {
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [selectedTag, setSelectedTag] = useState(null)
useEffect(() => {
const fetchPosts = async () => {
try {
const response = await axios.get('http://localhost:5000/api/posts')
setPosts(response.data)
setLoading(false)
} catch (err) {
console.error('Error fetching posts:', err)
setError('Failed to load blog posts. Please try again later.')
setLoading(false)
}
}
fetchPosts()
}, [])
// Extract all unique tags from posts
const allTags = posts.reduce((tags, post) => {
if (post.tags) {
post.tags.forEach(tag => {
if (!tags.includes(tag)) {
tags.push(tag)
}
})
}
return tags
}, [])
// Filter posts by selected tag
const filteredPosts = selectedTag
? posts.filter(post => post.tags && post.tags.includes(selectedTag))
: posts
if (loading) return <div className="loading">Loading posts...</div>
if (error) return <div className="error">{error}</div>
return (
<div className="blog-page">
<div className="container">
<h1>Blog</h1>
{allTags.length > 0 && (
<div className="tags-filter">
<span>Filter by tag: </span>
<button
className={selectedTag === null ? 'active' : ''}
onClick={() => setSelectedTag(null)}
>
All
</button>
{allTags.map(tag => (
<button
key={tag}
className={selectedTag === tag ? 'active' : ''}
onClick={() => setSelectedTag(tag)}
>
{tag}
</button>
))}
</div>
)}
<div className="posts-list">
{filteredPosts.length === 0 ? (
<p>No posts found.</p>
) : (
filteredPosts.map(post => (
<article key={post.slug} className="post-card">
<h2>
<Link to={`/blog/${post.slug}`}>{post.title}</Link>
</h2>
<div className="post-meta">
<time>{format(new Date(post.date), 'MMMM d, yyyy')}</time>
{post.tags && (
<div className="post-tags">
{post.tags.map(tag => (
<span key={tag} className="tag" onClick={() => setSelectedTag(tag)}>
{tag}
</span>
))}
</div>
)}
</div>
<p className="post-excerpt">{post.excerpt}</p>
<Link to={`/blog/${post.slug}`} className="read-more">
Read more
</Link>
</article>
))
)}
</div>
</div>
</div>
)
}
export default Blog

View file

@ -0,0 +1,21 @@
import { Link } from 'react-router-dom'
import './NotFound.css'
function NotFound() {
return (
<div className="not-found-page">
<div className="container">
<h1>404</h1>
<h2>Page Not Found</h2>
<p>
Oops! The page you're looking for doesn't exist or has been moved.
</p>
<Link to="/" className="back-home">
Go back to homepage
</Link>
</div>
</div>
)
}
export default NotFound

72
client/src/pages/Post.jsx Normal file
View file

@ -0,0 +1,72 @@
import { useState, useEffect } from 'react'
import { useParams, Link, useNavigate } from 'react-router-dom'
import axios from 'axios'
import { format } from 'date-fns'
import './Post.css'
function Post() {
const { slug } = useParams()
const navigate = useNavigate()
const [post, setPost] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchPost = async () => {
try {
const response = await axios.get(`http://localhost:5000/api/posts/${slug}`)
setPost(response.data)
setLoading(false)
} catch (err) {
console.error(`Error fetching post with slug ${slug}:`, err)
setError('Post not found or unable to load.')
setLoading(false)
}
}
fetchPost()
}, [slug])
if (loading) return <div className="loading">Loading post...</div>
if (error) return <div className="error">{error}</div>
if (!post) return <div className="error">Post not found</div>
return (
<div className="post-page">
<div className="container">
<article className="post">
<header className="post-header">
<h1>{post.title}</h1>
<div className="post-meta">
<time>{format(new Date(post.date), 'MMMM d, yyyy')}</time>
{post.tags && (
<div className="post-tags">
{post.tags.map(tag => (
<Link key={tag} to={`/blog?tag=${tag}`} className="tag">
{tag}
</Link>
))}
</div>
)}
</div>
</header>
<div
className="post-content"
dangerouslySetInnerHTML={{ __html: post.content }}
/>
<footer className="post-footer">
<div className="post-navigation">
<Link to="/blog" className="back-to-blog">
Back to Blog
</Link>
</div>
</footer>
</article>
</div>
</div>
)
}
export default Post

View file

@ -0,0 +1,104 @@
import { useState, useEffect } from 'react'
import axios from 'axios'
import './Projects.css'
function ProjectCard({ project }) {
return (
<div className={`project-card ${project.featured ? 'featured' : ''}`}>
{project.image && (
<div className="project-image">
<img src={`/projects/${project.image}`} alt={project.title} />
</div>
)}
<div className="project-content">
<h3>{project.title}</h3>
<p>{project.description}</p>
<div className="project-tech">
{project.technologies.map(tech => (
<span key={tech} className="tech-tag">{tech}</span>
))}
</div>
<div className="project-links">
{project.githubUrl && (
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
className="project-link github"
>
GitHub Repo
</a>
)}
{project.liveUrl && (
<a
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
className="project-link live"
>
Live Demo
</a>
)}
</div>
</div>
</div>
)
}
function Projects() {
const [projects, setProjects] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchProjects = async () => {
try {
const response = await axios.get('http://localhost:5000/api/projects')
setProjects(response.data)
setLoading(false)
} catch (err) {
console.error('Error fetching projects:', err)
setError('Failed to load projects. Please try again later.')
setLoading(false)
}
}
fetchProjects()
}, [])
// Sort projects to show featured ones first
const sortedProjects = [...projects].sort((a, b) => {
if (a.featured && !b.featured) return -1
if (!a.featured && b.featured) return 1
return 0
})
if (loading) return <div className="loading">Loading projects...</div>
if (error) return <div className="error">{error}</div>
return (
<div className="projects-page">
<div className="container">
<h1>Projects</h1>
<p className="projects-intro">
Here are some of the projects I've worked on. Check out my
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer">
GitHub profile
</a> for more.
</p>
<div className="projects-grid">
{sortedProjects.length === 0 ? (
<p>No projects found.</p>
) : (
sortedProjects.map(project => (
<ProjectCard key={project.id} project={project} />
))
)}
</div>
</div>
</div>
)
}
export default Projects