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:
copilot-swe-agent[bot]
2026-08-22 07:41:32 +00:00
committed by GitHub
parent 6717cbb30e
commit 64d611d9c4
5 changed files with 142 additions and 70 deletions

58
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@mui/material": "^7.3.7", "@mui/material": "^7.3.7",
"react": "^19.2.3", "react": "^19.2.3",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
"react-router-dom": "^7.18.2",
"swiper": "^12.0.3" "swiper": "^12.0.3"
}, },
"devDependencies": { "devDependencies": {
@@ -14996,6 +14997,57 @@
"node": ">=0.10.0" "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": { "node_modules/react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@@ -15950,6 +16002,12 @@
"node": ">= 0.8.0" "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": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",

View File

@@ -9,6 +9,7 @@
"@mui/material": "^7.3.7", "@mui/material": "^7.3.7",
"react": "^19.2.3", "react": "^19.2.3",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
"react-router-dom": "^7.18.2",
"swiper": "^12.0.3" "swiper": "^12.0.3"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,4 +1,5 @@
import { createContext, useEffect, useMemo, useState } from 'react'; import { createContext, useEffect, useMemo, useState } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from '@mui/material/styles'; import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import Menu from './components/Menu'; import Menu from './components/Menu';
@@ -57,7 +58,9 @@ function App() {
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<CssBaseline /> <CssBaseline />
<WidthContext.Provider value={width}> <WidthContext.Provider value={width}>
<Menu /> <BrowserRouter basename={process.env.PUBLIC_URL || '/'}>
<Menu />
</BrowserRouter>
</WidthContext.Provider> </WidthContext.Provider>
</ThemeProvider> </ThemeProvider>
</ColorModeContext.Provider> </ColorModeContext.Provider>

View File

@@ -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' import React, { Fragment } from 'react'
export const experiencesData = [ 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 = () => { const Career = () => {
return ( return (
<> <>
<Typography p={2} align="left" variant="h5"> <TimelineSection title="Professional" entries={experiencesData.filter(exp => exp.type === 'prof')} />
Professional <TimelineSection title="Education" entries={experiencesData.filter(exp => exp.type === 'edu')} />
</Typography> <TimelineSection title="Volunteering" entries={experiencesData.filter(exp => exp.type === 'extra')} />
{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>
)}
</> </>
) )
} }

View File

@@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { Route, Routes, Navigate, useLocation, useNavigate } from 'react-router-dom';
import AppBar from '@mui/material/AppBar'; import AppBar from '@mui/material/AppBar';
import Avatar from '@mui/material/Avatar'; import Avatar from '@mui/material/Avatar';
@@ -38,19 +39,19 @@ const drawerWidth = 240;
const menuSections = [ 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 /> 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 /> 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 /> 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 /> extLink: false, component: <UnderConstruction />
}, },
{ {
@@ -63,11 +64,17 @@ const menuSections = [
}, },
]; ];
const internalSections = menuSections.filter(menuItem => menuItem.extLink === false);
function Menu(props) { function Menu(props) {
const [mobileOpen, setMobileOpen] = React.useState(false); const [mobileOpen, setMobileOpen] = React.useState(false);
const [isClosing, setIsClosing] = React.useState(false); const [isClosing, setIsClosing] = React.useState(false);
const [activeSection, setActiveSection] = React.useState(menuSections[0]);
const { mode, toggleMode } = React.useContext(ColorModeContext); 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(() => { React.useEffect(() => {
document.title = `Kaushik | ${activeSection.display_name}`; document.title = `Kaushik | ${activeSection.display_name}`;
@@ -91,7 +98,7 @@ function Menu(props) {
const handleDrawerSelect = (selectedSectionKey) => { const handleDrawerSelect = (selectedSectionKey) => {
handleDrawerClose(); handleDrawerClose();
const menuSection = menuSections.filter((menuItem) => menuItem.key === selectedSectionKey)[0]; const menuSection = menuSections.filter((menuItem) => menuItem.key === selectedSectionKey)[0];
setActiveSection(menuSection); navigate(menuSection.path);
} }
const drawer = ( const drawer = (
@@ -203,7 +210,12 @@ function Menu(props) {
sx={{ flexGrow: 1, padding: 1, width: { sm: `calc(100% - ${drawerWidth}px)` } }} sx={{ flexGrow: 1, padding: 1, width: { sm: `calc(100% - ${drawerWidth}px)` } }}
> >
<Toolbar /> <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>
</Box> </Box>
); );