mirror of
https://github.com/20kaushik02/portfolio-website.git
synced 2026-09-10 14:04:09 +00:00
Add URL-based deep linking and a vertical timeline for Career
Co-authored-by: 20kaushik02 <59662438+20kaushik02@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
6717cbb30e
commit
64d611d9c4
58
package-lock.json
generated
58
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"@mui/material": "^7.3.7",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-router-dom": "^7.18.2",
|
||||
"swiper": "^12.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -14996,6 +14997,57 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
|
||||
"integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz",
|
||||
"integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router/node_modules/cookie": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@@ -15950,6 +16002,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"@mui/material": "^7.3.7",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-router-dom": "^7.18.2",
|
||||
"swiper": "^12.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Menu from './components/Menu';
|
||||
@@ -57,7 +58,9 @@ function App() {
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<WidthContext.Provider value={width}>
|
||||
<BrowserRouter basename={process.env.PUBLIC_URL || '/'}>
|
||||
<Menu />
|
||||
</BrowserRouter>
|
||||
</WidthContext.Provider>
|
||||
</ThemeProvider>
|
||||
</ColorModeContext.Provider>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Divider, List, ListItem, ListItemText, Typography } from '@mui/material'
|
||||
import { Box, List, ListItem, ListItemText, Typography, useTheme } from '@mui/material'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
export const experiencesData = [
|
||||
@@ -62,68 +62,66 @@ export const experiencesData = [
|
||||
},
|
||||
];
|
||||
|
||||
// Simple, dependency-free vertical timeline: a dot + connecting line down the
|
||||
// left edge, with each entry's content to the right.
|
||||
const TimelineEntry = ({ exp, isLast }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'stretch' }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: 24, flexShrink: 0 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 14,
|
||||
height: 14,
|
||||
borderRadius: '50%',
|
||||
bgcolor: exp.endDate === '' ? theme.palette.secondary.main : theme.palette.primary.main,
|
||||
border: `2px solid ${theme.palette.background.paper}`,
|
||||
boxShadow: `0 0 0 2px ${theme.palette.primary.main}`,
|
||||
mt: '6px',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
{!isLast && (
|
||||
<Box sx={{ flexGrow: 1, width: '2px', bgcolor: theme.palette.divider, my: '2px' }} />
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ flexGrow: 1, pb: isLast ? 1 : 3 }}>
|
||||
<Typography px={2} align="left" fontSize={16}>
|
||||
<b>{exp.designation}, {exp.location}</b>, <i>{exp.startDate} - {exp.endDate || 'Present'}</i>
|
||||
</Typography>
|
||||
<Typography component="div" p={2} align="left">
|
||||
<List disablePadding sx={{ pl: 2, listStyleType: 'disc' }}>
|
||||
{exp.desc.map((line, index) =>
|
||||
<ListItem disablePadding key={index} sx={{ display: 'list-item' }}>
|
||||
<ListItemText slotProps={{ primary: { fontSize: 13 } }} primary={line} />
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const TimelineSection = ({ title, entries }) => (
|
||||
<Fragment>
|
||||
<Typography p={2} align="left" variant="h5">
|
||||
{title}
|
||||
</Typography>
|
||||
<Box>
|
||||
{entries.map((exp, idx) => (
|
||||
<TimelineEntry key={exp.key} exp={exp} isLast={idx === entries.length - 1} />
|
||||
))}
|
||||
</Box>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const Career = () => {
|
||||
return (
|
||||
<>
|
||||
<Typography p={2} align="left" variant="h5">
|
||||
Professional
|
||||
</Typography>
|
||||
{experiencesData.filter(exp => exp.type === 'prof').map((exp, idx) =>
|
||||
<Fragment key={idx}>
|
||||
<Typography px={2} align="left" fontSize={16}>
|
||||
<b>{exp.designation}, {exp.location}</b>, <i>{exp.startDate} - {exp.endDate}</i>
|
||||
</Typography>
|
||||
<Typography component="div" p={2} align="left">
|
||||
<List disablePadding sx={{ pl: 2, listStyleType: 'disc' }}>
|
||||
{exp.desc.map((line, index) =>
|
||||
<ListItem disablePadding key={index} sx={{ display: 'list-item' }}>
|
||||
<ListItemText slotProps={{ primary: { fontSize: 13 } }} primary={line} />
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Typography>
|
||||
</Fragment>
|
||||
)}
|
||||
<Divider />
|
||||
<Typography p={2} align="left" variant="h5">
|
||||
Education
|
||||
</Typography>
|
||||
{experiencesData.filter(exp => exp.type === 'edu').map((exp, idx) =>
|
||||
<Fragment key={idx}>
|
||||
<Typography px={2} align="left" fontSize={16}>
|
||||
<b>{exp.designation}, {exp.location}</b>, <i>{exp.startDate} - {exp.endDate}</i>
|
||||
</Typography>
|
||||
<Typography component="div" p={2} align="left">
|
||||
<List disablePadding sx={{ pl: 2, listStyleType: 'disc' }}>
|
||||
{exp.desc.map((line, index) =>
|
||||
<ListItem disablePadding key={index} sx={{ display: 'list-item' }}>
|
||||
<ListItemText slotProps={{ primary: { fontSize: 13 } }} primary={line} />
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Typography>
|
||||
</Fragment>
|
||||
)}
|
||||
<Divider />
|
||||
<Typography p={2} align="left" variant="h5">
|
||||
Volunteering
|
||||
</Typography>
|
||||
{experiencesData.filter(exp => exp.type === 'extra').map((exp, idx) =>
|
||||
<Fragment key={idx}>
|
||||
<Typography px={2} align="left" fontSize={16}>
|
||||
<b>{exp.designation}, {exp.location}</b>, <i>{exp.startDate} - {exp.endDate}</i>
|
||||
</Typography>
|
||||
<Typography component="div" p={2} align="left">
|
||||
<List disablePadding sx={{ pl: 2, listStyleType: 'disc' }}>
|
||||
{exp.desc.map((line, index) =>
|
||||
<ListItem disablePadding key={index} sx={{ display: 'list-item' }}>
|
||||
<ListItemText slotProps={{ primary: { fontSize: 13 } }} primary={line} />
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Typography>
|
||||
</Fragment>
|
||||
)}
|
||||
<TimelineSection title="Professional" entries={experiencesData.filter(exp => exp.type === 'prof')} />
|
||||
<TimelineSection title="Education" entries={experiencesData.filter(exp => exp.type === 'edu')} />
|
||||
<TimelineSection title="Volunteering" entries={experiencesData.filter(exp => exp.type === 'extra')} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Route, Routes, Navigate, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
@@ -38,19 +39,19 @@ const drawerWidth = 240;
|
||||
|
||||
const menuSections = [
|
||||
{
|
||||
key: 'landing', display_name: 'About', appbar_text: 'Hello There!', display_icon: <LandingIcon />,
|
||||
key: 'landing', path: '/', display_name: 'About', appbar_text: 'Hello There!', display_icon: <LandingIcon />,
|
||||
extLink: false, component: <Landing />
|
||||
},
|
||||
{
|
||||
key: 'exp', display_name: 'Work', appbar_text: 'Journey', display_icon: <CareerIcon />,
|
||||
key: 'exp', path: '/work', display_name: 'Work', appbar_text: 'Journey', display_icon: <CareerIcon />,
|
||||
extLink: false, component: <Career />
|
||||
},
|
||||
{
|
||||
key: 'proj', display_name: 'Stuff', appbar_text: 'Workbench', display_icon: <ProjectsIcon />,
|
||||
key: 'proj', path: '/stuff', display_name: 'Stuff', appbar_text: 'Workbench', display_icon: <ProjectsIcon />,
|
||||
extLink: false, component: <Projects />
|
||||
},
|
||||
{
|
||||
key: 'ints', display_name: 'Interests', appbar_text: 'My Interests', display_icon: <InterestsIcon />,
|
||||
key: 'ints', path: '/interests', display_name: 'Interests', appbar_text: 'My Interests', display_icon: <InterestsIcon />,
|
||||
extLink: false, component: <UnderConstruction />
|
||||
},
|
||||
{
|
||||
@@ -63,11 +64,17 @@ const menuSections = [
|
||||
},
|
||||
];
|
||||
|
||||
const internalSections = menuSections.filter(menuItem => menuItem.extLink === false);
|
||||
|
||||
function Menu(props) {
|
||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
||||
const [isClosing, setIsClosing] = React.useState(false);
|
||||
const [activeSection, setActiveSection] = React.useState(menuSections[0]);
|
||||
const { mode, toggleMode } = React.useContext(ColorModeContext);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const activeSection = internalSections.find((menuItem) => menuItem.path === location.pathname)
|
||||
|| internalSections[0];
|
||||
|
||||
React.useEffect(() => {
|
||||
document.title = `Kaushik | ${activeSection.display_name}`;
|
||||
@@ -91,7 +98,7 @@ function Menu(props) {
|
||||
const handleDrawerSelect = (selectedSectionKey) => {
|
||||
handleDrawerClose();
|
||||
const menuSection = menuSections.filter((menuItem) => menuItem.key === selectedSectionKey)[0];
|
||||
setActiveSection(menuSection);
|
||||
navigate(menuSection.path);
|
||||
}
|
||||
|
||||
const drawer = (
|
||||
@@ -203,7 +210,12 @@ function Menu(props) {
|
||||
sx={{ flexGrow: 1, padding: 1, width: { sm: `calc(100% - ${drawerWidth}px)` } }}
|
||||
>
|
||||
<Toolbar />
|
||||
{activeSection.component}
|
||||
<Routes>
|
||||
{internalSections.map((menuItem) => (
|
||||
<Route key={menuItem.key} path={menuItem.path} element={menuItem.component} />
|
||||
))}
|
||||
<Route path="*" element={<Navigate to={internalSections[0].path} replace />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user