From 9a93ee5faf00dcf98cd9ccf572b7895e4fc6a40c Mon Sep 17 00:00:00 2001 From: Suyash Jain Date: Sun, 18 Jan 2026 23:59:19 +0530 Subject: [PATCH 1/3] refactor: update ContributorsList component --- frontend/src/components/ContributorsList.tsx | 78 ++++++++++++++++++ .../src/components/MenteeContributorsList.tsx | 58 ++----------- .../src/components/TopContributorsList.tsx | 81 ++++--------------- 3 files changed, 102 insertions(+), 115 deletions(-) create mode 100644 frontend/src/components/ContributorsList.tsx diff --git a/frontend/src/components/ContributorsList.tsx b/frontend/src/components/ContributorsList.tsx new file mode 100644 index 0000000000..b3e5082a0f --- /dev/null +++ b/frontend/src/components/ContributorsList.tsx @@ -0,0 +1,78 @@ +import upperFirst from 'lodash/upperFirst' +import Image from 'next/image' +import Link from 'next/link' +import { useState } from 'react' +import type { IconType } from 'react-icons' +import type { Contributor } from 'types/contributor' +import AnchorTitle from 'components/AnchorTitle' +import SecondaryCard from 'components/SecondaryCard' +import ShowMoreButton from 'components/ShowMoreButton' + +interface ContributorsListProps { + contributors: Contributor[] + label?: string + maxInitialDisplay?: number + icon?: IconType + getUrl: (login: string) => string +} + +const ContributorsList = ({ + contributors, + label = 'Contributors', + maxInitialDisplay = 12, + icon, + getUrl, +}: ContributorsListProps) => { + const [showAllContributors, setShowAllContributors] = useState(false) + + const toggleContributors = () => setShowAllContributors(!showAllContributors) + + const displayContributors = showAllContributors + ? contributors + : contributors.slice(0, maxInitialDisplay) + + if (contributors.length === 0) { + return null + } + + return ( + + + + } + > +
+ {displayContributors.map((item) => ( +
+
+ {item?.name + + {upperFirst(item.name) || upperFirst(item.login)} + +
+
+ ))} +
+ {contributors.length > maxInitialDisplay && } +
+ ) +} + +export default ContributorsList diff --git a/frontend/src/components/MenteeContributorsList.tsx b/frontend/src/components/MenteeContributorsList.tsx index 9c1d94f9b4..da29c60742 100644 --- a/frontend/src/components/MenteeContributorsList.tsx +++ b/frontend/src/components/MenteeContributorsList.tsx @@ -1,12 +1,6 @@ -import upperFirst from 'lodash/upperFirst' -import Image from 'next/image' -import Link from 'next/link' -import { useState } from 'react' import type { IconType } from 'react-icons' import type { Contributor } from 'types/contributor' -import AnchorTitle from 'components/AnchorTitle' -import SecondaryCard from 'components/SecondaryCard' -import ShowMoreButton from 'components/ShowMoreButton' +import ContributorsList from 'components/ContributorsList' interface MenteeContributorsListProps { contributors: Contributor[] @@ -25,55 +19,17 @@ const MenteeContributorsList = ({ programKey, moduleKey, }: MenteeContributorsListProps) => { - const [showAllContributors, setShowAllContributors] = useState(false) - - const toggleContributors = () => setShowAllContributors(!showAllContributors) - - const displayContributors = showAllContributors - ? contributors - : contributors.slice(0, maxInitialDisplay) - - if (contributors.length === 0) { - return null - } - const getMenteeUrl = (login: string) => `/my/mentorship/programs/${programKey}/modules/${moduleKey}/mentees/${login}` return ( - - - - } - > -
- {displayContributors.map((item, index) => ( -
-
- {item?.name - - {upperFirst(item.name) || upperFirst(item.login)} - -
-
- ))} -
- {contributors.length > maxInitialDisplay && } -
+ getUrl={getMenteeUrl} + /> ) } diff --git a/frontend/src/components/TopContributorsList.tsx b/frontend/src/components/TopContributorsList.tsx index d3bc34ca7f..384c952ad4 100644 --- a/frontend/src/components/TopContributorsList.tsx +++ b/frontend/src/components/TopContributorsList.tsx @@ -1,75 +1,28 @@ -import upperFirst from 'lodash/upperFirst' -import Image from 'next/image' -import Link from 'next/link' -import { useState } from 'react' import type { IconType } from 'react-icons' import type { Contributor } from 'types/contributor' import { getMemberUrl } from 'utils/urlFormatter' -import AnchorTitle from 'components/AnchorTitle' -import SecondaryCard from 'components/SecondaryCard' -import ShowMoreButton from 'components/ShowMoreButton' +import ContributorsList from 'components/ContributorsList' -const TopContributorsList = ({ - contributors, - label = 'Top Contributors', - maxInitialDisplay = 12, - icon, -}: { +interface TopContributorsListProps { contributors: Contributor[] label?: string maxInitialDisplay?: number icon?: IconType -}) => { - const [showAllContributors, setShowAllContributors] = useState(false) - - const toggleContributors = () => setShowAllContributors(!showAllContributors) - - const displayContributors = showAllContributors - ? contributors - : contributors.slice(0, maxInitialDisplay) - - if (contributors.length === 0) { - return - } - - return ( - - - - } - > -
- {displayContributors.map((item) => ( -
-
- {item?.name - - {upperFirst(item.name) || upperFirst(item.login)} - -
-
- ))} -
- {contributors.length > maxInitialDisplay && } -
- ) } +const TopContributorsList = ({ + contributors, + label = 'Top Contributors', + maxInitialDisplay = 12, + icon, +}: TopContributorsListProps) => ( + +) + export default TopContributorsList From dcafa09d90d477f64fab7aa6ab45f604722d2824 Mon Sep 17 00:00:00 2001 From: Suyash Jain Date: Mon, 19 Jan 2026 00:49:23 +0530 Subject: [PATCH 2/3] fix: sync ShowMoreButton state and fix avatar URL --- frontend/src/components/ContributorsList.tsx | 9 +++++++-- frontend/src/components/ShowMoreButton.tsx | 10 +++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ContributorsList.tsx b/frontend/src/components/ContributorsList.tsx index b3e5082a0f..dbe8cf0970 100644 --- a/frontend/src/components/ContributorsList.tsx +++ b/frontend/src/components/ContributorsList.tsx @@ -55,7 +55,7 @@ const ContributorsList = ({ alt={item?.name ? `${item.name}'s avatar` : 'Contributor avatar'} className="rounded-full" height={24} - src={`${item?.avatarUrl}&s=60`} + src={`${item?.avatarUrl}${item?.avatarUrl?.includes('?') ? '&' : '?'}s=60`} title={item?.name || item?.login} width={24} /> @@ -70,7 +70,12 @@ const ContributorsList = ({ ))} - {contributors.length > maxInitialDisplay && } + {contributors.length > maxInitialDisplay && ( + + )} ) } diff --git a/frontend/src/components/ShowMoreButton.tsx b/frontend/src/components/ShowMoreButton.tsx index 54f01a3ff1..94f4554383 100644 --- a/frontend/src/components/ShowMoreButton.tsx +++ b/frontend/src/components/ShowMoreButton.tsx @@ -1,12 +1,12 @@ import { Button } from '@heroui/button' -import { useState } from 'react' -import { FaChevronDown, FaChevronUp } from 'react-icons/fa' -const ShowMoreButton = ({ onToggle }: { onToggle: () => void }) => { - const [isExpanded, setIsExpanded] = useState(false) +import { FaChevronDown, FaChevronUp } from 'react-icons/fa' +const ShowMoreButton = ({ + onToggle, + isExpanded, +}: { onToggle: () => void; isExpanded: boolean }) => { const handleToggle = () => { - setIsExpanded(!isExpanded) onToggle() } From 8943c6ad10685bf2f7bd968a593ba85747911832 Mon Sep 17 00:00:00 2001 From: Kate Date: Tue, 20 Jan 2026 22:09:11 -0800 Subject: [PATCH 3/3] Update code --- ...est.tsx => ContributorsList.a11y.test.tsx} | 9 +- .../unit/components/CardDetailsPage.test.tsx | 22 +-- ...ist.test.tsx => ContributorsList.test.tsx} | 152 +++++++++++++----- .../unit/components/SingleModuleCard.test.tsx | 19 ++- frontend/src/app/about/page.tsx | 6 +- frontend/src/app/page.tsx | 7 +- frontend/src/components/CardDetailsPage.tsx | 19 ++- frontend/src/components/ContributorsList.tsx | 115 +++++++------ .../src/components/MenteeContributorsList.tsx | 36 ----- frontend/src/components/ShowMoreButton.tsx | 10 +- frontend/src/components/SingleModuleCard.tsx | 15 +- .../src/components/TopContributorsList.tsx | 28 ---- frontend/src/utils/urlFormatter.ts | 2 + 13 files changed, 233 insertions(+), 207 deletions(-) rename frontend/__tests__/a11y/components/{TopContributorsList.a11y.test.tsx => ContributorsList.a11y.test.tsx} (81%) rename frontend/__tests__/unit/components/{TopContributorsList.test.tsx => ContributorsList.test.tsx} (81%) delete mode 100644 frontend/src/components/MenteeContributorsList.tsx delete mode 100644 frontend/src/components/TopContributorsList.tsx diff --git a/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx similarity index 81% rename from frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx rename to frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx index a763ea4e34..db20e732a5 100644 --- a/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx +++ b/frontend/__tests__/a11y/components/ContributorsList.a11y.test.tsx @@ -2,7 +2,8 @@ import { render } from '@testing-library/react' import { axe, toHaveNoViolations } from 'jest-axe' import { ReactNode } from 'react' import { Contributor } from 'types/contributor' -import TopContributorsList from 'components/TopContributorsList' +import { getMemberUrl } from 'utils/urlFormatter' +import ContributorsList from 'components/ContributorsList' expect.extend(toHaveNoViolations) @@ -50,9 +51,11 @@ const mockContributors: Contributor[] = [ }, ] -describe('TopContributorsList Accessibility', () => { +describe('ContributorsList Accessibility', () => { it('should not have any accessibility violations', async () => { - const { container } = render() + const { container } = render( + + ) const results = await axe(container) diff --git a/frontend/__tests__/unit/components/CardDetailsPage.test.tsx b/frontend/__tests__/unit/components/CardDetailsPage.test.tsx index b387ad4270..29ccee235d 100644 --- a/frontend/__tests__/unit/components/CardDetailsPage.test.tsx +++ b/frontend/__tests__/unit/components/CardDetailsPage.test.tsx @@ -349,25 +349,27 @@ jest.mock('components/ToggleableList', () => ({ ), })) -jest.mock('components/TopContributorsList', () => ({ +jest.mock('components/ContributorsList', () => ({ __esModule: true, default: ({ contributors, maxInitialDisplay, // eslint-disable-next-line @typescript-eslint/no-unused-vars icon, + label = 'Contributors', // eslint-disable-next-line @typescript-eslint/no-unused-vars - label, + getUrl, ...props }: { contributors: unknown[] icon?: unknown label?: string maxInitialDisplay: number + getUrl: (login: string) => string [key: string]: unknown }) => ( -
- Top Contributors ({contributors.length} items, max display: {maxInitialDisplay}) +
+ {label} ({contributors.length} items, max display: {maxInitialDisplay})
), })) @@ -951,7 +953,7 @@ describe('CardDetailsPage', () => { it('passes correct props to child components', () => { render() - expect(screen.getByTestId('top-contributors-list')).toHaveTextContent( + expect(screen.getByTestId('contributors-list')).toHaveTextContent( 'Top Contributors (2 items, max display: 12)' ) }) @@ -1041,9 +1043,7 @@ describe('CardDetailsPage', () => { ) expect(screen.getByTestId('health-metrics')).toHaveTextContent('Health Metrics (1 items)') - expect(screen.getByTestId('top-contributors-list')).toHaveTextContent( - 'Top Contributors (2 items' - ) + expect(screen.getByTestId('contributors-list')).toHaveTextContent('Top Contributors (2 items') expect(screen.getByTestId('repositories-card')).toHaveTextContent('Repositories (2 items)') }) @@ -1152,7 +1152,7 @@ describe('CardDetailsPage', () => { render() - expect(screen.getByTestId('top-contributors-list')).toHaveTextContent( + expect(screen.getByTestId('contributors-list')).toHaveTextContent( 'Top Contributors (50 items, max display: 12)' ) }) @@ -1212,7 +1212,7 @@ describe('CardDetailsPage', () => { render() - expect(screen.getByTestId('top-contributors-list')).toHaveTextContent( + expect(screen.getByTestId('contributors-list')).toHaveTextContent( 'Top Contributors (1000 items, max display: 12)' ) }) @@ -1362,7 +1362,7 @@ describe('CardDetailsPage', () => { expect(screen.getByText('Project summary text')).toBeInTheDocument() expect(screen.getByText('User summary content')).toBeInTheDocument() expect(screen.getByTestId('health-metrics')).toBeInTheDocument() - expect(screen.getByTestId('top-contributors-list')).toBeInTheDocument() + expect(screen.getByTestId('contributors-list')).toBeInTheDocument() expect(screen.getByTestId('repositories-card')).toBeInTheDocument() expect(screen.getByTestId('sponsor-card')).toBeInTheDocument() }) diff --git a/frontend/__tests__/unit/components/TopContributorsList.test.tsx b/frontend/__tests__/unit/components/ContributorsList.test.tsx similarity index 81% rename from frontend/__tests__/unit/components/TopContributorsList.test.tsx rename to frontend/__tests__/unit/components/ContributorsList.test.tsx index 83d374cf7f..72347e59b8 100644 --- a/frontend/__tests__/unit/components/TopContributorsList.test.tsx +++ b/frontend/__tests__/unit/components/ContributorsList.test.tsx @@ -3,7 +3,8 @@ import React from 'react' import { FaUsers } from 'react-icons/fa6' import { render } from 'wrappers/testUtil' import type { Contributor } from 'types/contributor' -import TopContributorsList from 'components/TopContributorsList' +import { getMemberUrl } from 'utils/urlFormatter' +import ContributorsList from 'components/ContributorsList' jest.mock('next/link', () => ({ __esModule: true, @@ -178,9 +179,10 @@ const mockContributors: Contributor[] = [ }, ] -describe('TopContributorsList Component', () => { +describe('ContributorsList Component', () => { const defaultProps = { contributors: mockContributors, + getUrl: getMemberUrl, } afterEach(() => { @@ -188,8 +190,8 @@ describe('TopContributorsList Component', () => { }) describe('Renders successfully with minimal required props', () => { - it('renders with minimal props (only contributors)', () => { - render() + it('renders with minimal props (only contributors and getUrl)', () => { + render() expect(screen.getByTestId('secondary-card')).toBeInTheDocument() expect(screen.getByTestId('anchor-title')).toBeInTheDocument() @@ -198,12 +200,12 @@ describe('TopContributorsList Component', () => { }) it('renders component without crashing', () => { - const { container } = render() + const { container } = render() expect(container).toBeInTheDocument() }) it('renders without crashing with empty contributors array', () => { - render() + render() // Component returns early for empty array, so no secondary card should be rendered expect(screen.queryByTestId('secondary-card')).not.toBeInTheDocument() }) @@ -211,7 +213,7 @@ describe('TopContributorsList Component', () => { describe('Conditional rendering logic', () => { it('does not render anything when contributors array is empty', () => { - render() + render() // Component returns early for empty array, so no secondary card should be rendered expect(screen.queryByTestId('secondary-card')).not.toBeInTheDocument() }) @@ -219,7 +221,11 @@ describe('TopContributorsList Component', () => { it('renders show more/less button only when contributors exceed maxInitialDisplay', () => { // Test with few contributors (should not show button) const { rerender } = render( - + ) expect(screen.queryByRole('button')).not.toBeInTheDocument() @@ -232,7 +238,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - rerender() + rerender( + + ) expect(screen.getByRole('button')).toBeInTheDocument() expect(screen.getByText('Show more')).toBeInTheDocument() }) @@ -246,7 +258,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) // Should only show 5 initially expect(screen.getAllByTestId('contributor-avatar')).toHaveLength(5) @@ -269,7 +287,7 @@ describe('TopContributorsList Component', () => { }, ] - render() + render() expect(screen.getByText('Alex Developer')).toBeInTheDocument() expect(screen.getByText('Contributor2')).toBeInTheDocument() // capitalize utility should be applied @@ -279,15 +297,15 @@ describe('TopContributorsList Component', () => { describe('Prop-based behavior', () => { it('uses custom label when provided', () => { const customLabel = 'Featured Contributors' - render() + render() expect(screen.getByText(customLabel)).toBeInTheDocument() }) it('uses default label when not provided', () => { - render() + render() - expect(screen.getByText('Top Contributors')).toBeInTheDocument() + expect(screen.getByText('Contributors')).toBeInTheDocument() }) it('respects custom maxInitialDisplay prop', () => { @@ -299,19 +317,25 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) expect(screen.getAllByTestId('contributor-avatar')).toHaveLength(3) expect(screen.getByRole('button')).toBeInTheDocument() }) it('displays icon when provided', () => { - render() + render() expect(screen.getByTestId('card-icon')).toBeInTheDocument() }) it('does not display icon when not provided', () => { - render() + render() expect(screen.queryByTestId('card-icon')).not.toBeInTheDocument() }) }) @@ -326,7 +350,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) // Initially shows 5 contributors expect(screen.getAllByTestId('contributor-avatar')).toHaveLength(5) @@ -359,7 +389,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) const toggleButton = screen.getByRole('button') @@ -385,7 +421,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) // Initial state - collapsed expect(screen.getAllByTestId('contributor-avatar')).toHaveLength(5) @@ -408,7 +450,9 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) // Should show first 3 contributors expect(screen.getByText('User 0')).toBeInTheDocument() @@ -428,7 +472,7 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render() // Should show 12 by default (based on component default) expect(screen.getAllByTestId('contributor-avatar')).toHaveLength(12) @@ -445,7 +489,7 @@ describe('TopContributorsList Component', () => { }, ] - render() + render() expect(screen.getByText('Testuser')).toBeInTheDocument() }) @@ -460,16 +504,16 @@ describe('TopContributorsList Component', () => { }, ] - render() + render() const avatar = screen.getByTestId('contributor-avatar') - expect(avatar).toHaveAttribute('src', '&s=60') // Should append size parameter even with empty URL + expect(avatar).toHaveAttribute('src', '?s=60') // Should append size parameter even with empty URL }) }) describe('Text and content rendering', () => { it('renders contributor names correctly', () => { - render() + render() expect(screen.getByText('Alex Developer')).toBeInTheDocument() expect(screen.getByText('Jane Developer')).toBeInTheDocument() @@ -485,7 +529,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) expect(screen.getByText('Show more')).toBeInTheDocument() @@ -494,7 +544,7 @@ describe('TopContributorsList Component', () => { }) it('renders title with proper structure', () => { - render() + render() expect(screen.getByTestId('anchor-title')).toBeInTheDocument() expect(screen.getByText('Custom Title')).toBeInTheDocument() @@ -518,14 +568,20 @@ describe('TopContributorsList Component', () => { }, ] - render() + render() // Should still render without crashing expect(screen.getByTestId('secondary-card')).toBeInTheDocument() }) it('handles zero maxInitialDisplay value', () => { - render() + render( + + ) // Should still show button since contributors length > 0 expect(screen.getByRole('button')).toBeInTheDocument() @@ -533,14 +589,26 @@ describe('TopContributorsList Component', () => { }) it('handles negative maxInitialDisplay value', () => { - render() + render( + + ) // slice with negative number should still work expect(screen.getByTestId('secondary-card')).toBeInTheDocument() }) it('handles very large maxInitialDisplay value', () => { - render() + render( + + ) // Should show all contributors and no button expect(screen.getAllByTestId('contributor-avatar')).toHaveLength(3) @@ -550,7 +618,7 @@ describe('TopContributorsList Component', () => { describe('Accessibility roles and labels', () => { it('renders proper image alt text and titles', () => { - render() + render() const avatars = screen.getAllByTestId('contributor-avatar') expect(avatars[0]).toHaveAttribute('alt', "Alex Developer's avatar") @@ -562,7 +630,7 @@ describe('TopContributorsList Component', () => { }) it('renders proper link titles and hrefs', () => { - render() + render() const links = screen.getAllByTestId('contributor-link') expect(links[0]).toHaveAttribute('href', '/members/developer1') @@ -582,7 +650,13 @@ describe('TopContributorsList Component', () => { name: `User ${index}`, })) - render() + render( + + ) const button = screen.getByRole('button') expect(button).toBeInTheDocument() @@ -592,7 +666,7 @@ describe('TopContributorsList Component', () => { describe('DOM structure / classNames / styles', () => { it('renders correct CSS classes on components', () => { - render() + render() const contributorItems = screen .getAllByTestId('contributor-avatar') @@ -610,7 +684,7 @@ describe('TopContributorsList Component', () => { }) it('renders proper grid structure', () => { - const { container } = render() + const { container } = render() const gridContainer = container.querySelector('.grid') expect(gridContainer).toHaveClass( @@ -622,7 +696,7 @@ describe('TopContributorsList Component', () => { }) it('renders avatar with correct dimensions and styling', () => { - render() + render() const avatars = screen.getAllByTestId('contributor-avatar') for (const avatar of avatars) { @@ -633,7 +707,7 @@ describe('TopContributorsList Component', () => { }) it('renders contributor links with proper styling', () => { - render() + render() const links = screen.getAllByTestId('contributor-link') diff --git a/frontend/__tests__/unit/components/SingleModuleCard.test.tsx b/frontend/__tests__/unit/components/SingleModuleCard.test.tsx index e356e6bf13..825df4b8e0 100644 --- a/frontend/__tests__/unit/components/SingleModuleCard.test.tsx +++ b/frontend/__tests__/unit/components/SingleModuleCard.test.tsx @@ -67,10 +67,17 @@ jest.mock('components/ModuleCard', () => ({ }), })) -jest.mock('components/TopContributorsList', () => ({ +jest.mock('components/ContributorsList', () => ({ __esModule: true, - default: ({ contributors, label }: { contributors: unknown[]; label: string }) => ( -
+ default: ({ + contributors, + label, + }: { + contributors: unknown[] + label: string + getUrl: (login: string) => string + }) => ( +
{label}: {contributors.length} contributors @@ -153,7 +160,7 @@ describe('SingleModuleCard', () => { it('renders mentors list when mentors exist', () => { render() - expect(screen.getByTestId('top-contributors-list')).toBeInTheDocument() + expect(screen.getByTestId('contributors-list')).toBeInTheDocument() expect(screen.getByText('Mentors: 2 contributors')).toBeInTheDocument() }) @@ -161,7 +168,7 @@ describe('SingleModuleCard', () => { const moduleWithoutMentors = { ...mockModule, mentors: [] } render() - expect(screen.queryByTestId('top-contributors-list')).not.toBeInTheDocument() + expect(screen.queryByTestId('contributors-list')).not.toBeInTheDocument() }) it('renders module link with correct href', () => { @@ -222,7 +229,7 @@ describe('SingleModuleCard', () => { render() expect(screen.getByText('Test Module')).toBeInTheDocument() - expect(screen.queryByTestId('top-contributors-list')).not.toBeInTheDocument() + expect(screen.queryByTestId('contributors-list')).not.toBeInTheDocument() }) it('handles undefined admins array gracefully', () => { diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index 122c96b8e6..f447e70caa 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -24,13 +24,14 @@ import { projectTimeline, projectStory, } from 'utils/aboutData' +import { getMemberUrl } from 'utils/urlFormatter' import AnchorTitle from 'components/AnchorTitle' +import ContributorsList from 'components/ContributorsList' import Leaders from 'components/Leaders' import Markdown from 'components/MarkdownWrapper' import SecondaryCard from 'components/SecondaryCard' import ShowMoreButton from 'components/ShowMoreButton' import AboutSkeleton from 'components/skeletons/AboutSkeleton' -import TopContributorsList from 'components/TopContributorsList' const leaders = { arkid15r: 'CCSP, CISSP, CSSLP', @@ -150,11 +151,12 @@ const About = () => { {topContributors && ( - )} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 8520e8481c..d16568fbfd 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -27,9 +27,11 @@ import type { Chapter } from 'types/chapter' import type { Event } from 'types/event' import { formatDate, formatDateRange } from 'utils/dateFormatter' +import { getMemberUrl } from 'utils/urlFormatter' import AnchorTitle from 'components/AnchorTitle' import CalendarButton from 'components/CalendarButton' import ChapterMapWrapper from 'components/ChapterMapWrapper' +import ContributorsList from 'components/ContributorsList' import LeadersList from 'components/LeadersList' import LoadingSpinner from 'components/LoadingSpinner' import MovingLogos from 'components/LogoCarousel' @@ -40,7 +42,6 @@ import RecentIssues from 'components/RecentIssues' import RecentPullRequests from 'components/RecentPullRequests' import RecentReleases from 'components/RecentReleases' import SecondaryCard from 'components/SecondaryCard' -import TopContributorsList from 'components/TopContributorsList' import { TruncatedText } from 'components/TruncatedText' export default function Home() { @@ -314,10 +315,12 @@ export default function Home() { }} />
-
diff --git a/frontend/src/components/CardDetailsPage.tsx b/frontend/src/components/CardDetailsPage.tsx index 6ced0d1490..1bd3c19057 100644 --- a/frontend/src/components/CardDetailsPage.tsx +++ b/frontend/src/components/CardDetailsPage.tsx @@ -13,18 +13,19 @@ import type { ExtendedSession } from 'types/auth' import type { DetailsCardProps } from 'types/card' import { IS_PROJECT_HEALTH_ENABLED } from 'utils/env.client' import { scrollToAnchor } from 'utils/scrollToAnchor' +import { getMemberUrl, getMenteeUrl } from 'utils/urlFormatter' import { getSocialIcon } from 'utils/urlIconMappings' import AnchorTitle from 'components/AnchorTitle' import ChapterMapWrapper from 'components/ChapterMapWrapper' import ContributionHeatmap from 'components/ContributionHeatmap' import ContributionStats from 'components/ContributionStats' +import ContributorsList from 'components/ContributorsList' import EntityActions from 'components/EntityActions' import HealthMetrics from 'components/HealthMetrics' import InfoBlock from 'components/InfoBlock' import Leaders from 'components/Leaders' import LeadersList from 'components/LeadersList' import Markdown from 'components/MarkdownWrapper' -import MenteeContributorsList from 'components/MenteeContributorsList' import MetricsScoreCircle from 'components/MetricsScoreCircle' import Milestones from 'components/Milestones' import ModuleCard from 'components/ModuleCard' @@ -36,7 +37,6 @@ import SecondaryCard from 'components/SecondaryCard' import SponsorCard from 'components/SponsorCard' import StatusBadge from 'components/StatusBadge' import ToggleableList from 'components/ToggleableList' -import TopContributorsList from 'components/TopContributorsList' export type CardType = | 'chapter' @@ -293,36 +293,39 @@ const DetailsCard = ({
)} {topContributors && ( - )} {admins && admins.length > 0 && type === 'program' && ( - )} {mentors && mentors.length > 0 && ( - )} {mentees && mentees.length > 0 && ( - getMenteeUrl(programKey || '', entityKey || '', login)} /> )} {showIssuesAndMilestones(type) && ( diff --git a/frontend/src/components/ContributorsList.tsx b/frontend/src/components/ContributorsList.tsx index dbe8cf0970..ac2d9fa56e 100644 --- a/frontend/src/components/ContributorsList.tsx +++ b/frontend/src/components/ContributorsList.tsx @@ -9,75 +9,70 @@ import SecondaryCard from 'components/SecondaryCard' import ShowMoreButton from 'components/ShowMoreButton' interface ContributorsListProps { - contributors: Contributor[] - label?: string - maxInitialDisplay?: number - icon?: IconType - getUrl: (login: string) => string + contributors: Contributor[] + label?: string + maxInitialDisplay?: number + icon?: IconType + getUrl: (login: string) => string } const ContributorsList = ({ - contributors, - label = 'Contributors', - maxInitialDisplay = 12, - icon, - getUrl, + contributors, + label = 'Contributors', + maxInitialDisplay = 12, + icon, + getUrl, }: ContributorsListProps) => { - const [showAllContributors, setShowAllContributors] = useState(false) + const [showAllContributors, setShowAllContributors] = useState(false) - const toggleContributors = () => setShowAllContributors(!showAllContributors) + const toggleContributors = () => setShowAllContributors(!showAllContributors) - const displayContributors = showAllContributors - ? contributors - : contributors.slice(0, maxInitialDisplay) + const displayContributors = showAllContributors + ? contributors + : contributors.slice(0, maxInitialDisplay) - if (contributors.length === 0) { - return null - } + if (contributors.length === 0) { + return null + } - return ( - - -
- } - > -
- {displayContributors.map((item) => ( -
-
- {item?.name - - {upperFirst(item.name) || upperFirst(item.login)} - -
-
- ))} + return ( + + +
+ } + > +
+ {displayContributors.map((item) => ( +
+
+ {item?.name + + {upperFirst(item.name) || upperFirst(item.login)} +
- {contributors.length > maxInitialDisplay && ( - - )} - - ) +
+ ))} +
+ {contributors.length > maxInitialDisplay && } + + ) } export default ContributorsList diff --git a/frontend/src/components/MenteeContributorsList.tsx b/frontend/src/components/MenteeContributorsList.tsx deleted file mode 100644 index da29c60742..0000000000 --- a/frontend/src/components/MenteeContributorsList.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { IconType } from 'react-icons' -import type { Contributor } from 'types/contributor' -import ContributorsList from 'components/ContributorsList' - -interface MenteeContributorsListProps { - contributors: Contributor[] - label?: string - maxInitialDisplay?: number - icon?: IconType - programKey: string - moduleKey: string -} - -const MenteeContributorsList = ({ - contributors, - label = 'Mentees', - maxInitialDisplay = 12, - icon, - programKey, - moduleKey, -}: MenteeContributorsListProps) => { - const getMenteeUrl = (login: string) => - `/my/mentorship/programs/${programKey}/modules/${moduleKey}/mentees/${login}` - - return ( - - ) -} - -export default MenteeContributorsList diff --git a/frontend/src/components/ShowMoreButton.tsx b/frontend/src/components/ShowMoreButton.tsx index 94f4554383..54f01a3ff1 100644 --- a/frontend/src/components/ShowMoreButton.tsx +++ b/frontend/src/components/ShowMoreButton.tsx @@ -1,12 +1,12 @@ import { Button } from '@heroui/button' - +import { useState } from 'react' import { FaChevronDown, FaChevronUp } from 'react-icons/fa' -const ShowMoreButton = ({ - onToggle, - isExpanded, -}: { onToggle: () => void; isExpanded: boolean }) => { +const ShowMoreButton = ({ onToggle }: { onToggle: () => void }) => { + const [isExpanded, setIsExpanded] = useState(false) + const handleToggle = () => { + setIsExpanded(!isExpanded) onToggle() } diff --git a/frontend/src/components/SingleModuleCard.tsx b/frontend/src/components/SingleModuleCard.tsx index 68c76f4358..68e5f7ee8c 100644 --- a/frontend/src/components/SingleModuleCard.tsx +++ b/frontend/src/components/SingleModuleCard.tsx @@ -9,11 +9,11 @@ import { HiUserGroup } from 'react-icons/hi' import { ExtendedSession } from 'types/auth' import type { Module } from 'types/mentorship' import { formatDate } from 'utils/dateFormatter' +import { getMemberUrl, getMenteeUrl } from 'utils/urlFormatter' +import ContributorsList from 'components/ContributorsList' import EntityActions from 'components/EntityActions' import Markdown from 'components/MarkdownWrapper' -import MenteeContributorsList from 'components/MenteeContributorsList' import { getSimpleDuration } from 'components/ModuleCard' -import TopContributorsList from 'components/TopContributorsList' interface SingleModuleCardProps { module: Module @@ -82,29 +82,30 @@ const SingleModuleCard: React.FC = ({ module, accessLevel {/* Mentors */} {module.mentors?.length > 0 && ( - )} {module.mentees?.length > 0 && (pathname?.startsWith('/my/mentorship') ? ( - getMenteeUrl(programKey, module.key, login)} /> ) : ( - ))}
diff --git a/frontend/src/components/TopContributorsList.tsx b/frontend/src/components/TopContributorsList.tsx deleted file mode 100644 index 384c952ad4..0000000000 --- a/frontend/src/components/TopContributorsList.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { IconType } from 'react-icons' -import type { Contributor } from 'types/contributor' -import { getMemberUrl } from 'utils/urlFormatter' -import ContributorsList from 'components/ContributorsList' - -interface TopContributorsListProps { - contributors: Contributor[] - label?: string - maxInitialDisplay?: number - icon?: IconType -} - -const TopContributorsList = ({ - contributors, - label = 'Top Contributors', - maxInitialDisplay = 12, - icon, -}: TopContributorsListProps) => ( - -) - -export default TopContributorsList diff --git a/frontend/src/utils/urlFormatter.ts b/frontend/src/utils/urlFormatter.ts index e801487ae2..19218eb479 100644 --- a/frontend/src/utils/urlFormatter.ts +++ b/frontend/src/utils/urlFormatter.ts @@ -1,2 +1,4 @@ export const getMemberUrl = (login: string): string => `/members/${login}` export const getProjectUrl = (projectKey: string): string => `/projects/${projectKey}` +export const getMenteeUrl = (programKey: string, moduleKey: string, login: string): string => + `/my/mentorship/programs/${programKey}/modules/${moduleKey}/mentees/${login}`