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",
"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",

View File

@@ -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": {
@@ -41,4 +42,4 @@
"last 1 safari version"
]
}
}
}

View File

@@ -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}>
<Menu />
<BrowserRouter basename={process.env.PUBLIC_URL || '/'}>
<Menu />
</BrowserRouter>
</WidthContext.Provider>
</ThemeProvider>
</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'
export const experiencesData = [
@@ -62,70 +62,68 @@ 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')} />
</>
)
}
export default Career;
export default Career;

View File

@@ -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>
);