From 1dffb7c964f44eb8198d89e6a5b7b22ceb163842 Mon Sep 17 00:00:00 2001 From: Numan Date: Fri, 1 May 2026 09:54:18 +0530 Subject: [PATCH 1/4] [data] Information updated --- src/assets/data/experiencesData.json | 11 ++++++++--- src/assets/data/info.ts | 8 ++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/assets/data/experiencesData.json b/src/assets/data/experiencesData.json index c4aaf69..43da5bd 100644 --- a/src/assets/data/experiencesData.json +++ b/src/assets/data/experiencesData.json @@ -6,16 +6,21 @@ "end": "" }, "role": "Sr. Software Engineer", - "description": "DarwinBox a HCM Platform, based in Hyderabad, INDIA.", + "description": "Built and scaled enterprise HR modules (Recruitment, Payroll, Onboarding, Talent Intelligence) used by 1000+ organizations. Improved performance of data-heavy admin dashboards by ~80% using lazy loading, API debouncing, caching and chunked data rendering. Delivered complex features with a focus on scalability, performance, and clean architecture. Led code reviews and mentoring for junior engineers, improving team code quality and delivery speed. Contributed to frontend architecture decisions for large-scale applications.", "companyLogo": "../icons/dbox.png", "companyURL": "https://darwinbox.com/", "workingTech": [ "ReactJS", - "NodeJS", + "Angular", + "Typescript", + "Node.js", + "Express.js", "Redux", + "RxJS", "SCSS", "Bootstrap", - "jQuery" + "jQuery", + "MongoDB" ], "sidePic": { "url": "../images/num-planning.svg", diff --git a/src/assets/data/info.ts b/src/assets/data/info.ts index a8e6513..d41205b 100644 --- a/src/assets/data/info.ts +++ b/src/assets/data/info.ts @@ -70,7 +70,7 @@ export const InfoData: Record = { minor: { title: 'Wealth42', sub1: 'SDE', - sub2: 'Jan-2022 => 7M', + sub2: 'Jan-2022', inactive: true, url: 'https://wealth42.com/', img: 'w42.png', @@ -96,7 +96,7 @@ export const InfoData: Record = { extra: { title: 'Wealth42', sub1: 'SDE', - sub2: 'Jan-2022 => 7M', + sub2: 'Jan-2022', inactive: true, url: 'https://wealth42.com/', img: 'w42.png', @@ -122,7 +122,7 @@ export const InfoData: Record = { extra: { title: 'Wealth42', sub1: 'SDE', - sub2: 'Jan-2022 => 7M', + sub2: 'Jan-2022', inactive: true, url: 'https://wealth42.com/', img: 'w42.png', @@ -148,7 +148,7 @@ export const InfoData: Record = { minor: { title: 'Wealth42', sub1: 'SDE', - sub2: 'Jan-2022 => 7M', + sub2: 'Jan-2022', inactive: true, url: 'https://wealth42.com/', img: 'w42.png', From 9467e4e6b22c51b3512361ff3fcd3789be7cfc6a Mon Sep 17 00:00:00 2001 From: Numan Date: Sat, 2 May 2026 11:13:56 +0530 Subject: [PATCH 2/4] [Experience] experience description section changes --- package-lock.json | 75 +++++++++ package.json | 1 + src/assets/data/experiencesData.json | 20 ++- src/components/Experiences/Experiences.tsx | 183 ++++++++++++++++++++- 4 files changed, 270 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4b84b5..2f1a736 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@mui/material": "^6.5.0", "@testing-library/react": "^16.3.2", "gh-pages": "^3.2.3", + "motion": "^12.38.0", "react": "^19.2.4", "react-dom": "^19.2.4", "react-helmet-async": "^3.0.0", @@ -3322,6 +3323,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -3910,6 +3938,47 @@ "node": "*" } }, + "node_modules/motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz", + "integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.38.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5135,6 +5204,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", diff --git a/package.json b/package.json index f3e73a5..c710b35 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@mui/material": "^6.5.0", "@testing-library/react": "^16.3.2", "gh-pages": "^3.2.3", + "motion": "^12.38.0", "react": "^19.2.4", "react-dom": "^19.2.4", "react-helmet-async": "^3.0.0", diff --git a/src/assets/data/experiencesData.json b/src/assets/data/experiencesData.json index 43da5bd..3f4b035 100644 --- a/src/assets/data/experiencesData.json +++ b/src/assets/data/experiencesData.json @@ -6,7 +6,13 @@ "end": "" }, "role": "Sr. Software Engineer", - "description": "Built and scaled enterprise HR modules (Recruitment, Payroll, Onboarding, Talent Intelligence) used by 1000+ organizations. Improved performance of data-heavy admin dashboards by ~80% using lazy loading, API debouncing, caching and chunked data rendering. Delivered complex features with a focus on scalability, performance, and clean architecture. Led code reviews and mentoring for junior engineers, improving team code quality and delivery speed. Contributed to frontend architecture decisions for large-scale applications.", + "description": "

Built and scaled enterprise HR modules (Recruitment, Payroll, Onboarding, Talent Intelligence) used by 1000+ organizations.

", + "bulletPoints": [ + "Improved performance of data-heavy admin dashboards by ~80% using lazy loading, API debouncing, caching and chunked data rendering.", + "Delivered complex features with a focus on scalability, performance, and clean architecture.", + "Led code reviews and mentoring for junior engineers, improving team code quality and delivery speed.", + "Contributed to frontend architecture decisions for large-scale applications." + ], "companyLogo": "../icons/dbox.png", "companyURL": "https://darwinbox.com/", "workingTech": [ @@ -34,7 +40,11 @@ "end": "2022-01" }, "role": "Software Engineer", - "description": "Wealth42 a Fin-Tech startup, based in Bengaluru, INDIA.", + "description": "

Wealth42 is an investment platform that simplifies wealth management for retail investors through data-driven insights and seamless digital onboarding.

", + "bulletPoints": [ + "Developed a Financial Analytics Dashboard using functional programming principles and interactive charts-based data visualization, transforming complex portfolio and market data into clear, actionable insights for retail investors.", + "Built the Wealth42.com platform end-to-end — including a complete user onboarding flow and a reusable component system — reducing development effort by ~60% and enforcing UI consistency across the entire product." + ], "companyLogo": "../icons/w42.png", "companyURL": "https://wealth42.com/", "workingTech": [ @@ -60,7 +70,11 @@ "end": "2021-07-01" }, "role": "Full Stack Developer", - "description": "SrcLogicx is a startup related to Video/Audio Chat solutions, based in San Diego, California, US.", + "description": "

SrcLogicx is a software development company that builds custom digital products and SaaS solutions for clients across communication, enterprise, and productivity domains.

", + "bulletPoints": [ + "Developed a scalable, cloud-based video meeting platform integrating AWS Chime for real-time communication and AWS S3 for media storage, supporting 100+ concurrent attendees with stable, low-latency performance across sessions.", + "Built a meeting management dashboard with a reusable React component architecture and HubSpot integration, streamlining meeting workflows, improving UX, and significantly reducing feature implementation time through shared component patterns." + ], "companyLogo": "../icons/srclogicx.png", "companyURL": "https://srclogix.com/", "workingTech": [ diff --git a/src/components/Experiences/Experiences.tsx b/src/components/Experiences/Experiences.tsx index d17d7eb..de53a93 100644 --- a/src/components/Experiences/Experiences.tsx +++ b/src/components/Experiences/Experiences.tsx @@ -1,9 +1,174 @@ -import { useEffect } from 'react'; +import { motion } from 'motion/react'; +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import experiencesData from '../../assets/data/experiencesData.json'; import scrollToPosition from '../../helpers/scrollToPosition'; import { getDuration, getMonthNameYear } from '../../helpers/getDate'; import { SectionHeader, SkillsView } from '../SharedComponents'; +const descriptionRootClass = + 'leading-relaxed text-white/65 text-[0.8125rem] min-[768px]:text-base [&_p]:my-0 [&_p+p]:mt-2 [&_strong]:font-semibold [&_strong]:text-white/85'; + +const expandTransition = { duration: 0.35, ease: [0.25, 0.1, 0.25, 1] as const }; + +type ExperienceDescriptionProps = { + /** HTML snippet for the opening paragraph(s); plain text also works */ + descriptionHtml: string; + /** Optional list items — each string is HTML (e.g. ) rendered inside one
  • */ + bulletPointsHtml?: string[]; +}; + +const ExperienceDescription = ({ descriptionHtml, bulletPointsHtml }: ExperienceDescriptionProps) => { + const wrapRef = useRef(null); + const contentRef = useRef(null); + + const bulletsKey = bulletPointsHtml?.join('\u0000') ?? ''; + const [measured, setMeasured] = useState(false); + const [needsToggle, setNeedsToggle] = useState(false); + const [expanded, setExpanded] = useState(false); + const [lineClamp, setLineClamp] = useState(true); + const [collapsedH, setCollapsedH] = useState(0); + const [fullH, setFullH] = useState(0); + + const measure = useCallback(() => { + const root = contentRef.current; + const hasIntro = descriptionHtml.trim().length > 0; + const hasBullets = bulletPointsHtml?.some((b) => b.trim()) ?? false; + if (!root || (!hasIntro && !hasBullets)) { + setNeedsToggle(false); + setMeasured(true); + return; + } + + const wrap = wrapRef.current; + if (wrap) { + wrap.style.height = 'auto'; + } + + root.classList.add('line-clamp-3'); + const clamped = root.offsetHeight; + root.classList.remove('line-clamp-3'); + const full = root.scrollHeight; + + if (wrap) { + wrap.style.height = ''; + } + + const overflow = full > clamped + 2; + setNeedsToggle(overflow); + if (overflow) { + setCollapsedH(clamped); + setFullH(full); + } else { + setExpanded(false); + setLineClamp(false); + } + setMeasured(true); + }, [descriptionHtml, bulletsKey]); + + useLayoutEffect(() => { + measure(); + }, [measure]); + + useLayoutEffect(() => { + const onResize = () => measure(); + window.addEventListener('resize', onResize); + return () => window.removeEventListener('resize', onResize); + }, [measure]); + + const handleShowMore = () => { + setLineClamp(false); + setExpanded(true); + }; + + const handleShowLess = () => { + setExpanded(false); + }; + + const handleAnimationComplete = () => { + if (!expanded) { + setLineClamp(true); + } + }; + + const clampClass = lineClamp && !expanded ? 'line-clamp-3' : ''; + + const hasIntro = descriptionHtml.trim().length > 0; + + const descriptionBody = ( + <> + {hasIntro ? ( +
    + ) : null} + {bulletPointsHtml?.length ? ( +
      + {bulletPointsHtml.map((html, i) => ( +
    • + ))} +
    + ) : null} + + ); + + if (!measured) { + return ( +
    + {descriptionBody} +
    + ); + } + + if (!needsToggle) { + return ( +
    + {descriptionBody} +
    + ); + } + + return ( +
    + +
    + {descriptionBody} +
    +
    + + {expanded ? 'Show Less' : 'Show More'} + + + + +
    + ); +}; + const contentBeforeOdd = "before:content-[''] before:absolute before:top-4 before:right-full before:border-[7px] before:border-transparent before:border-r-[#1a3344] " + 'min-[1170px]:before:top-[30px] min-[1170px]:before:left-full min-[1170px]:before:right-auto min-[1170px]:before:border-l-[#1a3344] min-[1170px]:before:border-r-transparent'; @@ -72,7 +237,7 @@ const Experiences = () => {

    {companyData?.name}

    -
    +
    {
    -

    - {' '} - {companyData?.description}{' '} -

    + From 028244a41d7923c110ddc6b6d72cc6dfa9970013 Mon Sep 17 00:00:00 2001 From: Numan Date: Sat, 2 May 2026 11:30:42 +0530 Subject: [PATCH 3/4] [Experience] experience description section changes --- src/assets/data/experiencesData.json | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/assets/data/experiencesData.json b/src/assets/data/experiencesData.json index 3f4b035..151827e 100644 --- a/src/assets/data/experiencesData.json +++ b/src/assets/data/experiencesData.json @@ -16,13 +16,15 @@ "companyLogo": "../icons/dbox.png", "companyURL": "https://darwinbox.com/", "workingTech": [ - "ReactJS", + "React", "Angular", "Typescript", "Node.js", "Express.js", "Redux", + "Zustand", "RxJS", + "Tailwind CSS", "SCSS", "Bootstrap", "jQuery", @@ -48,10 +50,10 @@ "companyLogo": "../icons/w42.png", "companyURL": "https://wealth42.com/", "workingTech": [ - "ReactJS", + "React", + "Next.js", "PWA", - "NextJS", - "Redux-ToolKit", + "Redux Toolkit", "Tailwind CSS", "Redux", "SendBird", @@ -78,14 +80,13 @@ "companyLogo": "../icons/srclogicx.png", "companyURL": "https://srclogix.com/", "workingTech": [ - "ReactJS", - "NodeJS", - "NextJS", - "ExpressJS", + "React", + "Node.js", + "Next.js", + "Express.js", "Redux", - "AWS-S3", - "MongoDB", - "DynamoDB" + "AWS S3", + "MongoDB" ], "sidePic": { "url": "../images/num-with-lappy-flowers.svg", From bee5035d62bdec5802a2732589a3d9992191dfbf Mon Sep 17 00:00:00 2001 From: Numan Date: Sat, 2 May 2026 12:05:20 +0530 Subject: [PATCH 4/4] [github-actions] Auto deploy to firebase, if PR merged to 'firebase-deploy' branch --- .firebaserc | 5 +++++ .github/workflows/firebase-hosting-merge.yml | 20 ++++++++++++++++++ .../firebase-hosting-pull-request.yml | 21 +++++++++++++++++++ .gitignore | 4 +--- firebase.json | 16 ++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .firebaserc create mode 100644 .github/workflows/firebase-hosting-merge.yml create mode 100644 .github/workflows/firebase-hosting-pull-request.yml create mode 100644 firebase.json diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..bdb9c94 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "numan-dev" + } +} diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml new file mode 100644 index 0000000..d3cd364 --- /dev/null +++ b/.github/workflows/firebase-hosting-merge.yml @@ -0,0 +1,20 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on merge +on: + push: + branches: + - firebase-deploy +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm ci && npm run build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: ${{ secrets.GITHUB_TOKEN }} + firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_NUMAN_DEV }} + channelId: live + projectId: numan-dev diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml new file mode 100644 index 0000000..6205e4b --- /dev/null +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -0,0 +1,21 @@ +# This file was auto-generated by the Firebase CLI +# https://github.com/firebase/firebase-tools + +name: Deploy to Firebase Hosting on PR +on: pull_request +permissions: + checks: write + contents: read + pull-requests: write +jobs: + build_and_preview: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: npm ci && npm run build + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: ${{ secrets.GITHUB_TOKEN }} + firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_NUMAN_DEV }} + projectId: numan-dev diff --git a/.gitignore b/.gitignore index 75ce642..47fb3ac 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,8 @@ /build # firebase -.github .firebase -.firebaserc -firebase.json +firebase-debug.log # misc .DS_Store diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..340ed5b --- /dev/null +++ b/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": "build", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +}