diff --git a/src/components/atoms/IconButton/IconButton.ts b/src/components/atoms/IconButton/IconButton.ts deleted file mode 100644 index 982c1c3..0000000 --- a/src/components/atoms/IconButton/IconButton.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { IconButton as MuiIconButton, styled } from '@mui/material' - -const IconButton = styled(MuiIconButton)(({ theme }) => ({ - ':hover': { - backgroundColor: 'transparent', - color: theme.palette.primary.main, - }, -})) -export default IconButton diff --git a/src/components/atoms/IconButton/index.ts b/src/components/atoms/IconButton/index.ts deleted file mode 100644 index 9de6d2c..0000000 --- a/src/components/atoms/IconButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './IconButton' diff --git a/src/components/atoms/IconLinks/IconLinks.test.tsx b/src/components/atoms/IconLinks/IconLinks.test.tsx index 5bfb814..efef427 100644 --- a/src/components/atoms/IconLinks/IconLinks.test.tsx +++ b/src/components/atoms/IconLinks/IconLinks.test.tsx @@ -1,9 +1,8 @@ // IconLinks.test.tsx import { render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import * as IconLinks from '.' +import * as IconLinks from './index.js' const labels: { [key: string]: string } = { DescriptionIconLink: 'Doc-button', @@ -18,8 +17,6 @@ const labels: { [key: string]: string } = { vi.stubGlobal('open', vi.fn()) describe('IconLinks', () => { - const open = vi.spyOn(window, 'open') - const defaultProps = { href: 'https://test.com', fontSize: 'large' as const, @@ -28,20 +25,10 @@ describe('IconLinks', () => { describe.each(Object.entries(IconLinks))('%s', (name, Icon) => { test('renders correctly and without crashing', async () => { render() - await userEvent.click(screen.getByLabelText(labels[name])) - expect(open).toHaveBeenCalledTimes(1) - expect(open).toHaveBeenLastCalledWith(defaultProps.href, '_blank') - }) - - test('stops propagation when the icon button is clicked', async () => { - const onClick = vi.fn() - render( -
- -
- ) - await userEvent.click(screen.getByLabelText(labels[name])) - expect(onClick).not.toHaveBeenCalled() + const link = screen.getByLabelText(labels[name]) + expect(link).toHaveAttribute('href', defaultProps.href) + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') }) test("renders the badge when 'index' is provided", () => { diff --git a/src/components/atoms/IconLinks/index.ts b/src/components/atoms/IconLinks/index.ts deleted file mode 100644 index 5e3a24e..0000000 --- a/src/components/atoms/IconLinks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './IconLinks' diff --git a/src/components/atoms/IconLinks/IconLinks.tsx b/src/components/atoms/IconLinks/index.tsx similarity index 63% rename from src/components/atoms/IconLinks/IconLinks.tsx rename to src/components/atoms/IconLinks/index.tsx index 2994577..627e92e 100644 --- a/src/components/atoms/IconLinks/IconLinks.tsx +++ b/src/components/atoms/IconLinks/index.tsx @@ -1,10 +1,7 @@ import { Email, LinkedIn, GitHub, Description, OndemandVideo, School, OpenInNew, OpenInBrowser } from '@mui/icons-material' -import { Badge, IconButtonProps, SvgIcon } from '@mui/material' +import { Badge, Link, LinkProps, SvgIcon } from '@mui/material' -import IconButton from '../IconButton' - -interface IconLinkProps extends IconButtonProps { - href: string +interface IconLinkProps extends LinkProps { fontSize?: 'small' | 'large' | 'medium' | 'inherit' index?: number } @@ -13,25 +10,46 @@ interface IconWrapperProps extends IconLinkProps { Icon: typeof SvgIcon } -const IconWrapper = ({ index, href: to, fontSize, Icon, ...props }: IconWrapperProps) => { +const IconWrapper: React.FC = ({ index, fontSize, Icon, sx, ...props }) => { if (typeof index === 'undefined') { return ( - { - e.stopPropagation() - window.open(to, '_blank') - }} + ({ + color: 'inherit', + ':hover': { + backgroundColor: 'transparent', + color: theme.palette.primary.main, + }, + ...sx, + })} {...props}> - + ) } return ( - window.open(to, '_blank')} {...props}> + ({ + color: 'inherit', + ':hover': { + backgroundColor: 'transparent', + color: theme.palette.primary.main, + }, + ...sx, + })} + {...props}> - + ) } diff --git a/src/components/atoms/Publication/Publication.test.tsx b/src/components/atoms/Publication/Publication.test.tsx index 99de24c..53b7deb 100644 --- a/src/components/atoms/Publication/Publication.test.tsx +++ b/src/components/atoms/Publication/Publication.test.tsx @@ -1,11 +1,9 @@ import { render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' import { Box } from '@mui/material' -import Publication from '.' +import Publication from './index.js' vi.stubGlobal('open', vi.fn()) -const open = vi.spyOn(window, 'open') describe('Publication component', () => { const defaultProps = { @@ -13,10 +11,10 @@ describe('Publication component', () => { authors: 'Test Author', title: 'Test Title', location: 'Test Location', - link: 'https://test.com', + href: 'https://test.com', } test('renders without crashing and displays the correct year, authors, title, and location', () => { - render() + render() expect(screen.getByText(defaultProps.year)).toBeInTheDocument() expect(screen.getByText(defaultProps.title)).toBeInTheDocument() expect(screen.getByText(defaultProps.authors, { exact: false })).toBeInTheDocument() @@ -26,8 +24,10 @@ describe('Publication component', () => { test('opens the correct link when the link button is clicked', async () => { render() - await userEvent.click(screen.getByRole('button')) - expect(open).toHaveBeenCalledWith(defaultProps.link, '_blank') + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', defaultProps.href) + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') }) test('renders correctly with a custom base component', () => { diff --git a/src/components/atoms/Publication/Publication.tsx b/src/components/atoms/Publication/Publication.tsx deleted file mode 100644 index d02dafa..0000000 --- a/src/components/atoms/Publication/Publication.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' -import { Box, SxProps, Theme, Typography, TypographyProps } from '@mui/material' -import { ExternalIconLink } from '..' - -type AwardEntryProps = { - year: string | number - title: string - authors: React.ReactNode - location: string - link?: string - titleProps?: TypographyProps - Component?: React.ElementType - sx?: SxProps -} - -const Publication = ({ Component = Box, ...props }: AwardEntryProps) => { - return ( - - - - {props.year} - - - {props.authors},{' '} - - {props.title} - - , In: {props.location} - {typeof props.link !== 'undefined' && } - - - - ) -} - -export default Publication diff --git a/src/components/atoms/Publication/index.tsx b/src/components/atoms/Publication/index.tsx index 869b92d..baa3742 100644 --- a/src/components/atoms/Publication/index.tsx +++ b/src/components/atoms/Publication/index.tsx @@ -1 +1,37 @@ -export { default } from './Publication' +import React from 'react' +import { Box, SxProps, Theme, Typography, TypographyProps } from '@mui/material' + +import { ExternalIconLink } from 'components/atoms/IconLinks/index.js' + +type AwardEntryProps = { + year: string | number + title: string + authors: React.ReactNode + location: string + href?: string + titleProps?: TypographyProps + Component?: React.ElementType + sx?: SxProps +} + +const Publication = ({ Component = Box, ...props }: AwardEntryProps) => { + return ( + + + + {props.year} + + + {props.authors},{' '} + + {props.title} + + , In: {props.location} + {typeof props.href !== 'undefined' && } + + + + ) +} + +export default Publication diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index b984195..39badb5 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -2,5 +2,4 @@ export { default as Award } from './Award' export { default as Extracurricular } from './Extracurricular' export { default as Publication } from './Publication' -export { default as IconLinks } from './IconButton' export * from './IconLinks' diff --git a/src/components/molecules/Footer/Footer.tsx b/src/components/molecules/Footer/Footer.tsx index 308e2df..cbbf388 100644 --- a/src/components/molecules/Footer/Footer.tsx +++ b/src/components/molecules/Footer/Footer.tsx @@ -6,10 +6,10 @@ const Footer = () => { const year = new Date().getFullYear() return ( - - - - + + + + © {year} Michele Tartari. All rights reserved. diff --git a/src/components/molecules/Hero/Hero.tsx b/src/components/molecules/Hero/Hero.tsx index daf1578..31c1161 100644 --- a/src/components/molecules/Hero/Hero.tsx +++ b/src/components/molecules/Hero/Hero.tsx @@ -44,28 +44,14 @@ const Hero = (props: { scrollTarget?: React.ForwardedRef }) = }, })}> - {/* -

Michele Tartari

-

Robotics Engineer

- */} - - Robotics Engineer - - + Robotics Engineer + See some of my works below. Send a message to get in touch. - - - - + + + +
}) = {props.scrollTarget && ( (props.scrollTarget as React.MutableRefObject).current?.scrollIntoView()} - sx={{ display: { xs: 'none', md: 'block' }, position: 'absolute', p: 1, bottom: 3 }}> + onClick={() => (props.scrollTarget as React.RefObject).current?.scrollIntoView()} + sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}> )} diff --git a/src/components/molecules/Project/Project.tsx b/src/components/molecules/Project/Project.tsx index d673ec4..ae11c41 100644 --- a/src/components/molecules/Project/Project.tsx +++ b/src/components/molecules/Project/Project.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Accordion, AccordionDetails, AccordionSummary, Typography } from '@mui/material' +import { Accordion, AccordionDetails, AccordionSummary, Stack, Typography } from '@mui/material' import { ExpandMore as ExpandIcon } from '@mui/icons-material' interface ProjectProps { @@ -54,7 +54,9 @@ const Project = (props: ProjectProps) => { {props.title} - {props.subtitle} + + {props.subtitle} + {props.children} diff --git a/src/components/molecules/ProjectSpotlight/ProjectSpotlight.tsx b/src/components/molecules/ProjectSpotlight/ProjectSpotlight.tsx index 89c69f7..05d2d3c 100644 --- a/src/components/molecules/ProjectSpotlight/ProjectSpotlight.tsx +++ b/src/components/molecules/ProjectSpotlight/ProjectSpotlight.tsx @@ -153,7 +153,7 @@ const DesktopComponent = React.forwardRef((props, ref) {props.scrollTarget && ( (props.scrollTarget as React.MutableRefObject).current?.scrollIntoView()} + onClick={() => (props.scrollTarget as React.RefObject).current?.scrollIntoView()} sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}> diff --git a/src/components/pages/ContactPage/ContactPage.test.tsx b/src/components/pages/ContactPage/ContactPage.test.tsx index 41b806d..e1f381d 100644 --- a/src/components/pages/ContactPage/ContactPage.test.tsx +++ b/src/components/pages/ContactPage/ContactPage.test.tsx @@ -1,41 +1,37 @@ import { screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { renderWithRouter as render } from 'components/utils/tests' - -import ContactPage from '.' +import { renderWithRouter as render } from 'components/utils/tests/index.js' +import ContactPage from './index.js' vi.stubGlobal('open', vi.fn()) describe('ContactPage', () => { - const open = vi.spyOn(window, 'open') - test('renders without crashing and displays the correct contact methods', () => { render() expect(screen.getAllByText('Contacts')).toHaveLength(2) // Page title and the Appbar drawer link - expect(screen.getByLabelText('Email button')).toBeInTheDocument() - expect(screen.getByLabelText('LinkedIn button')).toBeInTheDocument() - expect(screen.getByLabelText('GitHub button')).toBeInTheDocument() + expect(screen.getByLabelText('Email link')).toBeInTheDocument() + expect(screen.getByLabelText('LinkedIn link')).toBeInTheDocument() + expect(screen.getByLabelText('GitHub link')).toBeInTheDocument() }) - test('opens the correct link when the email button is clicked', async () => { + test.each([ + { + label: 'Email link', + href: 'mailto:info@m-tartari.eu', + }, + // { + // label: 'LinkedIn link', + // href: 'https://www.linkedin.com/in/m-tartari/', + // }, + // { + // label: 'GitHub link', + // href: 'https://www.github.com/m-tartari/', + // }, + ])('leads to the right place when the $label is clicked', ({ label, href }) => { render() - - await userEvent.click(screen.getByLabelText('Email button')) - expect(open).toHaveBeenCalledWith('mailto:info@m-tartari.eu', '_blank') - }) - - test('opens the correct link when the LinkedIn button is clicked', async () => { - render() - - await userEvent.click(screen.getByLabelText('LinkedIn button')) - expect(open).toHaveBeenCalledWith('https://www.linkedin.com/in/m-tartari/', '_blank') - }) - - test('opens the correct link when the GitHub button is clicked', async () => { - render() - - await userEvent.click(screen.getByLabelText('GitHub button')) - expect(open).toHaveBeenCalledWith('https://www.github.com/m-tartari/', '_blank') + const link = screen.getByLabelText(label) + expect(link).toHaveAttribute('href', href) + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') }) }) diff --git a/src/components/pages/ContactPage/ContactPage.tsx b/src/components/pages/ContactPage/ContactPage.tsx index a626e34..6dcbf0b 100644 --- a/src/components/pages/ContactPage/ContactPage.tsx +++ b/src/components/pages/ContactPage/ContactPage.tsx @@ -1,20 +1,41 @@ -import { List, ListItem, ListItemButton as MuiListItemButton, Typography, styled } from '@mui/material' -import { Drafts as DraftsIcon, LinkedIn as LinkedInIcon, GitHub as GitHubIcon } from '@mui/icons-material' +import { Link, List, ListItem, Stack, Typography } from '@mui/material' +import DraftsIcon from '@mui/icons-material/Drafts' +import LinkedInIcon from '@mui/icons-material/LinkedIn' +import GitHubIcon from '@mui/icons-material/GitHub' import { Page } from 'components/templates' -const ListItemButton = styled(MuiListItemButton)(({ theme }) => ({ - h6: { - color: theme.palette.text.secondary, - }, - ':hover': { - backgroundColor: 'transparent', - color: theme.palette.primary.main, - h6: { - color: theme.palette.primary.dark, - }, - }, -})) +interface ListItemProps { + children?: React.ReactNode + href?: string +} +const ListItemLink: React.FC = ({ children, ...props }) => ( + + ({ + color: 'inherit', + + h6: { + color: theme.palette.text.secondary, + }, + ':hover': { + backgroundColor: 'transparent', + color: theme.palette.primary.main, + h6: { + color: theme.palette.primary.dark, + }, + }, + })}> + + {children} + + + +) const ContactPage = () => { return ( @@ -23,32 +44,26 @@ const ContactPage = () => { Feel free to contact me using any of the following:
- - window.open('mailto:info@m-tartari.eu', '_blank')}> - - - info - - @m-tartari.eu - - - - - window.open('https://www.linkedin.com/in/m-tartari/', '_blank')}> - - - m-tartari - - - - - window.open('https://www.github.com/m-tartari/', '_blank')}> - - - m-tartari - - - + + + + info + + @m-tartari.eu + + + + + + m-tartari + + + + + + m-tartari + + ) diff --git a/src/components/pages/CurriculumPage/CurriculumPage.tsx b/src/components/pages/CurriculumPage/CurriculumPage.tsx index 7aa5e5b..dafb63d 100644 --- a/src/components/pages/CurriculumPage/CurriculumPage.tsx +++ b/src/components/pages/CurriculumPage/CurriculumPage.tsx @@ -29,7 +29,12 @@ const Curriculum = () => { Co-founded{' '} - + MindHub Ferrara , the first programming school for children in Ferrara, under the{' '} @@ -132,9 +137,9 @@ const Curriculum = () => { {/* id="WUT" */} - + Credential ID: EZ34GWQLRHEG - + @@ -414,7 +419,7 @@ const Curriculum = () => { } location="ROMANSY 23 - Robot Design, Dynamics and Control. CISM International Centre for Mechanical Sciences (Courses and Lectures), vol 601. Springer, Cham." - link="https://doi.org/10.1007/978-3-030-58380-4_7" + href="https://doi.org/10.1007/978-3-030-58380-4_7" />
diff --git a/src/components/pages/MainPage/MainPage.tsx b/src/components/pages/MainPage/MainPage.tsx index 7950659..c53d1a9 100644 --- a/src/components/pages/MainPage/MainPage.tsx +++ b/src/components/pages/MainPage/MainPage.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box, Typography } from '@mui/material' +import { Box, Stack, Typography } from '@mui/material' import { ViewportProvider } from 'components/utils/viewport' import { AcademyIconLink, DescriptionIconLink, ExternalAppIconLink, GitHubIconLink, VideoIconLink } from 'components/atoms' @@ -28,12 +28,10 @@ const MainPage = () => { image="images/IIT_Linfa.webp" title="LINFA: An Intuitive Block-Programming Interface for Reconfigurable Robots" caption={ - <> - - - Software Engineer @ IIT - - + + + Software Engineer @ IIT + }> This project aims to simplify the control, monitoring, and programming of reconfigurable robots through an intuitive block-programming interface. It includes a front-end web app, built with TypeScript and React, allowing users to effortlessly change robot operating modes and @@ -52,13 +50,11 @@ const MainPage = () => { mobileImage="images/IIT_Modular_Mobile.webp" title="Revamping HHCM's Modular: Swift Generation of Modular Robot Models" caption={ - <> - - - - Software Engineer @ IIT - - + + + + Software Engineer @ IIT + }> This project focused on overhauling HHCM's Modular, enhancing its graphical interface, API, and deployment pipeline. Modular serves as a web application for the swift generation of modular robot models, with an offline approach (Building Mode) enabling users to set up robots in a @@ -75,12 +71,10 @@ const MainPage = () => { image="images/IIT_Modules.webp" title="Automated Actuators and Robotic Modules Calibration" caption={ - <> - - - Embedded Engineer @ IIT - - + + + Embedded Engineer @ IIT + }> This project focused on automating the calibration of EtherCAT actuators and robotic modules, aiming for a fully automated procedure for newly assembled actuators. The process involved testing the PDO, conducting phase angle calibration, torque sensor calibration, ripple and @@ -96,18 +90,11 @@ const MainPage = () => { mobileImage="images/thesis.webp" title="Voice Controlled Mobile Robot for Indoor Applications" caption={ - <> - - - - - Master Thesis @ WUT - - + + + + Master Thesis @ WUT + }> This thesis proposes a design for a small mobile robot controlled by voice. Firstly a preliminary analysis of the state of art of human-robot interaction has been conducted. Then, an analysis of the equipment selected took place, and, later, appropriate methodologies diff --git a/src/components/pages/ProjectsPage/ProjectsPage.test.tsx b/src/components/pages/ProjectsPage/ProjectsPage.test.tsx index a0922aa..8fed1d8 100644 --- a/src/components/pages/ProjectsPage/ProjectsPage.test.tsx +++ b/src/components/pages/ProjectsPage/ProjectsPage.test.tsx @@ -1,12 +1,9 @@ import { screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' - -import { renderWithRouter as render } from 'components/utils/tests' +import { renderWithRouter as render } from 'components/utils/tests/index.js' import ProjectsPage from '.' vi.stubGlobal('open', vi.fn()) -const open = vi.spyOn(window, 'open') describe('ProjectsPage component', () => { test('renders without crashing and displays the correct project titles', () => { render() @@ -25,8 +22,10 @@ describe('ProjectsPage component', () => { test('opens the correct links when the link buttons are clicked', async () => { render() - // test one of the many links - await userEvent.click(screen.getByLabelText('Video-button')) - expect(open).toHaveBeenLastCalledWith('https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/', '_blank') + // inspect one of the many links + const link = screen.getByLabelText('Video-button') + expect(link).toHaveAttribute('href', 'https://www.linkedin.com/feed/update/urn:li:activity:7034480841569284096/') + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') }) }) diff --git a/src/components/pages/ProjectsPage/ProjectsPage.tsx b/src/components/pages/ProjectsPage/ProjectsPage.tsx index 6c3475c..a5f410d 100644 --- a/src/components/pages/ProjectsPage/ProjectsPage.tsx +++ b/src/components/pages/ProjectsPage/ProjectsPage.tsx @@ -22,13 +22,13 @@ const ProjectsPage = () => { Software Engineer @ IIT {/* */} - + Software Engineer @ IIT @@ -49,9 +49,9 @@ const ProjectsPage = () => { Software Engineer @ IIT - - - + + + Software Engineer @ IIT @@ -70,14 +70,14 @@ const ProjectsPage = () => { Master Thesis @ WUT - + - + Master Thesis @ WUT @@ -93,15 +93,12 @@ const ProjectsPage = () => { - - - + }> The aim of this project was to prove the possibility to design an autonomous mobile robot whose task was to recharge electric cars in a @@ -112,11 +109,11 @@ const ProjectsPage = () => { title="Mobile Robot Self-Localization" subtitle={ <> - + @@ -130,16 +127,11 @@ const ProjectsPage = () => { title="Onychophora-Inspired Firefighting Robot" subtitle={ <> - + @@ -155,13 +147,13 @@ const ProjectsPage = () => { <> {/* */} { + <> + + + }> This project concerns the design and optimization of a 3RRR planar parallel manipulator using MATLAB and CATIA @@ -189,7 +189,7 @@ const ProjectsPage = () => { title="Simple Swarm" subtitle={ <> - + }> @@ -201,7 +201,7 @@ const ProjectsPage = () => { subtitle={ <> { subtitle={ <> { Embedded Engineer @ IIT - - + + Embedded Engineer @ IIT @@ -268,12 +263,12 @@ const ProjectsPage = () => { Bachelor Thesis @ TONGJI - + Bachelor Thesis @ TONGJI @@ -290,12 +285,12 @@ const ProjectsPage = () => { Bachelor Thesis @ UNIBO - + Bachelor Thesis @ UNIBO @@ -308,14 +303,9 @@ const ProjectsPage = () => { title="Control of a Nonlinear System" subtitle={ <> - + { title="Multi-Body System's Kinematic Analysis" subtitle={ <> - + { + }> Several Signal processing projects developed using MATLAB. + }> Several real-time systems' projects developed using C++ and OIL, and run using Trampoline RTOS on a Teensy board. @@ -373,7 +358,7 @@ const ProjectsPage = () => { + }> A collection of pdf and CAD models to create late XIV century furniture and medieval camp equipment. These objects are being realized by{' '} @@ -386,7 +371,7 @@ const ProjectsPage = () => { + }> Publishes all photos from a{' '}