added menubar and experience section

This commit is contained in:
2025-01-04 11:59:57 +02:00
parent 2f61020cdc
commit bd6539d4b6
7 changed files with 327 additions and 21 deletions

View File

@ -11,7 +11,8 @@
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0" "react-icons": "^5.3.0",
"react-router-dom": "^7.1.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",
@ -1241,6 +1242,11 @@
"@babel/types": "^7.20.7" "@babel/types": "^7.20.7"
} }
}, },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -1843,6 +1849,14 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true "dev": true
}, },
"node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"engines": {
"node": ">=18"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3293,6 +3307,44 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-router": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz",
"integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz",
"integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==",
"dependencies": {
"react-router": "7.1.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -3425,6 +3477,11 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -3718,6 +3775,11 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "dev": true
}, },
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -13,7 +13,8 @@
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0" "react-icons": "^5.3.0",
"react-router-dom": "^7.1.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",

View File

@ -1,9 +1,11 @@
import { useState } from 'react' import { useState } from 'react'
import { BrowserRouter as Router } from 'react-router-dom';
import { Routes, Route } from 'react-router-dom';
import './App.css' import './App.css'
import Home from './pages/Home'
import Navbar from './components/Navbar' import Navbar from './components/Navbar'
import Introduction from './components/Introduction'
import Skills from './components/Skills'
import Footer from './components/Footer' import Footer from './components/Footer'
import Experience from './pages/Experience';
function App() { function App() {
const [darkMode, setDarkMode] = useState(true) const [darkMode, setDarkMode] = useState(true)
@ -14,12 +16,16 @@ function App() {
return ( return (
<> <>
<div className={darkMode ? "dark" : ""}> <div className={darkMode ? "dark" : ""}>
<main className="bg-white px-10 dark:bg-gray-900"> <main className="bg-amber-50 px-10 dark:bg-gray-900">
<section className="min-h-screen"> <section className="min-h-screen">
<Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode}/> <Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode}/>
<Introduction></Introduction> <Router>
<Skills></Skills> <Routes>
<Footer></Footer> <Route path='/' element={<Home/>} />
<Route path='/experience' element={<Experience/>} />
</Routes>
</Router>
<Footer />
</section> </section>
</main> </main>
</div> </div>

View File

@ -4,7 +4,7 @@ const Footer = () => {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
return ( return (
<footer className="mt-12 py-6 border-t border-gray-200 dark:border-gray-800"> <footer className="mt-16 py-6 border-t border-gray-200 dark:border-gray-800">
<div className="max-w-4xl mx-auto px-4"> <div className="max-w-4xl mx-auto px-4">
<div className="flex flex-col items-center gap-4 text-gray-600 dark:text-gray-400 text-sm"> <div className="flex flex-col items-center gap-4 text-gray-600 dark:text-gray-400 text-sm">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

View File

@ -1,35 +1,106 @@
import React from "react" import React, { useState, useRef } from 'react';
import { Sun, Moon, FileText } from "lucide-react" import { Menu, Sun, Moon, FileText, Mail, Check, Copy } from "lucide-react";
interface NavProps { interface NavProps {
darkMode: boolean darkMode: boolean;
toggleDarkMode: () => void toggleDarkMode: () => void;
} }
const Navbar: React.FC<NavProps> = ({toggleDarkMode, darkMode}) => { const Navbar: React.FC<NavProps> = ({toggleDarkMode, darkMode}) => {
const [copied, setCopied] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const email = "taqitahmid@gmail.com";
const menuRef = useRef<HTMLDivElement>(null);
const handleCopyEmail = async () => {
try {
await navigator.clipboard.writeText(email);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy email');
}
};
const menuItem = [
{title: 'Home', href: '/'},
{title: 'Experience', href: '/experience'},
{title: 'Projects', href: '/projects'},
{title: 'Interests', href: '/interests'},
]
return ( return (
<div className="w-full flex justify-center"> <div className="w-full flex justify-center">
<nav className="py-5 mb-12 flex justify-between dark:text-white w-full max-w-5xl px-4"> <nav className="py-5 mb-12 flex justify-between dark:text-white w-full max-w-5xl px-4">
<h1>taqitahmid@gmail.com</h1> <button
onClick={handleCopyEmail}
className="flex items-center space-x-2 hover:bg-gray-100 dark:hover:bg-gray-800 px-3 py-2 rounded-lg transition-colors duration-200 group relative"
>
<Mail size={20} className="text-blue-500" />
<span className="hidden sm:inline">{email}</span>
<span className="sm:hidden">Email</span>
{copied ? (
<Check size={16} className="text-green-500" />
) : (
<Copy size={16} className="opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
)}
<span className="pointer-events-none absolute -bottom-8 left-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100">
{copied ? 'Copied!' : 'Click to copy'}
</span>
</button>
<ul className="flex items-center"> <ul className="flex items-center">
<li className="transition ease-in-out delay-100 hover:scale-110 duration-300 cursor-pointer" <li className="transition ease-in-out delay-50 duration-100 cursor-pointer"
onClick={toggleDarkMode}> onClick={toggleDarkMode}>
<div className="bg-blue-900 hover:bg-blue-1000 dark:bg-blue-500 dark:hover:bg-blue-600 text-white p-2 rounded-lg ml-8 shadow-sm"> <div className="bg-blue-900 hover:bg-blue-1000 dark:bg-blue-500 dark:hover:bg-blue-600 text-white p-2 rounded-lg ml-8 shadow-sm group relative">
{darkMode ? <Sun size={24} /> : <Moon size={24} />} {darkMode ? <Sun size={24} /> : <Moon size={24} />}
<span className="pointer-events-none absolute -bottom-8 left-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100">
{darkMode ? "Light Mode": "Dark Mode"}
</span>
</div> </div>
</li> </li>
<li className="transition ease-in-out delay-150 hover:scale-110 duration-300"> <li className="transition ease-in-out delay-50 duration-100">
<div className="group relative">
<a className="bg-blue-900 hover:bg-blue-1000 dark:bg-blue-500 dark:hover:bg-blue-600 text-white p-2 rounded-lg ml-8 shadow-sm inline-flex" <a className="bg-blue-900 hover:bg-blue-1000 dark:bg-blue-500 dark:hover:bg-blue-600 text-white p-2 rounded-lg ml-8 shadow-sm inline-flex"
href="https://www.linkedin.com/in/taqi-tahmid/overlay/1635520467350/single-media-viewer/?profileId=ACoAACDU_GsBCgKtvw2bmzbVwTy2WixBG6-e3JM" href="https://www.linkedin.com/in/taqi-tahmid/details/featured/1735981754176/single-media-viewer/?profileId=ACoAACDU_GsBCgKtvw2bmzbVwTy2WixBG6-e3JM"
target="_blank" target="_blank"
rel="noreferrer"> rel="noreferrer">
<FileText size={24} /> <FileText size={24} />
</a> </a>
<span className="pointer-events-none absolute -bottom-8 left-1/2 -translate-x-1/2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100">
Resume
</span>
</div>
</li>
<li className="p-2 ml-5 cursor-pointer">
<div ref={menuRef} className="group relative">
<button onClick={() => setIsMenuOpen(!isMenuOpen)} className='focus:outline-none'>
<Menu size={24}/>
</button>
{/* Dropdown Menu */}
{isMenuOpen && (
<div className="absolute right-0 mt-3 w-36 rounded-md shadow-lg bg-amber-50 dark:bg-gray-800 ring-1 ring-black ring-opacity-5">
<div className="py-1" role="menu" aria-orientation="vertical">
{menuItem.map((item) => (
<a
key={item.title}
href={item.href}
className="block px-4 py-2 text-sm text-gray-00 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors duration-150"
role='menuitem'
>
{item.title}
</a>
))}
</div>
</div>
)}
</div>
</li> </li>
</ul> </ul>
</nav> </nav>
</div> </div>
) );
} };
export default Navbar export default Navbar;

View File

@ -0,0 +1,153 @@
import { Building2, Calendar, GraduationCap, MapPin, Microscope, Wrench } from 'lucide-react';
const Experience = () => {
const BoldStyle = "text-blue-900 dark:text-blue-400 font-semibold";
const experiences = [
{
title: "Experienced Developer (DevOps)",
company: "Ericsson",
location: "Jorvas, Finland",
period: "November-2022 - Present",
responsibilities: [
"Managing and optimizing Kubernetes clusters in production environments",
"Designing and implementing CI/CD pipelines for end to end product development flow using Jenkins",
"Automating infrastructure deployment using Terraform and Ansible",
"Develop and maintain monitoring solutions of various resources for greater observability and troubleshooting",
"Actively support development teams regarding product development flow and infrastructure issues",
"Develop and perform automated end-to-end product testing with Python, Robot Framework, Jenkins, Bash, etc.",
],
tools: "Kubernetes, Docker, KVM, Openstack, Ansible, Terraform, Prometheus, Grafana"
},
{
title: "Test Engineer",
company: "Nokia",
location: "Espoo, Finland",
period: "June-2021 - October-2022",
responsibilities: [
"Develop and maintain Cloud RAN E2E test setup for vCU and vDU application testing on top of RedHat Openshift",
"Develop automation and CI/CD flow for Cloud RAN testing using Python, Robot Framework, Bash, Jenkins etc.",
"Develop and perform automated testing to validate the functionality of Nokia Cloud RAN base stations",
"Integrate new hardware and software into the test setup",
"Perform hands on debugging and log analysis to nd root cause and solve any software or hardware issues",
],
tools: "Keysight Nemo Outdoor, Nemo Analyze, Qualcomm PCAT, QCAT, QXDM"
},
{
title: "Testing and Prototyping Intern",
company: "GE Healthcare",
location: "Helsinki, Finland",
period: "Jan-2019 - May-2021",
responsibilities: [
"Planning, writing, and performing manual and automated tests of different prototype wireless medical devices",
"Designing driver and PCB circuits in Altium Designer to test the performance of the Digital Sensor Interface",
"Ensuring the PCB componets used in the devices are EU RoHS and REACH compliant",
],
tools: "LTSpice, Altium Designer, HP-ALM, Vector Network Analyzer, Spectrum Analyzer, Climate Chamber"
}
];
const education = [
{
degree: "Master's in Wireless Communication & RF Systems",
institution: "Tampere University",
location: "Tampere, Finland",
period: "2018 - 2020",
thesis: "5G Reference Signals and their Possibility to be for 5G Based Positioning"
},
{
degree: "Bachelor's in Electrical & Electronic Engineering",
institution: "Khulna University of Engineering & Technology",
location: "Khulna, Bangladesh",
period: "2013 - 2017",
thesis: "Density-based smart traffic control system using Canny edge detection technique using Digital Image Processing"
}
];
return (
<div className="p-4 max-w-4xl mx-auto">
<h2 className="text-3xl text-blue-900 dark:text-blue-400 py-4 font-medium font-burtons text-center">
Experience
</h2>
<div className="space-y-8">
{experiences.map((exp, index) => (
<div key={index} className="border-l-4 border-blue-900 dark:border-blue-400 pl-4 space-y-2">
<div className="flex items-center gap-2">
<Building2 className="text-blue-900 dark:text-blue-400" size={24} />
<h3 className={`text-xl ${BoldStyle}`}>{exp.title}</h3>
</div>
<div className="flex flex-col md:flex-row md:items-center gap-2 md:gap-6 text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1">
<Building2 size={16} />
<span>{exp.company}</span>
</div>
<div className="flex items-center gap-1">
<MapPin size={16} />
<span>{exp.location}</span>
</div>
<div className="flex items-center gap-1">
<Calendar size={16} />
<span>{exp.period}</span>
</div>
</div>
<div className="text-gray-800 dark:text-gray-300 space-y-1">
{exp.responsibilities.map((resp, idx) => (
<p key={idx} className="flex items-start">
<span className="mr-2 mt-2 ml-4"></span>
<span>{resp}</span>
</p>
))}
</div>
<div className="text-gray-800 dark:text-gray-300 space-y-1 flex items-start">
<div className='flex items-center gap-2'>
<Wrench size={20}/>
<span>{exp.tools}</span>
</div>
</div>
</div>
))}
</div>
<h2 className="text-3xl text-blue-900 dark:text-blue-400 py-4 mt-8 font-medium font-burtons text-center">
Education
</h2>
<div className="space-y-6">
{education.map((edu, index) => (
<div key={index} className="border-l-4 border-blue-900 dark:border-blue-400 pl-4 space-y-2">
<div className="flex items-center gap-2">
<GraduationCap className="text-blue-900 dark:text-blue-400" size={24} />
<h3 className={`text-xl ${BoldStyle}`}>{edu.degree}</h3>
</div>
<div className="flex flex-col md:flex-row md:items-center gap-2 md:gap-6 text-gray-600 dark:text-gray-400">
<div className="flex items-center gap-1">
<Building2 size={16} />
<span>{edu.institution}</span>
</div>
<div className="flex items-center gap-1">
<MapPin size={16} />
<span>{edu.location}</span>
</div>
<div className="flex items-center gap-1">
<Calendar size={16} />
<span>{edu.period}</span>
</div>
</div>
<div className="text-gray-600 dark:text-gray-400 space-y-1 flex items-start">
<div className='flex items-center gap-1'>
<Microscope />
<span>{edu.thesis}</span>
</div>
</div>
</div>
))}
</div>
</div>
);
};
export default Experience;

View File

@ -0,0 +1,13 @@
import Introduction from '../components/Introduction'
import Skills from '../components/Skills'
function Home() {
return (
<>
<Introduction />
<Skills />
</>
)
}
export default Home