From efd91adbfa390fc16beee7a1207b7af5378d9843 Mon Sep 17 00:00:00 2001 From: Amit Raikwar Date: Wed, 5 Aug 2026 04:26:29 +0530 Subject: [PATCH] feat(resume): add resume modal section with audio walkthrough and pdf preview (AR-23) --- jest.js | 8 + .../Button/LinkButton/LinkButton.tsx | 5 +- src/components/Button/LinkButton/types.ts | 3 +- src/components/Cursor/FollowCursor.tsx | 31 ++- .../ResumeAudioPlayer/ResumeAudioPlayer.tsx | 43 ++++ .../__tests__/ResumeAudioPlayer.test.tsx | 34 ++++ src/components/ResumeAudioPlayer/index.ts | 2 + src/components/animateModal/AnimatedModal.tsx | 10 +- src/components/animateModal/Modal.tsx | 18 +- src/components/index.ts | 2 + src/data/index.ts | 2 + src/data/resume.ts | 22 +++ src/index.css | 6 + src/localization/locales/en/main.json | 10 + src/screens/common/NavigationBar.tsx | 26 ++- src/screens/common/SocialNavigation.tsx | 4 +- src/screens/mainFlow/contents/Contents.tsx | 2 + src/screens/mainFlow/contents/Resume.tsx | 184 ++++++++++++++++++ .../contents/__tests__/Resume.test.tsx | 56 ++++++ src/screens/mainFlow/contents/index.ts | 2 + src/setupTests.ts | 2 + 21 files changed, 449 insertions(+), 23 deletions(-) create mode 100644 src/components/ResumeAudioPlayer/ResumeAudioPlayer.tsx create mode 100644 src/components/ResumeAudioPlayer/__tests__/ResumeAudioPlayer.test.tsx create mode 100644 src/components/ResumeAudioPlayer/index.ts create mode 100644 src/data/resume.ts create mode 100644 src/screens/mainFlow/contents/Resume.tsx create mode 100644 src/screens/mainFlow/contents/__tests__/Resume.test.tsx diff --git a/jest.js b/jest.js index b60c3b6..4a3647c 100644 --- a/jest.js +++ b/jest.js @@ -46,3 +46,11 @@ jest.mock('ogl', () => ({ Triangle: jest.fn(), Mesh: jest.fn(), })); + +global.ResizeObserver = class ResizeObserver { + observe = jest.fn(); + unobserve = jest.fn(); + disconnect = jest.fn(); +}; + + diff --git a/src/components/Button/LinkButton/LinkButton.tsx b/src/components/Button/LinkButton/LinkButton.tsx index 106a39c..1363ee7 100644 --- a/src/components/Button/LinkButton/LinkButton.tsx +++ b/src/components/Button/LinkButton/LinkButton.tsx @@ -6,12 +6,11 @@ import { useRef } from 'react'; const LinkButton = ({ text, - href, - animationOnHover, withUnderline, withArrow, fontSize, onClick, + href = undefined, }: LinkButtonProps) => { const ref = useRef(null); const { setCursorInsets } = useCursor(); @@ -52,7 +51,7 @@ const LinkButton = ({ rightIcon={withArrow ? : undefined} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} - onClick={() => onClick?.(href)} + onClick={() => href && onClick?.(href)} justifyItems={'end'} rowGap={2} {...(!onClick ? { as: 'a', href } : {})} diff --git a/src/components/Button/LinkButton/types.ts b/src/components/Button/LinkButton/types.ts index dfd9e96..45c3182 100644 --- a/src/components/Button/LinkButton/types.ts +++ b/src/components/Button/LinkButton/types.ts @@ -1,11 +1,10 @@ import { BoxProps } from '@chakra-ui/react'; export type LinkButtonProps = { - href: string; text: string; withUnderline?: boolean; withArrow?: boolean; - animationOnHover?: boolean; + href?: string; fontSize?: BoxProps['fontSize']; onClick?: (target: string) => void; }; diff --git a/src/components/Cursor/FollowCursor.tsx b/src/components/Cursor/FollowCursor.tsx index 4fe988b..0d7d1e1 100644 --- a/src/components/Cursor/FollowCursor.tsx +++ b/src/components/Cursor/FollowCursor.tsx @@ -39,15 +39,40 @@ const FollowCursor = () => { const shouldHide = hasInsets && (insets.width === 0 || insets.height === 0); useEffect(() => { + let mouseTimeout: ReturnType | null = null; + + const enableIframePointerEvents = () => { + document.body.classList.remove('disable-iframe-pointer-events'); + }; + + const disableIframePointerEvents = () => { + document.body.classList.add('disable-iframe-pointer-events'); + if (mouseTimeout) clearTimeout(mouseTimeout); + mouseTimeout = setTimeout(enableIframePointerEvents, 150); + }; + const handleMouseMove = (e: MouseEvent) => { dotTargetX.set(e.clientX); dotTargetY.set(e.clientY); ringTargetX.set(e.clientX); ringTargetY.set(e.clientY); + + disableIframePointerEvents(); + }; + + const handleMouseDown = () => { + enableIframePointerEvents(); }; window.addEventListener('mousemove', handleMouseMove, { passive: true }); - return () => window.removeEventListener('mousemove', handleMouseMove); + window.addEventListener('mousedown', handleMouseDown, { passive: true }); + + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mousedown', handleMouseDown); + if (mouseTimeout) clearTimeout(mouseTimeout); + enableIframePointerEvents(); + }; }, [dotTargetX, dotTargetY, ringTargetX, ringTargetY]); const ringSize = shouldHide ? 0 : isHovering ? 48 : 36; @@ -72,7 +97,7 @@ const FollowCursor = () => { borderRadius: '50%', backgroundColor: '#ffffff', mixBlendMode: 'difference', - zIndex: 99999, + zIndex: 2147483647, pointerEvents: 'none', x: dotX, y: dotY, @@ -105,7 +130,7 @@ const FollowCursor = () => { position: 'fixed', left: 0, top: 0, - zIndex: 99998, + zIndex: 2147483646, pointerEvents: 'none', x: ringX, y: ringY, diff --git a/src/components/ResumeAudioPlayer/ResumeAudioPlayer.tsx b/src/components/ResumeAudioPlayer/ResumeAudioPlayer.tsx new file mode 100644 index 0000000..d372ca2 --- /dev/null +++ b/src/components/ResumeAudioPlayer/ResumeAudioPlayer.tsx @@ -0,0 +1,43 @@ +import { Box } from '@chakra-ui/react'; + +export interface ResumeAudioPlayerProps { + audioUrl: string; + title?: string; + subtitle?: string; + fallbackDuration?: string; +} + +const ResumeAudioPlayer = ({ + audioUrl, + title = 'Audio Walkthrough', +}: ResumeAudioPlayerProps) => { + const isGDrive = audioUrl.includes('drive.google.com'); + const embedSrc = isGDrive + ? audioUrl.replace(/\/view.*/, '/preview') + : audioUrl; + + return ( + +