added menubar and experience section
This commit is contained in:
64
frontend/package-lock.json
generated
64
frontend/package-lock.json
generated
@ -11,7 +11,8 @@
|
||||
"lucide-react": "^0.469.0",
|
||||
"react": "^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": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
@ -1241,6 +1242,11 @@
|
||||
"@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": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
@ -1843,6 +1849,14 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"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": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@ -3293,6 +3307,44 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -3425,6 +3477,11 @@
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@ -3718,6 +3775,11 @@
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"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": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
"lucide-react": "^0.469.0",
|
||||
"react": "^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": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import './App.css'
|
||||
import Home from './pages/Home'
|
||||
import Navbar from './components/Navbar'
|
||||
import Introduction from './components/Introduction'
|
||||
import Skills from './components/Skills'
|
||||
import Footer from './components/Footer'
|
||||
import Experience from './pages/Experience';
|
||||
|
||||
function App() {
|
||||
const [darkMode, setDarkMode] = useState(true)
|
||||
@ -14,12 +16,16 @@ function App() {
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode}/>
|
||||
<Introduction></Introduction>
|
||||
<Skills></Skills>
|
||||
<Footer></Footer>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path='/' element={<Home/>} />
|
||||
<Route path='/experience' element={<Experience/>} />
|
||||
</Routes>
|
||||
</Router>
|
||||
<Footer />
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@ const Footer = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
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="flex flex-col items-center gap-4 text-gray-600 dark:text-gray-400 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@ -1,35 +1,106 @@
|
||||
import React from "react"
|
||||
import { Sun, Moon, FileText } from "lucide-react"
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Menu, Sun, Moon, FileText, Mail, Check, Copy } from "lucide-react";
|
||||
|
||||
interface NavProps {
|
||||
darkMode: boolean
|
||||
toggleDarkMode: () => void
|
||||
darkMode: boolean;
|
||||
toggleDarkMode: () => void;
|
||||
}
|
||||
|
||||
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 (
|
||||
<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">
|
||||
<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">
|
||||
<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}>
|
||||
<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} />}
|
||||
<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>
|
||||
</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"
|
||||
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"
|
||||
rel="noreferrer">
|
||||
<FileText size={24} />
|
||||
</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>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar
|
||||
export default Navbar;
|
||||
153
frontend/src/pages/Experience.tsx
Normal file
153
frontend/src/pages/Experience.tsx
Normal 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;
|
||||
13
frontend/src/pages/Home.tsx
Normal file
13
frontend/src/pages/Home.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import Introduction from '../components/Introduction'
|
||||
import Skills from '../components/Skills'
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<>
|
||||
<Introduction />
|
||||
<Skills />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
Reference in New Issue
Block a user