diff --git a/package.json b/package.json index 7b8c087..984a7a9 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,7 @@ "version": "2.0.0", "description": "Hanna Achenbach portfolio", "type": "module", - "license": "MIT", - "scripts": { "start": "remix dev", "build": "remix build", diff --git a/public/data/intro-data.tsx b/public/data/intro-data.tsx index 40af843..94e8ac7 100644 --- a/public/data/intro-data.tsx +++ b/public/data/intro-data.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react'; +import type {ReactNode} from 'react'; export const tags = [ 'React', diff --git a/server.js b/server.js index 4e2a0e0..9bbaae4 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,4 @@ -import { createRequestHandler } from "@remix-run/vercel"; -import * as build from "@remix-run/dev/server-build"; +import {createRequestHandler} from '@remix-run/vercel'; +import * as build from '@remix-run/dev/server-build'; -export default createRequestHandler({ build, mode: process.env.NODE_ENV }); +export default createRequestHandler({build, mode: process.env.NODE_ENV}); diff --git a/src/components/molecules/CookieBanner.tsx b/src/components/molecules/CookieBanner.tsx index d900496..3eaff75 100644 --- a/src/components/molecules/CookieBanner.tsx +++ b/src/components/molecules/CookieBanner.tsx @@ -10,7 +10,7 @@ interface CookieBanner { onReject(): void; } -export const CookieBanner: FC = () => { +const CookieBanner: FC = () => { const location = useLocation(); const navigate = useNavigate(); const {consent, setConsent} = useCookieConsent(); @@ -21,7 +21,7 @@ export const CookieBanner: FC = () => { className={classNames( 'min-h-full min-w-full fixed top-0 bottom-0 left-0 right-0 z-50', { - ' bg-neutral-900/50 dark:bg-netral-300/25': + ' bg-neutral-900/50 dark:bg-neutral-300/25': location.pathname !== '/cookie-consent', }, )} @@ -36,9 +36,9 @@ export const CookieBanner: FC = () => { <> {' '} -{' '} - + Read more - + )} @@ -68,3 +68,5 @@ export const CookieBanner: FC = () => { ); }; + +export default CookieBanner; diff --git a/src/components/molecules/DarkModeSwitch.tsx b/src/components/molecules/DarkModeSwitch.tsx index 55baf9d..1577c9e 100644 --- a/src/components/molecules/DarkModeSwitch.tsx +++ b/src/components/molecules/DarkModeSwitch.tsx @@ -1,7 +1,7 @@ import {useGTMDispatch} from '@elgorditosalsero/react-gtm-hook'; import classNames from 'classnames'; -import {FC} from 'react'; -import {ReactProps} from '~/components'; +import type {FC} from 'react'; +import type {ReactProps} from '~/components'; import {Typo} from '~/components/primitives/typography'; import {useTheme} from '~/contexts/ThemeContext'; diff --git a/src/components/molecules/FormComponents.tsx b/src/components/molecules/FormComponents.tsx index 5de8d7b..ca98330 100644 --- a/src/components/molecules/FormComponents.tsx +++ b/src/components/molecules/FormComponents.tsx @@ -1,4 +1,4 @@ -import {FC} from 'react'; +import type {FC} from 'react'; import {input} from '~/components/primitives'; import {Typo} from '~/components/primitives/typography'; @@ -68,7 +68,7 @@ export const RadioGroup: FC< > = ({label, options, ...props}) => { return ( <> - {label[0]} + {label[0]} {options.map(({value, label}) => (
))} - {label[1]} + {label[1]} ); }; diff --git a/src/components/molecules/HeadlineWithDivider.tsx b/src/components/molecules/HeadlineWithDivider.tsx index 9a29bb4..a3b8238 100644 --- a/src/components/molecules/HeadlineWithDivider.tsx +++ b/src/components/molecules/HeadlineWithDivider.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import {FC, ReactNode} from 'react'; -import {ReactProps} from '~/components'; +import type {FC, ReactNode} from 'react'; +import type {ReactProps} from '~/components'; import {HR} from '~/components/primitives'; import {Typo} from '~/components/primitives/typography'; /** diff --git a/src/components/molecules/Layout.tsx b/src/components/molecules/Layout.tsx index 2e42bc5..486a513 100644 --- a/src/components/molecules/Layout.tsx +++ b/src/components/molecules/Layout.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import {ComponentWithChildren} from '~/components'; +import type {ComponentWithChildren} from '~/components'; export const ContainerOuter: ComponentWithChildren = ({ children, diff --git a/src/components/molecules/ScrollPosition.tsx b/src/components/molecules/ScrollPosition.tsx new file mode 100644 index 0000000..8c9677e --- /dev/null +++ b/src/components/molecules/ScrollPosition.tsx @@ -0,0 +1,35 @@ +import {useLocation} from '@remix-run/react'; +import type {FC} from 'react'; +import {useCallback, useEffect, useState} from 'react'; +import {useWindow} from '~/contexts/WindowContext'; + +/** + * + * @returns a custom component to restore scroll position and make it feel like a single page app + */ +export const ScrollPosition: FC = () => { + const {pathname} = useLocation(); + const windowContext = useWindow(); + const [currentPathname, setCurrentPathname] = useState(pathname); + + const resetScrollPosition = useCallback( + ( + _pathname: string, + _currentPathname: string, + _windowContext: ReturnType, + ) => { + if (_windowContext && _currentPathname !== _pathname) { + debugger; + window.scroll(0, _windowContext.pageYOffset); + setCurrentPathname(_pathname); + } + }, + [], + ); + + useEffect(() => { + resetScrollPosition(pathname, currentPathname, windowContext); + }, [pathname, currentPathname, windowContext, resetScrollPosition]); + + return null; +}; diff --git a/src/components/molecules/SrollPosition.tsx b/src/components/molecules/SrollPosition.tsx deleted file mode 100644 index 28b9c3c..0000000 --- a/src/components/molecules/SrollPosition.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import {useLocation} from '@remix-run/react'; -import {FC, useEffect, useState} from 'react'; -import {useWindow} from '~/contexts/WindowContext'; - -/** - * - * @returns a custom component to restore scroll position and make it feel like a single page app - */ -export const SrollPosition: FC = () => { - const {pathname} = useLocation(); - const windowContext = useWindow(); - const [currentPathname, setCurrentPathname] = useState(pathname); - - useEffect(() => { - if (windowContext && currentPathname !== pathname) { - window.scroll(0, windowContext.pageYOffset); - setCurrentPathname(pathname); - } - }, [pathname, windowContext]); - - return null; -}; diff --git a/src/components/molecules/SuspenseImage.tsx b/src/components/molecules/SuspenseImage.tsx index 1bc3ba2..3d82d47 100644 --- a/src/components/molecules/SuspenseImage.tsx +++ b/src/components/molecules/SuspenseImage.tsx @@ -1,7 +1,8 @@ import classNames from 'classnames'; -import {FC, Suspense} from 'react'; +import type {FC} from 'react'; +import {Suspense} from 'react'; import {Img, resource} from 'react-suspense-img'; -import {ReactProps} from '~/components'; +import type {ReactProps} from '~/components'; export const SuspenseImage: FC< ReactProps & { diff --git a/src/components/primitives/index.tsx b/src/components/primitives/index.tsx index ca2248b..51a7801 100644 --- a/src/components/primitives/index.tsx +++ b/src/components/primitives/index.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; -import {FC} from 'react'; -import {ReactProps} from '~/components'; +import type {FC} from 'react'; +import type {ReactProps} from '~/components'; export const blackBorder = 'border-solid border-bl dark:border-white'; export const link = diff --git a/src/components/primitives/typography.tsx b/src/components/primitives/typography.tsx index b0ed080..fbeebf8 100644 --- a/src/components/primitives/typography.tsx +++ b/src/components/primitives/typography.tsx @@ -123,7 +123,7 @@ const P: FC = ({children, ...typoProps}) => {

); }; -const SPAN: FC = ({children, ...typoProps}) => { +const Span: FC = ({children, ...typoProps}) => { const className = classNames( 'font-thin', commonClassNames(typoProps), @@ -181,7 +181,7 @@ const LINK_EXTERNAL: FC< ); }; -const CAPTION: FC = ({children, ...typoProps}) => { +const Caption: FC = ({children, ...typoProps}) => { const className = classNames( 'text-sm font-thin', commonClassNames(typoProps), @@ -202,8 +202,8 @@ export const Typo = { H4, H5, P, - SPAN, + Span, LinkInternal: LINK_INTERNAL, LinkExternal: LINK_EXTERNAL, - Caption: CAPTION, + Caption, } as const; diff --git a/src/contexts/CookieContext.tsx b/src/contexts/CookieContext.tsx index 45595dd..83f55bd 100644 --- a/src/contexts/CookieContext.tsx +++ b/src/contexts/CookieContext.tsx @@ -1,5 +1,5 @@ import {createContext, useContext, useEffect, useState} from 'react'; -import {ComponentWithChildren} from '~/components'; +import type {ComponentWithChildren} from '~/components'; interface CookieContext { consent: boolean | null; diff --git a/src/contexts/ThemeContext.tsx b/src/contexts/ThemeContext.tsx index 390fb45..cc3334a 100644 --- a/src/contexts/ThemeContext.tsx +++ b/src/contexts/ThemeContext.tsx @@ -1,5 +1,5 @@ import {createContext, useContext, useEffect, useState} from 'react'; -import {ComponentWithChildren} from '~/components'; +import type {ComponentWithChildren} from '~/components'; import {useWindow} from '~/contexts/WindowContext'; import {STORAGE_ITEMS} from '~/utils/constants'; @@ -37,7 +37,7 @@ export const ThemeContextProvider: ComponentWithChildren = ({children}) => { setSystemDarkMode(system); let isDarkMode = false; const stored = window.localStorage.getItem(STORAGE_ITEMS.DARK_MODE); - if (!!stored) { + if (stored) { // setting previously saved in localStorage isDarkMode = JSON.parse(stored ?? 'false'); } else { diff --git a/src/contexts/WindowContext.tsx b/src/contexts/WindowContext.tsx index 5bcf233..1d1a88c 100644 --- a/src/contexts/WindowContext.tsx +++ b/src/contexts/WindowContext.tsx @@ -1,13 +1,6 @@ import debounce from 'just-debounce'; -import { - createContext, - FC, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; -import {ComponentWithChildren} from '~/components'; +import {createContext, useContext, useEffect, useMemo, useState} from 'react'; +import type {ComponentWithChildren} from '~/components'; type WindowContext = { width: number; @@ -37,7 +30,7 @@ export const WindowContextProvider: ComponentWithChildren = ({children}) => { setWindowObject({ width: window.innerWidth, height: window.innerHeight, - pageYOffset: window.pageYOffset, + pageYOffset: window.scrollY, }); }; @@ -58,7 +51,7 @@ export const WindowContextProvider: ComponentWithChildren = ({children}) => { window.removeEventListener('resize', debouncedHandleResize); window.removeEventListener('scroll', debouncedHandleResize); }; - }, []); // Empty array ensures that effect is only run on mount + }, [debouncedHandleResize]); // Empty array ensures that effect is only run on mount return ( diff --git a/src/root.tsx b/src/root.tsx index 2eb040c..2d60373 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -14,11 +14,9 @@ import { import classNames from 'classnames'; import dotenv from 'dotenv'; import type {FC} from 'react'; -import {useEffect, useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import {Helmet} from 'react-helmet'; import type {ComponentWithChildren} from '~/components'; -import {CookieBanner} from '~/components/molecules/CookieBanner'; -import {SrollPosition} from '~/components/molecules/SrollPosition'; import {Typo} from '~/components/primitives/typography'; import tailwindStyles from '~/styles/tailwind.css'; import { @@ -27,6 +25,7 @@ import { } from '~/contexts/CookieContext'; import {ThemeContextProvider, useTheme} from '~/contexts/ThemeContext'; import {WindowContextProvider} from '~/contexts/WindowContext'; +import CookieBanner from './components/molecules/CookieBanner'; const description = 'I am a Berlin based frontend engineer and artist coming from a design driven background. On this portfolio you can find my cv and career path, a collection of things I currently love, some of my work and a way to contact me for job offers.'; @@ -65,14 +64,20 @@ const PageViewTracker: FC = () => { const [oldPathname, setOldPathname] = useState('/'); const {pathname} = useLocation(); const sendDataToGTM = useGTMDispatch(); - useEffect(() => { + + const trackNavigation = useCallback(() => { sendDataToGTM({ 'event': 'navigate', 'gtm.oldUrl': oldPathname, 'gtm.newUrl': pathname, }); setOldPathname(pathname); - }, [pathname]); + }, [oldPathname, pathname, sendDataToGTM]); + + useEffect(() => { + trackNavigation(); + }, [trackNavigation]); + return null; }; @@ -131,8 +136,6 @@ const Document: ComponentWithChildren = ({children}) => { {process.env.NODE_ENV === 'development' && } {children} - - {/* */} ); @@ -160,12 +163,14 @@ const GTMErrorTracker: FC<{err: string}> = ({err}) => { const sendDataToGTM = useGTMDispatch(); const {pathname} = useLocation(); useEffect(() => { - sendDataToGTM({ - 'event': 'gtm.pageError', - 'gtm.errorMessage': err, - 'gtm.errorUrl': pathname, - }); - }, []); + if (err && pathname) { + sendDataToGTM({ + 'event': 'gtm.pageError', + 'gtm.errorMessage': err, + 'gtm.errorUrl': pathname, + }); + } + }, [sendDataToGTM, err, pathname]); return null; }; diff --git a/src/routes/cv.tsx b/src/routes/cv.tsx index c77133f..8a65f2e 100644 --- a/src/routes/cv.tsx +++ b/src/routes/cv.tsx @@ -1,8 +1,7 @@ -import {useGTMDispatch} from '@elgorditosalsero/react-gtm-hook'; import type {MetaFunction} from '@remix-run/node'; import classNames from 'classnames'; import {educations, experiences} from 'public/data/cv-data'; -import {useEffect, useState} from 'react'; +import {useCallback, useEffect, useMemo, useState} from 'react'; import {HeadlineWithDivider} from '~/components/molecules/HeadlineWithDivider'; import {ContainerInner, SpacedCols} from '~/components/molecules/Layout'; import {PageLayout} from '~/components/molecules/PageLayout'; @@ -46,26 +45,27 @@ const sideBar = ( const CV = () => { const windowContext = useWindow(); - const sendDataToGTM = useGTMDispatch(); - - const defaultState = new Map([ - [ - 'aiven', - windowContext && windowContext.width && windowContext?.width < 768 - ? false - : true, - ], - ['back', false], - ['neugelb', false], - ['autentek_2', false], - ['autentek_1', false], - ]); + + const defaultState = useMemo( + () => + new Map([ + [ + 'aiven', + windowContext?.width && windowContext.width < 768 ? true : false, + ], + ['back', false], + ['neugelb', false], + ['autentek_2', false], + ['autentek_1', false], + ]), + [windowContext], + ); const [openSections, setOpenSections] = useState< Map | undefined >(); - useEffect(() => { + const updateSections = useCallback(() => { const stored = window.localStorage.getItem(STORAGE_ITEMS.CV_SECTIONS); if (stored) { setOpenSections(new Map(JSON.parse(stored))); @@ -76,13 +76,17 @@ const CV = () => { JSON.stringify(Array.from(defaultState.entries())), ); } - }, []); + }, [setOpenSections, defaultState]); + + useEffect(() => { + updateSections(); + }, [updateSections]); const persistSection = ( e: React.MouseEvent, section: ExperienceId, ) => { - const isClose = !openSections?.get(section) == false; + const isClose = !openSections?.get(section) === false; const updatedMap = new Map( openSections?.set(section, !openSections.get(section)), @@ -105,16 +109,15 @@ const CV = () => { } }; + const isMobile = Boolean( + windowContext && windowContext.width && windowContext?.width < 768, + ); + return ( = 768 && - sideBar - } + sideBar={!isMobile && sideBar} > @@ -225,22 +228,19 @@ const CV = () => { )} -
persistSection(e, key)} > Show {openSections.get(key) ? 'less' : 'more'} -
+ ))}
- {windowContext && - windowContext.width && - windowContext?.width < 768 && - sideBar} + {isMobile && sideBar}
); };