added routing

This commit is contained in:
2ManyProjects 2025-04-18 21:42:19 -05:00
parent 117603977a
commit 10cdb7bb5f
8 changed files with 489 additions and 82 deletions

View file

@ -7,6 +7,8 @@
"express": "^4.21.2", "express": "^4.21.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router": "^7.5.1",
"react-router-dom": "^7.5.1",
"react-scripts": "5.0.1" "react-scripts": "5.0.1"
}, },
"scripts": { "scripts": {

View file

@ -1,80 +1,23 @@
import React, { useState } from 'react'; import ReactDOM from "react-dom/client";
import './App.css'; import { BrowserRouter, Routes, Route } from "react-router-dom";
import Header from './components/Header'; import Layout from "./pages/Layout";
import ProjectCard from './components/ProjectCard'; import Dashboard from "./pages/Dashboard";
import ProjectDetails from './components/ProjectDetails';
import forgejoLogo from './assets/forgejo.svg'
import githubLogo from './assets/github-mark-white.svg'
import LinkedinLogo from './assets/linkedIn.svg'
import projects from './data/projects'; import projects from './data/projects';
function App() { export default function App() {
const [selectedProjectId, setSelectedProjectId] = useState(null);
const handleProjectClick = (projectId) => {
setSelectedProjectId(projectId);
window.scrollTo(0, 0);
};
const handleBackClick = () => {
setSelectedProjectId(null);
};
const selectedProject = selectedProjectId ? projects.find(p => p.id === selectedProjectId) : null;
return ( return (
<div className="app"> <BrowserRouter>
<Header <Routes>
onBackClick={handleBackClick} <Route path="/" element={<Layout />}>
showBackButton={selectedProjectId !== null} <Route index element={<Dashboard projectId={null}/>} />
/> {projects.map(item => {
return(<Route path={item.id} element={<Dashboard projectId={item.id}/>} />)
<main className="app-content"> })}
{selectedProject ? ( </Route>
<ProjectDetails project={selectedProject} /> </Routes>
) : ( </BrowserRouter>
<div className="projects-list">
<div className="intro">
<h2>Freelance Development Experience</h2>
<p>
Below is a portfolio of my freelance projects.
Each project represents solutions to complex problems across various domains. This site is hosted on my rural farm in Northern, Ontario, Canada. If the site is down its most likely due to weather conditions like a blizzard if its winter. Relavent Links in the footer unfortunately a significant amount of my professional work is locked in private git, code commit and gitlab repos. If you've been given this site please feel free to contact me for more details.
</p>
<p>
This site is a minimal React site, hosted on my own git server with a custom CI/CD pipline hooked up to pm2 for loadbalancing across a proxmox cluster (mainly for other stuff but it hosts this site too). The images are hosted via Google drive so if too many people hit the site the images will stop loading. Thanks for your understanding.
</p>
</div>
{projects.map(project => (
<ProjectCard
key={project.id}
project={project}
onClick={handleProjectClick}
/>
))}
</div>
)}
</main>
<footer className="app-footer">
<div className="footer-content">
<p>&copy; {new Date().getFullYear()} Comet Technologies | All Rights Reserved</p>
<div className="social-links">
<a href="https://github.com/2ManyProjects" target="_blank" rel="noopener noreferrer" className="social-icon">
<img src={githubLogo} alt="Github" width="24" height="24" />
</a>
<a href="https://git.2many.ca/root?tab=activity" target="_blank" rel="noopener noreferrer" className="social-icon">
<img src={forgejoLogo} alt="Forgejo" width="24" height="24" />
</a>
<a href="https://www.linkedin.com/in/shaiv-kamat-a39ba514a/" target="_blank" rel="noopener noreferrer" className="social-icon">
<img src={LinkedinLogo} alt="Linkedin" width="24" height="24" />
</a>
</div>
</div>
</footer>
</div>
); );
} }
export default App; const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

View file

@ -1,13 +1,16 @@
import React from 'react'; import React from 'react';
import { Link } from "react-router-dom";
const Header = ({ onBackClick, showBackButton }) => { const Header = ({ onBackClick, showBackButton }) => {
return ( return (
<header className="app-header"> <header className="app-header">
<div className="header-content"> <div className="header-content">
{showBackButton && ( {showBackButton && (
<button className="back-button" onClick={onBackClick}> <Link to={"/"} style={{textDecorationColor: "transparent"}}>
<button className="back-button">
&larr; Back to Projects &larr; Back to Projects
</button> </button>
</Link>
)} )}
<h1>Comet Technologies</h1> <h1>Comet Technologies</h1>
<p className="tagline">You need it, I'll build it</p> <p className="tagline">You need it, I'll build it</p>

View file

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import RenderChips from './RenderChips'; import RenderChips from './RenderChips';
import { Link } from "react-router-dom";
const ProjectCard = ({ project, onClick }) => { const ProjectCard = ({ project, onClick }) => {
@ -8,7 +9,7 @@ const ProjectCard = ({ project, onClick }) => {
return ( return (
<div className="project-card" onClick={() => onClick(project.id)}> <Link className="project-card" to={project.id} style={{textDecorationColor: "transparent"}}>
<h3>{project.title}</h3> <h3>{project.title}</h3>
{project.langs && <RenderChips langs={project.langs} type='lang'/>} {project.langs && <RenderChips langs={project.langs} type='lang'/>}
@ -22,7 +23,7 @@ const ProjectCard = ({ project, onClick }) => {
</p> </p>
<div className="view-details">View Details</div> <div className="view-details">View Details</div>
</div> </Link>
); );
}; };

View file

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import './App.css';
import App from './App'; import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root')); const root = ReactDOM.createRoot(document.getElementById('root'));

344
src/pages/Dashboard.css Normal file
View file

@ -0,0 +1,344 @@
/* Base Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f8f9fa;
}
.app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header Styles */
.app-header {
background-color: #1a1a2e;
color: white;
padding: 2rem 1rem;
text-align: center;
}
.header-content {
max-width: 1200px;
margin: 0 auto;
position: relative;
}
.back-button {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: white;
font-size: 1rem;
cursor: pointer;
padding: 0.5rem;
}
.back-button:hover {
text-decoration: underline;
}
.app-header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
}
.tagline {
font-size: 1.1rem;
font-weight: 300;
}
/* Main Content */
.app-content {
flex: 1;
padding: 2rem 1rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
/* Project List */
.projects-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 2rem;
}
.intro {
grid-column: 1 / -1;
margin-bottom: 1rem;
}
.intro h2 {
font-size: 2rem;
margin-bottom: 1rem;
}
.intro p {
font-size: 1.1rem;
color: #555;
}
/* Project Card */
.project-card {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
}
.project-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.project-card h3 {
margin-bottom: 1rem;
color: #1a1a2e;
}
.project-summary {
margin-bottom: 1.5rem;
color: #555;
}
.view-details {
text-align: right;
color: #16213e;
font-weight: 500;
}
/* Project Details */
.project-details {
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.project-details h2 {
color: #1a1a2e;
margin-bottom: 1.5rem;
font-size: 1.8rem;
}
.project-carousel {
margin-bottom: 2rem;
}
.project-description p {
margin-bottom: 1rem;
font-size: 1.05rem;
}
/* Carousel Styles */
.carousel {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
background-color: #f1f1f1;
border-radius: 4px;
overflow: hidden;
}
.carousel-image-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
height: 400px;
}
.carousel-image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.carousel-button {
background-color: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-size: 1.5rem;
padding: 0.5rem 1rem;
cursor: pointer;
height: 100%;
z-index: 2;
}
.carousel-button:hover {
background-color: rgba(0, 0, 0, 0.7);
}
.carousel-counter {
position: absolute;
bottom: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}
.carousel-loading, .carousel-empty {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
background-color: #f1f1f1;
color: #555;
font-style: italic;
}
/* Footer */
.app-footer {
background-color: #1a1a2e;
color: white;
text-align: center;
padding: 1.5rem;
margin-top: 2rem;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
.social-links {
display: flex;
gap: 1.5rem;
}
.social-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.5);
color: white;
transition: all 0.3s ease;
padding: 8px;
}
.social-icon:hover {
color: #4361ee;
border-color: #4361ee;
transform: translateY(-3px);
background-color: rgba(255, 255, 255, 0.1);
}
.social-icon svg {
width: 20px;
height: 20px;
}
/* Chip styling */
.chips-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 2px 0;
}
.chip {
display: inline-block;
padding: 1px 12px;
border-radius: 16px;
font-size: 0.6rem;
font-weight: 1000;
}
.chip-large {
display: inline-block;
padding: 1px 16px;
border-radius: 16px;
font-size: 1rem;
font-weight: 750;
}
.lang-chip {
background-color: #e0f2ff;
color: #0366d6;
border: 1px solid #79b8ff;
}
.software-chip {
background-color: #f3e8ff;
color: #5a32a3;
border: 1px solid #d1bef2;
}
.project-card {
display: flex;
flex-direction: column;
}
.view-details {
margin-top: auto;
text-align: right;
}
/* Responsive styles */
@media (max-width: 768px) {
.header-content {
padding: 0 1rem;
}
.back-button {
position: static;
transform: none;
margin-bottom: 1rem;
display: inline-block;
}
.project-details {
padding: 1.5rem;
}
.carousel-image-container {
height: 300px;
}
}
@media (max-width: 480px) {
.app-header h1 {
font-size: 2rem;
}
.carousel-image-container {
height: 200px;
}
}
@media (max-width: 768px) {
.footer-content {
flex-direction: column;
gap: 1rem;
}
.social-links {
margin-bottom: 1rem;
}
}

78
src/pages/Dashboard.js Normal file
View file

@ -0,0 +1,78 @@
import React, { useState, useEffect } from 'react';
import './Dashboard.css';
import Header from '../components/Header';
import ProjectCard from '../components/ProjectCard';
import ProjectDetails from '../components/ProjectDetails';
import forgejoLogo from '../assets/forgejo.svg'
import githubLogo from '../assets/github-mark-white.svg'
import LinkedinLogo from '../assets/linkedIn.svg'
import projects from '../data/projects';
function Dashboard({projectId}) {
const [selectedProjectId, setSelectedProjectId] = useState(projectId);
useEffect(() => {setSelectedProjectId(projectId)}, [projectId])
const handleProjectClick = (projectId) => {
setSelectedProjectId(projectId);
window.scrollTo(0, 0);
};
const selectedProject = selectedProjectId ? projects.find(p => p.id === selectedProjectId) : null;
return (
<div className="app">
<Header
showBackButton={selectedProjectId !== null}
/>
<main className="app-content">
{selectedProject ? (
<ProjectDetails project={selectedProject} />
) : (
<div className="projects-list">
<div className="intro">
<h2>Freelance Development Experience</h2>
<p>
Below is a portfolio of my freelance projects.
Each project represents solutions to complex problems across various domains. This site is hosted on my rural farm in Northern, Ontario, Canada. If the site is down its most likely due to weather conditions like a blizzard if its winter. Relavent Links in the footer unfortunately a significant amount of my professional work is locked in private git, code commit and gitlab repos. If you've been given this site please feel free to contact me for more details.
</p>
<p>
This site is a minimal React site, hosted on my own git server with a custom CI/CD pipline hooked up to pm2 for loadbalancing across a proxmox cluster (mainly for other stuff but it hosts this site too). The images are hosted via Google drive so if too many people hit the site the images will stop loading. Thanks for your understanding.
</p>
</div>
{projects.map(project => (
<ProjectCard
key={project.id}
project={project}
onClick={handleProjectClick}
/>
))}
</div>
)}
</main>
<footer className="app-footer">
<div className="footer-content">
<p>&copy; {new Date().getFullYear()} Comet Technologies | All Rights Reserved</p>
<div className="social-links">
<a href="https://github.com/2ManyProjects" target="_blank" rel="noopener noreferrer" className="social-icon">
<img src={githubLogo} alt="Github" width="24" height="24" />
</a>
<a href="https://git.2many.ca/root?tab=activity" target="_blank" rel="noopener noreferrer" className="social-icon">
<img src={forgejoLogo} alt="Forgejo" width="24" height="24" />
</a>
<a href="https://www.linkedin.com/in/shaiv-kamat-a39ba514a/" target="_blank" rel="noopener noreferrer" className="social-icon">
<img src={LinkedinLogo} alt="Linkedin" width="24" height="24" />
</a>
</div>
</div>
</footer>
</div>
);
}
export default Dashboard;

37
src/pages/Layout.js Normal file
View file

@ -0,0 +1,37 @@
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<>
<Outlet />
</>
)
};
export default Layout;
/*
import ReactDOM from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Layout from "./pages/Layout";
import Dashboard from "./pages/Dashboard";
import projects from './data/projects';
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Dashboard projectId={null}/>} />
{projects.map(item => {
return(<Route path={item.id} element={<Dashboard projectId={item.id}/>} />)
})}
</Route>
</Routes>
</BrowserRouter>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
*/