diff --git a/frontend/__tests__/unit/components/AnchorTitle.test.tsx b/frontend/__tests__/unit/components/AnchorTitle.test.tsx index f0a6f3abcc..883a93f353 100644 --- a/frontend/__tests__/unit/components/AnchorTitle.test.tsx +++ b/frontend/__tests__/unit/components/AnchorTitle.test.tsx @@ -1,11 +1,7 @@ -import { library } from '@fortawesome/fontawesome-svg-core' -import { faLink } from '@fortawesome/free-solid-svg-icons' import { screen, render, fireEvent, waitFor } from '@testing-library/react' import slugifyMock from 'utils/slugify' import AnchorTitle from 'components/AnchorTitle' -library.add(faLink) - jest.mock('utils/slugify', () => ({ __esModule: true, default: jest.fn((str: string) => @@ -101,7 +97,7 @@ describe('AnchorTitle Component', () => { expect(titleElement).toHaveAttribute('data-anchor-title', 'true') }) - it('renders FontAwesome link icon', () => { + it('renders react-icons link icon', () => { render() const link = screen.getByRole('link') const icon = link.querySelector('svg') diff --git a/frontend/__tests__/unit/components/Badges.test.tsx b/frontend/__tests__/unit/components/Badges.test.tsx index cd171deb42..87534d4f24 100644 --- a/frontend/__tests__/unit/components/Badges.test.tsx +++ b/frontend/__tests__/unit/components/Badges.test.tsx @@ -2,31 +2,26 @@ import { render, screen } from '@testing-library/react' import React from 'react' import Badges from 'components/Badges' -jest.mock('wrappers/FontAwesomeIconWrapper', () => { - const RealWrapper = jest.requireActual('wrappers/FontAwesomeIconWrapper').default - - const getName = (icon) => { - if (!icon) return 'medal' - if (typeof icon === 'string') { - const m = icon.match(/fa-([a-z0-9-]+)$/i) - if (m) return m[1] - const last = icon.trim().split(/\s+/).pop() || '' - return last.replace(/^fa-/, '') || 'medal' - } - if (Array.isArray(icon) && icon.length >= 2) return String(icon[1]) - if (icon && typeof icon === 'object') return icon.iconName || String(icon[1] ?? 'medal') - return 'medal' - } - - return function MockFontAwesomeIconWrapper(props) { - const name = getName(props.icon) +jest.mock('wrappers/IconWrapper', () => ({ + IconWrapper: ({ + icon, + className, + ...props + }: { + icon: React.ComponentType<{ className?: string }> + className?: string + }) => { + let iconName = icon.name?.toLowerCase() || 'medal' + iconName = iconName.replace(/^fa/, '') + iconName = iconName.replace(/^reg/, '') + if (!iconName) iconName = 'medal' return ( -
- +
+
) - } -}) + }, +})) jest.mock('@heroui/tooltip', () => ({ Tooltip: ({ diff --git a/frontend/__tests__/unit/components/BarChart.test.tsx b/frontend/__tests__/unit/components/BarChart.test.tsx index 904d812225..31d29279cb 100644 --- a/frontend/__tests__/unit/components/BarChart.test.tsx +++ b/frontend/__tests__/unit/components/BarChart.test.tsx @@ -1,11 +1,7 @@ -import { library } from '@fortawesome/fontawesome-svg-core' -import { faFire } from '@fortawesome/free-solid-svg-icons' import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' import React from 'react' - -// Register FontAwesome icon -library.add(faFire) +import { FaFire } from 'react-icons/fa' +import '@testing-library/jest-dom' // Mock react-apexcharts completely jest.mock('react-apexcharts', () => { @@ -170,8 +166,7 @@ describe('', () => { }) it('renders with custom icon when provided', () => { - // cspell:ignore fas - renderWithTheme() + renderWithTheme() expect(screen.getByTestId('anchor-title')).toHaveTextContent('Calories Burned') expect(screen.getByTestId('card-icon')).toBeInTheDocument() }) diff --git a/frontend/__tests__/unit/components/CalendarButton.test.tsx b/frontend/__tests__/unit/components/CalendarButton.test.tsx index 14e8c45b0a..db66d6e144 100644 --- a/frontend/__tests__/unit/components/CalendarButton.test.tsx +++ b/frontend/__tests__/unit/components/CalendarButton.test.tsx @@ -1,6 +1,5 @@ -import { faCalendarDay, faCalendarPlus } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { render, screen } from '@testing-library/react' +import { FaCalendarDay, FaCalendarPlus } from 'react-icons/fa6' import CalendarButton from 'components/CalendarButton' const mockEvent = { @@ -25,7 +24,7 @@ describe('CalendarButton', () => { expect(link.tagName).toBe('A') }) - it('renders default FontAwesome calendar-plus icon', () => { + it('renders default calendar-plus icon', () => { render() const svg = document.querySelector('svg') expect(svg).toBeInTheDocument() @@ -33,10 +32,7 @@ describe('CalendarButton', () => { it('renders custom icon when provided', () => { render( - } - /> + } /> ) expect(screen.getByTestId('custom-icon')).toBeInTheDocument() }) @@ -185,12 +181,9 @@ describe('CalendarButton', () => { }) describe('icon prop extensibility', () => { - it('accepts FontAwesome icon as JSX', () => { + it('accepts icon as JSX', () => { render( - } - /> + } /> ) const svg = document.querySelector('svg') expect(svg).toHaveClass('custom-icon-class') @@ -321,12 +314,7 @@ describe('CalendarButton', () => { render( - } + icon={} /> ) const svg = document.querySelector('svg') diff --git a/frontend/__tests__/unit/components/Card.test.tsx b/frontend/__tests__/unit/components/Card.test.tsx index bc0c587c67..1f1ab4ac15 100644 --- a/frontend/__tests__/unit/components/Card.test.tsx +++ b/frontend/__tests__/unit/components/Card.test.tsx @@ -1,5 +1,6 @@ import { screen, render, fireEvent } from '@testing-library/react' -import { ReactNode } from 'react' +import React, { ReactNode } from 'react' +import { FaLeaf, FaFire, FaCrown, FaStar, FaGithub, FaTwitter } from 'react-icons/fa6' import type { CardProps } from 'types/card' import Card from 'components/Card' @@ -12,11 +13,6 @@ interface MockLinkProps { className?: string } -interface MockFontAwesomeIconProps { - icon: unknown - className?: string -} - interface MockTooltipProps { children: ReactNode content: string @@ -63,14 +59,6 @@ jest.mock('next/link', () => { } }) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: ({ icon, className }: MockFontAwesomeIconProps) => ( - - {String(icon)} - - ), -})) - jest.mock('@heroui/tooltip', () => ({ Tooltip: ({ children, content, id }: MockTooltipProps) => (
@@ -79,15 +67,22 @@ jest.mock('@heroui/tooltip', () => ({ ), })) -jest.mock('wrappers/FontAwesomeIconWrapper', () => { - return function FontAwesomeIconWrapper({ icon, className }: MockFontAwesomeIconProps) { +jest.mock('wrappers/IconWrapper', () => ({ + IconWrapper: ({ + icon, + className, + }: { + icon: React.ComponentType<{ className?: string }> + className?: string + }) => { + const iconName = icon?.name?.toLowerCase().replace('fa', '') || 'icon' return ( - - {String(icon)} + + ) - } -}) + }, +})) jest.mock('components/ActionButton', () => { return function ActionButton({ children, onClick, tooltipLabel, url }: MockActionButtonProps) { @@ -131,7 +126,11 @@ jest.mock('components/MarkdownWrapper', () => { }) jest.mock('utils/urlIconMappings', () => ({ - getSocialIcon: jest.fn().mockReturnValue('mocked-social-icon'), + getSocialIcon: jest.fn().mockReturnValue(({ className }: { className?: string }) => ( + + + + )), })) jest.mock('utils/data', () => ({ @@ -179,12 +178,12 @@ describe('Card', () => { it('conditionally renders level badge when provided', () => { const propsWithLevel = { ...baseProps, - level: { level: 'Beginner', color: '#4CAF50', icon: 'leaf-icon' }, + level: { level: 'Beginner', color: '#4CAF50', icon: FaLeaf }, } render() expect(screen.getByTestId('tooltip')).toBeInTheDocument() - expect(screen.getByTestId('font-awesome-wrapper')).toBeInTheDocument() + expect(screen.getByTestId('icon-wrapper')).toBeInTheDocument() }) it('does not render level badge when not provided', () => { @@ -229,12 +228,12 @@ describe('Card', () => { const propsWithSocial = { ...baseProps, social: [ - { title: 'GitHub', url: 'https://github.com/test', icon: 'github' }, - { title: 'Twitter', url: 'https://twitter.com/test', icon: 'twitter' }, + { title: 'GitHub', url: 'https://github.com/test', icon: FaGithub }, + { title: 'Twitter', url: 'https://twitter.com/test', icon: FaTwitter }, ], } render() - expect(screen.getAllByTestId('font-awesome-icon')).toHaveLength(2) + expect(screen.getAllByTestId('social-icon')).toHaveLength(2) const allLinks = screen.getAllByRole('link') expect(allLinks.length).toBeGreaterThan(1) @@ -296,7 +295,7 @@ describe('Card', () => { it('applies different level badge colors based on props', () => { const propsWithLevel = { ...baseProps, - level: { level: 'Advanced', color: '#FF5722', icon: 'fire-icon' }, + level: { level: 'Advanced', color: '#FF5722', icon: FaFire }, } render() @@ -400,7 +399,7 @@ describe('Card', () => { it('has proper accessibility attributes', () => { const propsWithTooltip = { ...baseProps, - level: { level: 'Intermediate', color: '#2196F3', icon: 'star-icon' }, + level: { level: 'Intermediate', color: '#2196F3', icon: FaStar }, tooltipLabel: 'Click to contribute', } @@ -466,11 +465,11 @@ describe('Card', () => { it('renders complete card with all optional props', () => { const fullProps = { ...baseProps, - level: { level: 'Expert', color: '#9C27B0', icon: 'crown-icon' }, + level: { level: 'Expert', color: '#9C27B0', icon: FaCrown }, icons: { react: 'active', typescript: 'active' }, projectName: 'Full Stack Project', projectLink: 'https://fullstack.com', - social: [{ title: 'GitHub', url: 'https://github.com/full', icon: 'active' }], + social: [{ title: 'GitHub', url: 'https://github.com/full', icon: FaGithub }], topContributors: [ { login: 'expert', @@ -486,7 +485,7 @@ describe('Card', () => { expect(screen.getByTestId('tooltip')).toBeInTheDocument() expect(screen.getAllByTestId('display-icon')).toHaveLength(2) expect(screen.getByRole('link', { name: 'Full Stack Project' })).toBeInTheDocument() - expect(screen.getByTestId('font-awesome-icon')).toBeInTheDocument() + expect(screen.getByTestId('social-icon')).toBeInTheDocument() expect(screen.getByTestId('contributor-avatar')).toBeInTheDocument() }) }) diff --git a/frontend/__tests__/unit/components/CardDetailsPage.test.tsx b/frontend/__tests__/unit/components/CardDetailsPage.test.tsx index ab62cbc02e..cd80c70615 100644 --- a/frontend/__tests__/unit/components/CardDetailsPage.test.tsx +++ b/frontend/__tests__/unit/components/CardDetailsPage.test.tsx @@ -1,10 +1,9 @@ -import { faCode, faTags } from '@fortawesome/free-solid-svg-icons' import { render, screen, cleanup } from '@testing-library/react' import React from 'react' import '@testing-library/jest-dom' +import { FaCode, FaTags } from 'react-icons/fa6' import type { DetailsCardProps } from 'types/card' import CardDetailsPage from 'components/CardDetailsPage' - jest.mock('next/link', () => { const MockLink = ({ children, @@ -56,31 +55,16 @@ jest.mock('next/image', () => ({ ), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: ({ - icon, - className, - ...props - }: { - icon: { iconName: string } - className?: string - [key: string]: unknown - }) => , -})) - jest.mock('utils/env.client', () => ({ IS_PROJECT_HEALTH_ENABLED: true, })) jest.mock('utils/urlIconMappings', () => ({ getSocialIcon: (url: string) => { - if (url?.includes('github')) { - return { iconName: 'github' } + const safe = encodeURIComponent(url) + return function MockSocialIcon(props: { className?: string }) { + return } - if (url?.includes('twitter')) { - return { iconName: 'twitter' } - } - return { iconName: 'link' } }, })) @@ -134,6 +118,7 @@ jest.mock('components/HealthMetrics', () => ({ jest.mock('components/InfoBlock', () => ({ __esModule: true, default: ({ + icon: _icon, pluralizedName, unit, value, @@ -276,9 +261,10 @@ jest.mock('components/SecondaryCard', () => ({ title, children, className, + icon: _icon, ...props }: { - _icon: unknown + _icon?: unknown title: React.ReactNode children: React.ReactNode className?: string @@ -314,6 +300,7 @@ jest.mock('components/ToggleableList', () => ({ __esModule: true, default: ({ items, + icon: _icon, label, ...props }: { @@ -412,13 +399,13 @@ describe('CardDetailsPage', () => { const mockStats = [ { - icon: faCode, + icon: FaCode, pluralizedName: 'repositories', unit: '', value: 10, }, { - icon: faTags, + icon: FaTags, pluralizedName: 'stars', unit: '', value: 100, @@ -1329,9 +1316,9 @@ describe('CardDetailsPage', () => { it('handles zero and negative values in stats', () => { const statsWithZeroValues = [ - { icon: faCode, value: 0, unit: 'Star' }, - { icon: faTags, value: invalidValues.negativeNumber, unit: 'Issue' }, - { icon: faCode, value: invalidValues.nullValue, unit: 'Fork' }, + { icon: FaCode, value: 0, unit: 'Star' }, + { icon: FaTags, value: -10, unit: 'Issue' }, + { icon: FaCode, value: null, unit: 'Fork' }, ] expect(() => diff --git a/frontend/__tests__/unit/components/DashboardCard.test.tsx b/frontend/__tests__/unit/components/DashboardCard.test.tsx index 55b295f304..5d5b8e08f5 100644 --- a/frontend/__tests__/unit/components/DashboardCard.test.tsx +++ b/frontend/__tests__/unit/components/DashboardCard.test.tsx @@ -1,6 +1,6 @@ -import { faUser, faChartBar } from '@fortawesome/free-solid-svg-icons' import { render, screen } from '@testing-library/react' import React from 'react' +import { FaUser, FaChartBar } from 'react-icons/fa' import '@testing-library/jest-dom' import DashboardCard from 'components/DashboardCard' @@ -45,7 +45,7 @@ jest.mock('components/SecondaryCard', () => ({ describe('DashboardCard', () => { const baseProps = { title: 'Test Card', - icon: faUser, + icon: FaUser, className: undefined, stats: undefined, } @@ -56,7 +56,7 @@ describe('DashboardCard', () => { describe('Essential Rendering', () => { it('renders successfully with minimal required props', () => { - render() + render() expect(screen.getByTestId('secondary-card')).toBeInTheDocument() expect(screen.getByTestId('anchor-title')).toHaveTextContent('Test Card') expect(screen.getByTestId('secondary-content')).toBeInTheDocument() @@ -101,15 +101,15 @@ describe('DashboardCard', () => { }) it('renders different icons based on prop', () => { - const { rerender } = render() + const { rerender } = render() expect(screen.getByTestId('secondary-content').querySelector('svg')).toBeInTheDocument() - rerender() + rerender() expect(screen.getByTestId('secondary-content').querySelector('svg')).toBeInTheDocument() }) }) describe('DOM Structure', () => { - it('renders FontAwesomeIcon with correct icon', () => { + it('renders icon with correct icon', () => { render() expect(screen.getByTestId('secondary-content').querySelector('svg')).toBeInTheDocument() }) @@ -218,7 +218,7 @@ describe('DashboardCard', () => { it('handles rapid prop changes gracefully', () => { const { rerender } = render() - const icons = [faUser, faChartBar] + const icons = [FaUser, FaChartBar] const titles = ['A', 'B', 'C'] for (let i = 0; i < 3; i++) { rerender( diff --git a/frontend/__tests__/unit/components/DisplayIcon.test.tsx b/frontend/__tests__/unit/components/DisplayIcon.test.tsx index 981d817239..f34eb7ae3a 100644 --- a/frontend/__tests__/unit/components/DisplayIcon.test.tsx +++ b/frontend/__tests__/unit/components/DisplayIcon.test.tsx @@ -1,9 +1,9 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' +import { FaStar, FaCodeFork, FaUser, FaClock, FaComment, FaQuestion } from 'react-icons/fa6' import type { Icon } from 'types/icon' import DisplayIcon from 'components/DisplayIcon' - interface TooltipProps { children: React.ReactNode content: string @@ -13,11 +13,6 @@ interface TooltipProps { placement: string } -interface IconWrapperProps { - className?: string - icon: string -} - jest.mock('@heroui/tooltip', () => ({ Tooltip: ({ children, content, delay, closeDelay, showArrow, placement }: TooltipProps) => (
({ }), })) -jest.mock('wrappers/FontAwesomeIconWrapper', () => { - return function MockFontAwesomeIconWrapper({ className, icon }: IconWrapperProps) { - return - } -}) +jest.mock('wrappers/IconWrapper', () => ({ + IconWrapper: ({ + className, + icon: IconComponent, + }: { + className?: string + icon: React.ComponentType<{ className?: string }> + }) => { + // This derives a data-icon attribute from the react-icon component name + let iconName = '' + const toKebab = (name: string) => + name + .replaceAll('Fa', '') + .replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2') + .toLowerCase() + + if (IconComponent?.displayName) { + iconName = 'fa-' + toKebab(IconComponent.displayName) + } else if (IconComponent?.name) { + iconName = 'fa-' + toKebab(IconComponent.name) + } + + return IconComponent ? ( + + + + ) : null + }, +})) jest.mock('utils/data', () => ({ ICONS: { - starsCount: { label: 'Stars', icon: 'fa-star' }, - forksCount: { label: 'Forks', icon: 'fa-code-fork' }, - contributorsCount: { label: 'Contributors', icon: 'fa-users' }, - contributionCount: { label: 'Contributors', icon: 'fa-users' }, - issuesCount: { label: 'Issues', icon: 'fa-exclamation-circle' }, - license: { label: 'License', icon: 'fa-balance-scale' }, - unknownItem: { label: 'Unknown', icon: 'fa-question' }, + starsCount: { label: 'Stars', icon: FaStar }, + forksCount: { label: 'Forks', icon: FaCodeFork }, + contributorsCount: { label: 'Contributors', icon: FaUser }, + createdAt: { label: 'Creation date', icon: FaClock }, + commentsCount: { label: 'Comments count', icon: FaComment }, + unknownItem: { label: 'Unknown', icon: FaQuestion }, }, })) @@ -117,7 +135,7 @@ describe('DisplayIcon', () => { expect(screen.getByTestId('font-awesome-icon')).toHaveAttribute('data-icon', 'fa-code-fork') rerender() - expect(screen.getByTestId('font-awesome-icon')).toHaveAttribute('data-icon', 'fa-users') + expect(screen.getByTestId('font-awesome-icon')).toHaveAttribute('data-icon', 'fa-user') }) it('applies different container classes based on item type', () => { diff --git a/frontend/__tests__/unit/components/DonutBarChart.test.tsx b/frontend/__tests__/unit/components/DonutBarChart.test.tsx index b3bcb71f8f..593e187746 100644 --- a/frontend/__tests__/unit/components/DonutBarChart.test.tsx +++ b/frontend/__tests__/unit/components/DonutBarChart.test.tsx @@ -1,7 +1,7 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core' import { screen } from '@testing-library/react' import { render } from '@testing-library/react' import { useTheme } from 'next-themes' +import { FaChartPie, FaChartBar, FaChartLine, FaTachometerAlt, FaHeart } from 'react-icons/fa' import DonutBarChart from 'components/DonutBarChart' // Mock next-themes @@ -47,12 +47,16 @@ jest.mock('components/AnchorTitle', () => { }) jest.mock('components/SecondaryCard', () => { - const MockSecondaryCard = ({ title, icon, children }) => ( -
-
{title}
-
{children}
-
- ) + const MockSecondaryCard = ({ title, icon, children }) => { + const iconName = icon?.displayName ?? icon?.name ?? icon + + return ( +
+
{title}
+
{children}
+
+ ) + } MockSecondaryCard.displayName = 'SecondaryCard' return MockSecondaryCard }) @@ -60,8 +64,6 @@ jest.mock('components/SecondaryCard', () => { describe('DonutBarChart Component Test Suite', () => { const mockUseTheme = useTheme as jest.MockedFunction - const iconProp = (name: string): IconProp => name as IconProp - beforeEach(() => { mockUseTheme.mockReturnValue({ theme: 'light', @@ -75,28 +77,22 @@ describe('DonutBarChart Component Test Suite', () => { describe('Basic rendering functionality', () => { it('renders the component with required props', () => { - render( - - ) + render() expect(screen.getByTestId('secondary-card')).toBeInTheDocument() expect(screen.getByTestId('apex-chart')).toBeInTheDocument() }) it('renders title through AnchorTitle component', () => { - render( - - ) + render() expect(screen.getByTestId('anchor-title')).toHaveTextContent('Health Metrics') }) it('passes icon to SecondaryCard', () => { - render( - - ) + render() - expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', 'analytics') + expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', 'FaChartLine') }) }) @@ -104,7 +100,7 @@ describe('DonutBarChart Component Test Suite', () => { it('processes series data with rounding', () => { const series = [33.333, 33.333, 33.334] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -116,7 +112,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles integer values correctly', () => { const series = [50, 30, 20] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -127,7 +123,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles decimal values with proper rounding', () => { const series = [25.555, 30.777, 43.668] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -139,7 +135,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles zero values', () => { const series = [0, 50, 0] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -150,7 +146,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles single value', () => { const series = [100] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -161,7 +157,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles empty series array', () => { const series: number[] = [] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -172,7 +168,7 @@ describe('DonutBarChart Component Test Suite', () => { describe('Chart configuration', () => { it('configures chart with correct options', () => { - render() + render() const chart = screen.getByTestId('apex-chart') const options = JSON.parse(chart.dataset.options || '{}') @@ -187,7 +183,7 @@ describe('DonutBarChart Component Test Suite', () => { }) it('sets correct chart type and height', () => { - render() + render() const chart = screen.getByTestId('apex-chart') @@ -196,7 +192,7 @@ describe('DonutBarChart Component Test Suite', () => { }) it('uses fixed color scheme', () => { - render() + render() const chart = screen.getByTestId('apex-chart') const options = JSON.parse(chart.dataset.options || '{}') @@ -209,7 +205,7 @@ describe('DonutBarChart Component Test Suite', () => { }) it('uses fixed labels', () => { - render() + render() const chart = screen.getByTestId('apex-chart') const options = JSON.parse(chart.dataset.options || '{}') @@ -228,7 +224,7 @@ describe('DonutBarChart Component Test Suite', () => { resolvedTheme: 'light', }) - render() + render() const chart = screen.getByTestId('apex-chart') const options = JSON.parse(chart.dataset.options || '{}') @@ -245,7 +241,7 @@ describe('DonutBarChart Component Test Suite', () => { resolvedTheme: 'dark', }) - render() + render() const chart = screen.getByTestId('apex-chart') const options = JSON.parse(chart.dataset.options || '{}') @@ -262,7 +258,7 @@ describe('DonutBarChart Component Test Suite', () => { resolvedTheme: 'dark', }) - render() + render() const chart = screen.getByTestId('apex-chart') // The key should be applied but we can't directly test it in our mock @@ -274,7 +270,7 @@ describe('DonutBarChart Component Test Suite', () => { describe('Component structure and accessibility', () => { it('maintains proper component hierarchy', () => { - render() + render() const card = screen.getByTestId('secondary-card') const title = screen.getByTestId('anchor-title') @@ -290,7 +286,7 @@ describe('DonutBarChart Component Test Suite', () => { }) it('renders chart inside proper container div', () => { - render() + render() const cardContent = screen.getByTestId('card-content') const chart = screen.getByTestId('apex-chart') @@ -301,18 +297,14 @@ describe('DonutBarChart Component Test Suite', () => { describe('Prop validation and edge cases', () => { it('handles different icon types', () => { - const iconTypes = ['chart-pie', 'chart-bar', 'analytics', 'dashboard', 'heart'] + const iconTypes = [FaChartPie, FaChartBar, FaChartLine, FaTachometerAlt, FaHeart] for (const iconType of iconTypes) { const { unmount } = render( - + ) - expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', iconType) + expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', iconType.name) unmount() } }) @@ -328,7 +320,7 @@ describe('DonutBarChart Component Test Suite', () => { for (const title of titles) { const { unmount } = render( - + ) expect(screen.getByTestId('anchor-title')).toHaveTextContent(title) @@ -339,7 +331,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles large series values', () => { const largeSeries = [999999.999, 1000000.001, 2000000.5] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -351,7 +343,7 @@ describe('DonutBarChart Component Test Suite', () => { it('handles negative values', () => { const negativeSeries = [-10.5, 50.7, -20.3] - render() + render() const chart = screen.getByTestId('apex-chart') const chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -363,14 +355,14 @@ describe('DonutBarChart Component Test Suite', () => { describe('Performance and re-rendering', () => { it('handles series updates correctly', () => { const { rerender } = render( - + ) let chart = screen.getByTestId('apex-chart') let chartSeries = JSON.parse(chart.dataset.series || '[]') expect(chartSeries).toEqual([50, 30, 20]) - rerender() + rerender() chart = screen.getByTestId('apex-chart') chartSeries = JSON.parse(chart.dataset.series || '[]') @@ -379,31 +371,31 @@ describe('DonutBarChart Component Test Suite', () => { it('handles title updates correctly', () => { const { rerender } = render( - + ) expect(screen.getByTestId('anchor-title')).toHaveTextContent('Original Title') - rerender() + rerender() expect(screen.getByTestId('anchor-title')).toHaveTextContent('Updated Title') }) it('handles icon updates correctly', () => { const { rerender } = render( - + ) - expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', 'chart-pie') + expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', 'FaChartPie') - rerender() + rerender() - expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', 'chart-bar') + expect(screen.getByTestId('secondary-card')).toHaveAttribute('data-icon', 'FaChartBar') }) it('handles theme changes correctly', () => { const { rerender } = render( - + ) let chart = screen.getByTestId('apex-chart') @@ -418,7 +410,7 @@ describe('DonutBarChart Component Test Suite', () => { resolvedTheme: 'dark', }) - rerender() + rerender() chart = screen.getByTestId('apex-chart') options = JSON.parse(chart.dataset.options || '{}') @@ -431,7 +423,7 @@ describe('DonutBarChart Component Test Suite', () => { const mockRound = jest.requireMock('utils/round').round render( - + ) expect(mockRound).toHaveBeenCalledTimes(3) @@ -441,13 +433,13 @@ describe('DonutBarChart Component Test Suite', () => { }) it('integrates properly with next-themes useTheme hook', () => { - render() + render() expect(mockUseTheme).toHaveBeenCalled() }) it('uses dynamic import for Chart component (SSR safety)', () => { - render() + render() // Chart should render (mocked) proving dynamic import works expect(screen.getByTestId('apex-chart')).toBeInTheDocument() diff --git a/frontend/__tests__/unit/components/Footer.test.tsx b/frontend/__tests__/unit/components/Footer.test.tsx index 8781e84fb2..51b7ab78d9 100644 --- a/frontend/__tests__/unit/components/Footer.test.tsx +++ b/frontend/__tests__/unit/components/Footer.test.tsx @@ -4,9 +4,7 @@ */ import { render, screen, fireEvent } from '@testing-library/react' import '@testing-library/jest-dom' -import { ReactNode } from 'react' -import { footerSections, footerIcons } from 'utils/constants' -import Footer from 'components/Footer' +import React, { ReactNode } from 'react' // Define proper types for mock props interface MockLinkProps { @@ -18,11 +16,6 @@ interface MockLinkProps { 'aria-label'?: string } -interface MockFontAwesomeIconProps { - icon: unknown - className?: string -} - interface MockButtonProps { children: ReactNode onPress?: () => void @@ -42,14 +35,6 @@ jest.mock('next/link', () => { } }) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: ({ icon, className }: MockFontAwesomeIconProps) => ( - - {typeof icon === 'string' ? icon : JSON.stringify(icon)} - - ), -})) - jest.mock('@heroui/button', () => ({ Button: ({ children, onPress, className, disableAnimation, ...props }: MockButtonProps) => ( +
+ ) + }, +})) + +jest.mock('react-icons/fa6', () => ({ + FaChevronUp: (props: React.HTMLAttributes) => ( + + chevron-up + + ), + FaChevronDown: (props: React.HTMLAttributes) => ( + + chevron-down + + ), + FaUsers: (props: React.HTMLAttributes) => users, +})) + const mockContributors: Contributor[] = [ { avatarUrl: 'https://github.com/developer1.avatar', @@ -272,14 +306,12 @@ describe('TopContributorsList Component', () => { }) it('displays icon when provided', () => { - render() - + render() expect(screen.getByTestId('card-icon')).toBeInTheDocument() }) it('does not display icon when not provided', () => { render() - expect(screen.queryByTestId('card-icon')).not.toBeInTheDocument() }) }) diff --git a/frontend/__tests__/unit/components/UserCard.test.tsx b/frontend/__tests__/unit/components/UserCard.test.tsx index bb774648eb..dca1fa0266 100644 --- a/frontend/__tests__/unit/components/UserCard.test.tsx +++ b/frontend/__tests__/unit/components/UserCard.test.tsx @@ -53,23 +53,20 @@ jest.mock('@heroui/button', () => { } }) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: ({ - icon, - className, - ...props - }: { - icon: { iconName: string } - className?: string - [key: string]: unknown - }) => , +jest.mock('react-icons/fa6', () => ({ + FaChevronRight: (props: React.SVGProps) => ( + + ), + FaFolderOpen: (props: React.SVGProps) => ( + + ), + FaMedal: (props: React.SVGProps) => , + FaUser: (props: React.SVGProps) => , })) -jest.mock('@heroui/tooltip', () => ({ - Tooltip: ({ children, content }: { children: React.ReactNode; content: string }) => ( -
- {children} -
+jest.mock('react-icons/hi', () => ({ + HiUserGroup: (props: React.SVGProps) => ( + ), })) diff --git a/frontend/__tests__/unit/components/UserMenu.test.tsx b/frontend/__tests__/unit/components/UserMenu.test.tsx index 7637ca8be4..9e1cf77a19 100644 --- a/frontend/__tests__/unit/components/UserMenu.test.tsx +++ b/frontend/__tests__/unit/components/UserMenu.test.tsx @@ -2,6 +2,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { useDjangoSession } from 'hooks/useDjangoSession' import { useLogout } from 'hooks/useLogout' import { signIn } from 'next-auth/react' +import React from 'react' import { ExtendedSession } from 'types/auth' import UserMenu from 'components/UserMenu' @@ -41,11 +42,9 @@ jest.mock('next/image', () => ({ ), })) -// Mock FontAwesome icons -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: ({ icon }: { icon: unknown }) => ( - - ), +// Add this react-icons mock for FaGithub: +jest.mock('react-icons/fa', () => ({ + FaGithub: (props: React.SVGProps) => , })) describe('UserMenu Component', () => { diff --git a/frontend/__tests__/unit/pages/About.test.tsx b/frontend/__tests__/unit/pages/About.test.tsx index 1beed73c73..ab809f5b46 100644 --- a/frontend/__tests__/unit/pages/About.test.tsx +++ b/frontend/__tests__/unit/pages/About.test.tsx @@ -3,7 +3,7 @@ import { addToast } from '@heroui/toast' import { fireEvent, screen, waitFor, within } from '@testing-library/react' import { mockAboutData } from '@unit/data/mockAboutData' import { useRouter } from 'next/navigation' -import { act } from 'react' +import React, { act } from 'react' import { render } from 'wrappers/testUtil' import About from 'app/about/page' import { @@ -20,13 +20,32 @@ jest.mock('@apollo/client/react', () => ({ const mockRouter = { push: jest.fn(), } + jest.mock('next/navigation', () => ({ ...jest.requireActual('next/navigation'), useRouter: jest.fn(() => mockRouter), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , +jest.mock('react-icons/fa', () => ({ + FaMapSigns: () => , + FaTools: () => , +})) + +jest.mock('react-icons/fa6', () => ({ + FaCircleCheck: () => , + FaClock: () => , + FaScroll: () => , + FaBullseye: () => , + FaUser: () => , + FaUsersGear: () => , + FaLink: () => , + FaChevronRight: () => , + FaFolderOpen: () => , + FaMedal: () => , +})) + +jest.mock('react-icons/hi', () => ({ + HiUserGroup: () => , })) jest.mock('@heroui/toast', () => ({ @@ -105,6 +124,78 @@ jest.mock('components/MarkdownWrapper', () => ({ default: ({ content }) =>
{content}
, })) +jest.mock('components/AnchorTitle', () => ({ + __esModule: true, + default: ({ title }: { title: string }) => {title}, +})) + +jest.mock('components/UserCard', () => ({ + __esModule: true, + default: ({ + name, + credentials, + description, + button, + }: { + name?: string + credentials?: string + description?: string + button?: { label?: string; onclick?: () => void } + }) => ( +
+ {name && {name}} + {credentials && {credentials}} + {description && {description}} + {button?.label && ( + + )} +
+ ), +})) + +jest.mock('components/ShowMoreButton', () => ({ + __esModule: true, + default: function ShowMoreButtonMock({ onToggle }: { onToggle: () => void }) { + const [isExpanded, setIsExpanded] = React.useState(false) + + const handleClick = () => { + setIsExpanded(!isExpanded) + onToggle() + } + + return ( +
+ +
+ ) + }, +})) + const mockUserData = (username) => ({ data: { user: mockAboutData.users[username] }, loading: false, diff --git a/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx b/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx index fc150607e5..c214b06b46 100644 --- a/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx +++ b/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx @@ -34,10 +34,6 @@ jest.mock('@heroui/toast', () => ({ addToast: jest.fn(), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - jest.mock('next/navigation', () => ({ useRouter: () => ({ push: jest.fn() }), })) diff --git a/frontend/__tests__/unit/pages/ChapterDetails.test.tsx b/frontend/__tests__/unit/pages/ChapterDetails.test.tsx index 8c43fcd5d6..6c34a7c4e3 100644 --- a/frontend/__tests__/unit/pages/ChapterDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ChapterDetails.test.tsx @@ -9,10 +9,6 @@ jest.mock('@apollo/client/react', () => ({ useQuery: jest.fn(), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: () => ({ diff --git a/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx b/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx index c175293a25..a24ad0447c 100644 --- a/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx +++ b/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx @@ -10,10 +10,6 @@ jest.mock('@apollo/client/react', () => ({ useQuery: jest.fn(), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - const mockRouter = { push: jest.fn(), } diff --git a/frontend/__tests__/unit/pages/Header.test.tsx b/frontend/__tests__/unit/pages/Header.test.tsx index fb794dcd1c..092454557c 100644 --- a/frontend/__tests__/unit/pages/Header.test.tsx +++ b/frontend/__tests__/unit/pages/Header.test.tsx @@ -32,19 +32,17 @@ jest.mock('next/link', () => { } }) -// Mock FontAwesome components with proper icon mapping -jest.mock('@fortawesome/react-fontawesome', () => ({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - FontAwesomeIcon: ({ icon, className }: any) => { - // Map icon names to test IDs based on the actual icons used - const iconMap: { [key: string]: string } = { - bars: 'icon-bars', - xmark: 'icon-xmark', - times: 'icon-times', - } - const testId = iconMap[icon.iconName] || `icon-${icon.iconName}` - return - }, +jest.mock('react-icons/fa', () => ({ + FaBars: (props: React.SVGProps) => , + FaTimes: (props: React.SVGProps) => , + FaRegHeart: (props: React.SVGProps) => , + FaRegStar: (props: React.SVGProps) => , + FaHeart: (props: React.SVGProps) => ( + + ), + FaStar: (props: React.SVGProps) => ( + + ), })) // Mock HeroUI Button diff --git a/frontend/__tests__/unit/pages/Home.test.tsx b/frontend/__tests__/unit/pages/Home.test.tsx index 3338d8c314..3a9893245e 100644 --- a/frontend/__tests__/unit/pages/Home.test.tsx +++ b/frontend/__tests__/unit/pages/Home.test.tsx @@ -17,11 +17,6 @@ jest.mock('server/fetchAlgoliaData', () => ({ fetchAlgoliaData: jest.fn(), })) -jest.mock('wrappers/FontAwesomeIconWrapper', () => ({ - __esModule: true, - default: () => , -})) - jest.mock('@heroui/toast', () => ({ addToast: jest.fn(), })) diff --git a/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx b/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx index 3881dd3d55..90e97c890b 100644 --- a/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx +++ b/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx @@ -20,10 +20,6 @@ jest.mock('@heroui/toast', () => ({ addToast: jest.fn(), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - jest.mock('next/navigation', () => ({ ...jest.requireActual('next/navigation'), useRouter: jest.fn(() => mockRouter), diff --git a/frontend/__tests__/unit/pages/ProjectDetails.test.tsx b/frontend/__tests__/unit/pages/ProjectDetails.test.tsx index e27d14a161..000c4238bb 100644 --- a/frontend/__tests__/unit/pages/ProjectDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectDetails.test.tsx @@ -14,10 +14,6 @@ jest.mock('@heroui/toast', () => ({ addToast: jest.fn(), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - jest.mock('react-apexcharts', () => { return { __esModule: true, diff --git a/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx b/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx index 63444cb2ce..0a7d7fd480 100644 --- a/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx @@ -27,10 +27,6 @@ jest.mock('hooks/useDjangoSession', () => ({ }), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - const mockError = { error: new Error('GraphQL error'), } diff --git a/frontend/__tests__/unit/pages/Projects.test.tsx b/frontend/__tests__/unit/pages/Projects.test.tsx index 8f1f8597e2..245c249fa9 100644 --- a/frontend/__tests__/unit/pages/Projects.test.tsx +++ b/frontend/__tests__/unit/pages/Projects.test.tsx @@ -27,10 +27,6 @@ jest.mock('components/Pagination', () =>
)) ) -jest.mock('wrappers/FontAwesomeIconWrapper', () => ({ - __esModule: true, - default: () => , -})) jest.mock('@/components/MarkdownWrapper', () => { return ({ content, className }: { content: string; className?: string }) => ( diff --git a/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx b/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx index ba32623bf5..b4ec1e4a95 100644 --- a/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx @@ -12,10 +12,6 @@ jest.mock('@heroui/toast', () => ({ addToast: jest.fn(), })) -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: () => , -})) - const createDropDownMockItem = (item, onAction) => (
))} @@ -308,7 +301,7 @@ const ModuleIssueDetailsPage = () => { )} - +
{issue.pullRequests?.length ? ( issue.pullRequests.map((pr) => ( @@ -382,7 +375,7 @@ const ModuleIssueDetailsPage = () => {

- +
Interested Users
@@ -426,7 +419,7 @@ const ModuleIssueDetailsPage = () => { !issueId ? 'Loading issue…' : assigning ? 'Assigning…' : 'Assign to this user' } > - + Assign

diff --git a/frontend/src/app/organizations/[organizationKey]/page.tsx b/frontend/src/app/organizations/[organizationKey]/page.tsx index 1541f91d95..a466dcf927 100644 --- a/frontend/src/app/organizations/[organizationKey]/page.tsx +++ b/frontend/src/app/organizations/[organizationKey]/page.tsx @@ -1,15 +1,11 @@ 'use client' import { useQuery } from '@apollo/client/react' -import { - faCodeFork, - faExclamationCircle, - faFolderOpen, - faStar, - faUsers, -} from '@fortawesome/free-solid-svg-icons' import Link from 'next/link' import { useParams } from 'next/navigation' import { useState, useEffect } from 'react' +import { FaExclamationCircle } from 'react-icons/fa' +import { FaCodeFork, FaFolderOpen, FaStar } from 'react-icons/fa6' +import { HiUserGroup } from 'react-icons/hi' import { handleAppError, ErrorDisplay } from 'app/global-error' import { GetOrganizationDataDocument } from 'types/__generated__/organizationQueries.generated' import { formatDate } from 'utils/dateFormatter' @@ -85,27 +81,27 @@ const OrganizationDetailsPage = () => { const organizationStats = [ { - icon: faStar, + icon: FaStar, value: organization.stats.totalStars, unit: 'Star', }, { - icon: faCodeFork, + icon: FaCodeFork, value: organization.stats.totalForks, unit: 'Fork', }, { - icon: faUsers, + icon: HiUserGroup, value: organization.stats.totalContributors, unit: 'Contributor', }, { - icon: faExclamationCircle, + icon: FaExclamationCircle, value: organization.stats.totalIssues, unit: 'Issue', }, { - icon: faFolderOpen, + icon: FaFolderOpen, value: organization.stats.totalRepositories, unit: 'Repository', pluralizedName: 'Repositories', diff --git a/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx b/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx index 9e4d82c3b1..0ee25859ec 100644 --- a/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx +++ b/frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx @@ -1,15 +1,11 @@ 'use client' import { useQuery } from '@apollo/client/react' -import { - faCodeCommit, - faCodeFork, - faExclamationCircle, - faStar, - faUsers, -} from '@fortawesome/free-solid-svg-icons' import Link from 'next/link' import { useParams } from 'next/navigation' import { useEffect, useState } from 'react' +import { FaExclamationCircle } from 'react-icons/fa' +import { FaCodeCommit, FaCodeFork, FaStar } from 'react-icons/fa6' +import { HiUserGroup } from 'react-icons/hi' import { handleAppError, ErrorDisplay } from 'app/global-error' import { GetRepositoryDataDocument } from 'types/__generated__/repositoryQueries.generated' import type { Contributor } from 'types/contributor' @@ -81,28 +77,27 @@ const RepositoryDetailsPage = () => { const RepositoryStats = [ { - icon: faStar, + icon: FaStar, value: repository.starsCount, unit: 'Star', }, { - icon: faCodeFork, + icon: FaCodeFork, value: repository.forksCount, unit: 'Fork', }, { - icon: faUsers, + icon: HiUserGroup, value: repository.contributorsCount, unit: 'Contributor', }, - { - icon: faExclamationCircle, + icon: FaExclamationCircle, value: repository.openIssuesCount, unit: 'Issue', }, { - icon: faCodeCommit, + icon: FaCodeCommit, value: repository.commitsCount, unit: 'Commit', }, diff --git a/frontend/src/app/organizations/page.tsx b/frontend/src/app/organizations/page.tsx index 754d517045..c8056ae101 100644 --- a/frontend/src/app/organizations/page.tsx +++ b/frontend/src/app/organizations/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useSearchPage } from 'hooks/useSearchPage' import { useRouter } from 'next/navigation' -import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper' +import { FaRightToBracket } from 'react-icons/fa6' import type { Organization } from 'types/organization' import SearchPageLayout from 'components/SearchPageLayout' import UserCard from 'components/UserCard' @@ -30,7 +30,7 @@ const OrganizationPage = () => { const submitButton = { label: 'View Profile', - icon: , + icon: , onclick: handleButtonClick, } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index a5f8ad210b..d93c622faf 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,26 +1,25 @@ 'use client' import { useQuery } from '@apollo/client/react' -import { IconProp } from '@fortawesome/fontawesome-svg-core' -import { - faBook, - faCalendar, - faCalendarAlt, - faCode, - faFileCode, - faFolder, - faGlobe, - faMapMarkerAlt, - faNewspaper, - faTag, - faUser, - faUsers, -} from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { addToast } from '@heroui/toast' import upperFirst from 'lodash/upperFirst' import millify from 'millify' import Link from 'next/link' import { useEffect, useState } from 'react' +import type { IconType } from 'react-icons' +import { FaCalendarAlt, FaMapMarkerAlt } from 'react-icons/fa' +import { + FaBook, + FaCalendar, + FaCode, + FaFileCode, + FaFolder, + FaGlobe, + FaNewspaper, + FaTag, + FaUser, +} from 'react-icons/fa6' +import { HiUserGroup } from 'react-icons/hi' +import { IconWrapper } from 'wrappers/IconWrapper' import { fetchAlgoliaData } from 'server/fetchAlgoliaData' import { GetMainPageDataDocument } from 'types/__generated__/homeQueries.generated' import type { AlgoliaResponse } from 'types/algolia' @@ -96,18 +95,18 @@ export default function Home() { return } - const getProjectIcon = (projectType: string) => { + const getProjectIcon = (projectType: string): IconType => { switch (projectType.toLowerCase()) { case 'code': - return faCode + return FaCode case 'documentation': - return faBook + return FaBook case 'other': - return faFileCode + return FaFileCode case 'tool': - return faTag + return FaTag default: - return faFileCode + return FaFileCode } } @@ -156,7 +155,7 @@ export default function Home() { @@ -196,7 +195,7 @@ export default function Home() { {event.suggestedLocation && (
- +
)} @@ -217,7 +216,7 @@ export default function Home() {
@@ -238,11 +237,11 @@ export default function Home() {
- + {formatDate(chapter.createdAt)}
- +
@@ -250,7 +249,7 @@ export default function Home() { {chapter.leaders.length > 0 && (
{' '} - +
)} @@ -259,7 +258,7 @@ export default function Home() {
@@ -277,20 +276,17 @@ export default function Home() {
- + {formatDate(project.createdAt)}
- +
{project.leaders.length > 0 && (
- +
)} @@ -301,11 +297,7 @@ export default function Home() {
- +
@@ -334,7 +326,7 @@ export default function Home() {
@@ -360,11 +352,11 @@ export default function Home() {
- + {formatDate(post.publishedAt)}
- +
diff --git a/frontend/src/app/projects/[projectKey]/page.tsx b/frontend/src/app/projects/[projectKey]/page.tsx index 77fbf46021..f7fc8a1169 100644 --- a/frontend/src/app/projects/[projectKey]/page.tsx +++ b/frontend/src/app/projects/[projectKey]/page.tsx @@ -1,16 +1,12 @@ 'use client' import { useQuery } from '@apollo/client/react' -import { - faCodeFork, - faExclamationCircle, - faFolderOpen, - faStar, - faUsers, -} from '@fortawesome/free-solid-svg-icons' import upperFirst from 'lodash/upperFirst' import Link from 'next/link' import { useParams } from 'next/navigation' import { useState, useEffect } from 'react' +import { FaExclamationCircle } from 'react-icons/fa' +import { FaCodeFork, FaFolderOpen, FaStar } from 'react-icons/fa6' +import { HiUserGroup } from 'react-icons/hi' import { ErrorDisplay, handleAppError } from 'app/global-error' import { GetProjectDocument } from 'types/__generated__/projectQueries.generated' import type { Contributor } from 'types/contributor' @@ -69,20 +65,20 @@ const ProjectDetailsPage = () => { }, ] const projectStats = [ - { icon: faStar, value: project.starsCount, unit: 'Star' }, - { icon: faCodeFork, value: project.forksCount, unit: 'Fork' }, + { icon: FaStar, value: project.starsCount, unit: 'Star' }, + { icon: FaCodeFork, value: project.forksCount, unit: 'Fork' }, { - icon: faUsers, + icon: HiUserGroup, value: project.contributorsCount, unit: 'Contributor', }, { - icon: faExclamationCircle, + icon: FaExclamationCircle, value: project.issuesCount, unit: 'Issue', }, { - icon: faFolderOpen, + icon: FaFolderOpen, value: project.repositoriesCount, unit: 'Repository', pluralizedName: 'Repositories', diff --git a/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx b/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx index 08a934aa64..06004602ec 100644 --- a/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx +++ b/frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx @@ -1,18 +1,18 @@ 'use client' import { useQuery } from '@apollo/client/react' -import { - faPeopleGroup, - faCodeFork, - faDollar, - faCodePullRequest, - faChartArea, - faExclamationCircle, - faHandshake, - faStar, - faTags, -} from '@fortawesome/free-solid-svg-icons' import { useParams } from 'next/navigation' import { FC, useState, useEffect } from 'react' +import { FaExclamationCircle } from 'react-icons/fa' +import { + FaPeopleGroup, + FaCodeFork, + FaDollarSign, + FaCodePullRequest, + FaChartArea, + FaHandshake, + FaStar, + FaTags, +} from 'react-icons/fa6' import { handleAppError } from 'app/global-error' import { GetProjectHealthMetricsDetailsDocument } from 'types/__generated__/projectsHealthDashboardQueries.generated' import { HealthMetricsProps } from 'types/healthMetrics' @@ -78,7 +78,7 @@ const ProjectHealthMetricsDetails: FC = () => { ? 'Funding Requirements Compliant' : 'Funding Requirements Not Compliant' } - icon={faDollar} + icon={FaDollarSign} compliant={metricsLatest.isFundingRequirementsCompliant} /> { ? 'Leader Requirements Compliant' : 'Leader Requirements Not Compliant' } - icon={faHandshake} + icon={FaHandshake} compliant={metricsLatest.isLeaderRequirementsCompliant} />
@@ -95,7 +95,7 @@ const ProjectHealthMetricsDetails: FC = () => {
{ /> {
{ /> {
{ /> {
{ - if (isActiveSortDesc) { - return faSortDown - } else if (isActiveSortAsc) { - return faSortUp - } else { - return faSort - } - })() + let iconType: IconType + if (isActiveSortDesc) { + iconType = FaSortDown + } else if (isActiveSortAsc) { + iconType = FaSortUp + } else { + iconType = FaSort + } return (
@@ -114,8 +114,8 @@ const SortableColumnHeader: FC<{ aria-pressed={isActive} > {label} - @@ -272,7 +272,7 @@ const MetricsPage: FC = () => {
{
) } + const projectsCardsItems: { type: 'healthy' | 'needsAttention' | 'unhealthy' count: number - icon: IconProp + icon: IconType }[] = [ { type: 'healthy', count: stats.projectsCountHealthy, - icon: faCheck, + icon: FaCheck, }, { type: 'needsAttention', count: stats.projectsCountNeedAttention, - icon: faWarning, + icon: FaTriangleExclamation, }, { type: 'unhealthy', count: stats.projectsCountUnhealthy, - icon: faRectangleXmark, + icon: FaRectangleXmark, }, ] - const dashboardCardsItems = [ + const dashboardCardsItems: { + title: string + icon: IconType + stats?: string + }[] = [ { title: 'Average Score', - icon: faChartLine, + icon: FaChartLine, stats: `${stats.averageScore.toFixed(1)}`, }, { title: 'Contributors', - icon: faUsers, + icon: HiUserGroup, stats: millify(stats.totalContributors), }, { title: 'Forks', - icon: faCodeBranch, + icon: FaCodeBranch, stats: millify(stats.totalForks), }, { title: 'Stars', - icon: faStar, + icon: FaStar, stats: millify(stats.totalStars), }, ] @@ -118,7 +123,7 @@ const ProjectsDashboardPage: FC = () => {
{ /> { const { items: projects, @@ -39,7 +40,7 @@ const ProjectsPage = () => { const submitButton = { label: 'View Details', - icon: , + icon: , onclick: handleButtonClick, } diff --git a/frontend/src/app/settings/api-keys/page.tsx b/frontend/src/app/settings/api-keys/page.tsx index 94739c7f02..b042c21955 100644 --- a/frontend/src/app/settings/api-keys/page.tsx +++ b/frontend/src/app/settings/api-keys/page.tsx @@ -1,22 +1,13 @@ 'use client' import { useMutation, useQuery } from '@apollo/client/react' -import { - faSpinner, - faKey, - faPlus, - faCopy, - faEye, - faEyeSlash, - faInfoCircle, - faTrash, -} from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Button } from '@heroui/button' import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from '@heroui/modal' import { Input } from '@heroui/react' import { addToast } from '@heroui/toast' import { format, addDays } from 'date-fns' import { useState } from 'react' +import { FaInfoCircle } from 'react-icons/fa' +import { FaSpinner, FaKey, FaPlus, FaCopy, FaEye, FaEyeSlash, FaTrash } from 'react-icons/fa6' import { CreateApiKeyDocument, GetApiKeysDocument, @@ -174,10 +165,7 @@ export default function Page() {
- +

API Key Limits

@@ -201,7 +189,7 @@ export default function Page() {

- +

Your API Keys

@@ -257,7 +245,7 @@ export default function Page() { onPress={() => setKeyToRevoke(key)} className="text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20" > - + @@ -323,10 +311,10 @@ export default function Page() { onPress={() => setShowNewKey(!showNewKey)} isIconOnly > - + {showNewKey ? : }
@@ -399,7 +387,7 @@ export default function Page() { onPress={handleCreateKey} isDisabled={createLoading || !newKeyName.trim()} > - {createLoading && } + {createLoading && } Create API Key @@ -418,7 +406,7 @@ export default function Page() {

- + After revoking this key, you'll be able to create a new one if needed.

diff --git a/frontend/src/components/AnchorTitle.tsx b/frontend/src/components/AnchorTitle.tsx index 6482623e3f..a03cd5f6a4 100644 --- a/frontend/src/components/AnchorTitle.tsx +++ b/frontend/src/components/AnchorTitle.tsx @@ -1,6 +1,5 @@ -import { faLink } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import React, { useEffect, useCallback } from 'react' +import { FaLink } from 'react-icons/fa6' import { scrollToAnchor, scrollToAnchorWithHistory } from 'utils/scrollToAnchor' import slugify from 'utils/slugify' @@ -52,7 +51,7 @@ const AnchorTitle: React.FC = ({ title }) => { onClick={handleClick} aria-label={`Link to ${title} section`} > - +
diff --git a/frontend/src/components/Badges.tsx b/frontend/src/components/Badges.tsx index 1af558aa8a..d8f6b868df 100644 --- a/frontend/src/components/Badges.tsx +++ b/frontend/src/components/Badges.tsx @@ -1,5 +1,5 @@ import { Tooltip } from '@heroui/tooltip' -import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper' +import { IconWrapper } from 'wrappers/IconWrapper' import { BADGE_CLASS_MAP } from 'utils/data' type BadgeProps = { @@ -14,7 +14,6 @@ const normalizeCssClass = (cssClass: string | undefined) => { if (!cssClass || cssClass.trim() === '') { return '' } - // Convert backend snake_case format to frontend camelCase format return cssClass.trim().replaceAll(/_([a-z])/g, (_, letter) => letter.toUpperCase()) } @@ -30,7 +29,7 @@ const Badges = ({ name, cssClass, showTooltip = true }: BadgeProps) => { return (
- +
) diff --git a/frontend/src/components/BarChart.tsx b/frontend/src/components/BarChart.tsx index ebe5b0edbd..b8c1d8f271 100644 --- a/frontend/src/components/BarChart.tsx +++ b/frontend/src/components/BarChart.tsx @@ -1,7 +1,7 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core' import dynamic from 'next/dynamic' import { useTheme } from 'next-themes' import React from 'react' +import type { IconType } from 'react-icons' import { ApexBarChartDataSeries } from 'types/healthMetrics' import AnchorTitle from 'components/AnchorTitle' import SecondaryCard from 'components/SecondaryCard' @@ -13,7 +13,7 @@ const Chart = dynamic(() => import('react-apexcharts'), { const BarChart: React.FC<{ title: string - icon?: IconProp + icon?: IconType labels: string[] days: number[] requirements: number[] diff --git a/frontend/src/components/BreadCrumbs.tsx b/frontend/src/components/BreadCrumbs.tsx index ebc27cb868..2df6047782 100644 --- a/frontend/src/components/BreadCrumbs.tsx +++ b/frontend/src/components/BreadCrumbs.tsx @@ -1,7 +1,6 @@ -import { faChevronRight } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Breadcrumbs, BreadcrumbItem as HeroUIBreadcrumbItem } from '@heroui/react' import Link from 'next/link' +import { FaChevronRight } from 'react-icons/fa' import type { BreadcrumbItem } from 'types/breadcrumb' type BreadCrumbRendererProps = Readonly<{ @@ -14,12 +13,7 @@ export default function BreadCrumbRenderer({ items }: BreadCrumbRendererProps) {
- } + separator={} className="text-gray-800 dark:text-gray-200" itemClasses={{ base: 'transition-colors duration-200', diff --git a/frontend/src/components/BreadCrumbsWrapper.tsx b/frontend/src/components/BreadCrumbsWrapper.tsx index 50995f1d5d..40138c117d 100644 --- a/frontend/src/components/BreadCrumbsWrapper.tsx +++ b/frontend/src/components/BreadCrumbsWrapper.tsx @@ -1,11 +1,10 @@ 'use client' -import { faChevronRight } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Breadcrumbs, BreadcrumbItem as HeroUIBreadcrumbItem } from '@heroui/react' import { useBreadcrumbs } from 'hooks/useBreadcrumbs' import Link from 'next/link' import { usePathname } from 'next/navigation' +import { FaChevronRight } from 'react-icons/fa6' export default function BreadCrumbsWrapper() { const pathname = usePathname() @@ -18,12 +17,7 @@ export default function BreadCrumbsWrapper() {
- } + separator={} className="text-gray-800 dark:text-gray-200" itemClasses={{ base: 'transition-colors duration-200', diff --git a/frontend/src/components/CalendarButton.tsx b/frontend/src/components/CalendarButton.tsx index 089cc5fe10..0c95745851 100644 --- a/frontend/src/components/CalendarButton.tsx +++ b/frontend/src/components/CalendarButton.tsx @@ -1,6 +1,5 @@ -import { faCalendar, faCalendarPlus } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { useState } from 'react' +import { FaCalendar, FaCalendarPlus } from 'react-icons/fa6' import type { CalendarButtonProps } from 'types/calendar' import getGoogleCalendarUrl from 'utils/getGoogleCalendarUrl' @@ -25,13 +24,16 @@ export default function CalendarButton(props: Readonly) { rel="noopener noreferrer" aria-label={ariaLabel} title={ariaLabel} - className={className} + className={`flex items-center ${className}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > - {icon || ( - - )} + {icon || + (isHovered ? ( + + ) : ( + + ))} {showLabel && {label}} ) diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx index 26d785a1d5..7dbb47a3c2 100644 --- a/frontend/src/components/Card.tsx +++ b/frontend/src/components/Card.tsx @@ -1,8 +1,7 @@ -import { faCalendar } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Tooltip } from '@heroui/tooltip' import Link from 'next/link' -import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper' +import { FaCalendar } from 'react-icons/fa6' +import { IconWrapper } from 'wrappers/IconWrapper' import type { CardProps } from 'types/card' import { ICONS } from 'utils/data' import { formatDateRange } from 'utils/dateFormatter' @@ -45,7 +44,7 @@ const Card = ({ className="flex h-8 w-8 min-w-8 items-center justify-center rounded-full text-xs shadow" style={{ backgroundColor: level.color }} > - + )} @@ -80,7 +79,7 @@ const Card = ({ {/* Timeline Section (Optional) */} {timeline?.start && timeline?.end && (
- + {formatDateRange(timeline.start, timeline.end)}
)} @@ -106,21 +105,21 @@ const Card = ({ {social && social.length > 0 && (
- {social.map((item) => ( - - - - ))} + {social.map((item) => { + const SocialIcon = getSocialIcon(item.url) + return ( + + + + ) + })}
)} diff --git a/frontend/src/components/CardDetailsPage.tsx b/frontend/src/components/CardDetailsPage.tsx index ab5d4a2314..a138984e7b 100644 --- a/frontend/src/components/CardDetailsPage.tsx +++ b/frontend/src/components/CardDetailsPage.tsx @@ -1,15 +1,14 @@ -import { - faCircleInfo, - faChartPie, - faFolderOpen, - faCode, - faTags, - faUsers, - faRectangleList, -} from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import upperFirst from 'lodash/upperFirst' import { useSession } from 'next-auth/react' +import { + FaCircleInfo, + FaChartPie, + FaFolderOpen, + FaCode, + FaTags, + FaRectangleList, +} from 'react-icons/fa6' +import { HiUserGroup } from 'react-icons/hi' import type { ExtendedSession } from 'types/auth' import type { DetailsCardProps } from 'types/card' import { IS_PROJECT_HEALTH_ENABLED } from 'utils/env.client' @@ -120,7 +119,7 @@ const DetailsCard = ({

{description}

{summary && ( - }> + }>

{summary}

)} @@ -128,7 +127,7 @@ const DetailsCard = ({ {userSummary && {userSummary}}
} className={secondaryCardStyles} > @@ -154,7 +153,7 @@ const DetailsCard = ({ type === 'user' || type === 'organization') && ( } className="md:col-span-2" > @@ -194,12 +193,12 @@ const DetailsCard = ({ {languages.length !== 0 && ( } /> )} {topics.length !== 0 && ( - } /> + } /> )}
)} @@ -212,7 +211,7 @@ const DetailsCard = ({ {tags?.length > 0 && ( } isDisabled={true} /> @@ -220,7 +219,7 @@ const DetailsCard = ({ {domains?.length > 0 && ( } isDisabled={true} /> @@ -231,7 +230,7 @@ const DetailsCard = ({
} isDisabled={true} /> @@ -243,13 +242,13 @@ const DetailsCard = ({ {topContributors && ( )} {admins && admins.length > 0 && type === 'program' && ( 0 && ( 0 && ( 0 && ( - }> + }> )} {type === 'program' && modules.length > 0 && ( } > @@ -339,17 +338,20 @@ export const SocialLinks = ({ urls }) => {
Social Links
- {urls.map((url) => ( - - - - ))} + {urls.map((url) => { + const SocialIcon = getSocialIcon(url) + return ( + + + + ) + })}
) diff --git a/frontend/src/components/ChapterMap.tsx b/frontend/src/components/ChapterMap.tsx index b471428ffe..ca27cad9ca 100644 --- a/frontend/src/components/ChapterMap.tsx +++ b/frontend/src/components/ChapterMap.tsx @@ -1,10 +1,10 @@ 'use client' -import { faLocationDot, faUnlock } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Button } from '@heroui/button' import { Tooltip } from '@heroui/tooltip' import L, { MarkerClusterGroup } from 'leaflet' import React, { useEffect, useRef, useState } from 'react' +import { FaUnlock } from 'react-icons/fa' +import { FaLocationDot } from 'react-icons/fa6' import type { Chapter } from 'types/chapter' import type { UserLocation } from 'utils/geolocationUtils' import 'leaflet.markercluster' @@ -198,7 +198,7 @@ const ChapterMap = ({ aria-label="Unlock map" >

-

@@ -220,7 +220,7 @@ const ChapterMap = ({ userLocation ? 'Reset location filter' : 'Share location to find nearby chapters' } > - + )} diff --git a/frontend/src/components/DashboardCard.tsx b/frontend/src/components/DashboardCard.tsx index 4c6787f190..365fe975cc 100644 --- a/frontend/src/components/DashboardCard.tsx +++ b/frontend/src/components/DashboardCard.tsx @@ -1,12 +1,12 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import React from 'react' +import type { IconType } from 'react-icons' +import { IconWrapper } from 'wrappers/IconWrapper' import AnchorTitle from 'components/AnchorTitle' import SecondaryCard from 'components/SecondaryCard' const DashboardCard: React.FC<{ readonly title: string - readonly icon: IconProp + readonly icon: IconType readonly stats?: string readonly className?: string }> = ({ title, icon, stats, className }) => { @@ -16,7 +16,7 @@ const DashboardCard: React.FC<{ className={`overflow-hidden transition-colors duration-300 hover:bg-blue-100 dark:hover:bg-blue-950 ${className}`} > - + {stats &&

{stats}

}
diff --git a/frontend/src/components/DisplayIcon.tsx b/frontend/src/components/DisplayIcon.tsx index 9bb0f888f5..569e247e7e 100644 --- a/frontend/src/components/DisplayIcon.tsx +++ b/frontend/src/components/DisplayIcon.tsx @@ -1,6 +1,6 @@ import { Tooltip } from '@heroui/tooltip' import { millify } from 'millify' -import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper' +import { IconWrapper } from 'wrappers/IconWrapper' import type { Icon } from 'types/icon' import { IconKeys, ICONS } from 'utils/data' @@ -19,7 +19,7 @@ export default function DisplayIcon({ item, icons }: { item: string; icons: Icon .filter(Boolean) .join(' ') - // className for the FontAwesome icon + // className for the icon const iconClassName = [ 'text-gray-600 dark:text-gray-300', item === 'stars_count' || item === 'starsCount' ? 'icon-rotate' : '', @@ -35,7 +35,7 @@ export default function DisplayIcon({ item, icons }: { item: string; icons: Icon return icons[item] ? ( {/* Display formatted number if the value is a number */} - {typeof icons[item] === 'number' - ? millify(icons[item], { precision: 1 }) // Format large numbers using 'millify' library - : icons[item]} + {typeof icons[item] === 'number' ? millify(icons[item], { precision: 1 }) : icons[item]} - +
diff --git a/frontend/src/components/DonutBarChart.tsx b/frontend/src/components/DonutBarChart.tsx index 532c8caaa1..c610275c1b 100644 --- a/frontend/src/components/DonutBarChart.tsx +++ b/frontend/src/components/DonutBarChart.tsx @@ -1,7 +1,7 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core' import dynamic from 'next/dynamic' import { useTheme } from 'next-themes' import React from 'react' +import type { IconType } from 'react-icons' import { round } from 'utils/round' import AnchorTitle from 'components/AnchorTitle' import SecondaryCard from 'components/SecondaryCard' @@ -12,7 +12,7 @@ const Chart = dynamic(() => import('react-apexcharts'), { }) const DonutBarChart: React.FC<{ - icon: IconProp + icon: IconType title: string series: number[] }> = ({ icon, title, series }) => { diff --git a/frontend/src/components/EntityActions.tsx b/frontend/src/components/EntityActions.tsx index 2af5205da3..8183d0f453 100644 --- a/frontend/src/components/EntityActions.tsx +++ b/frontend/src/components/EntityActions.tsx @@ -1,10 +1,9 @@ 'use client' -import { faEllipsisV } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { useRouter } from 'next/navigation' import type React from 'react' import { useState, useRef, useEffect } from 'react' +import { FaEllipsisV } from 'react-icons/fa' import { ProgramStatusEnum } from 'types/__generated__/graphql' interface EntityActionsProps { @@ -105,10 +104,7 @@ const EntityActions: React.FC = ({ aria-expanded={dropdownOpen} aria-haspopup="true" > - + {dropdownOpen && (
diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index abff0939ce..54e98b3bd2 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -1,9 +1,8 @@ 'use client' -import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Button } from '@heroui/button' import Link from 'next/link' import { useState, useCallback } from 'react' +import { FaChevronDown, FaChevronUp } from 'react-icons/fa6' import type { Section } from 'types/section' import { footerIcons } from 'utils/constants' import { footerSections } from 'utils/constants' @@ -36,9 +35,9 @@ export default function Footer() {

{section.title}

{openSection === section.title ? ( - + ) : ( - + )}
@@ -70,18 +69,21 @@ export default function Footer() { {/* Social Media Icons Section */}
- {footerIcons.map((social) => ( - - - - ))} + {footerIcons.map((social) => { + const SocialIcon = social.icon + return ( + + + + ) + })}
{/* Footer bottom section with copyright and version */}
diff --git a/frontend/src/components/GeneralCompliantComponent.tsx b/frontend/src/components/GeneralCompliantComponent.tsx index df4c36fbe4..c7cdfaf0ad 100644 --- a/frontend/src/components/GeneralCompliantComponent.tsx +++ b/frontend/src/components/GeneralCompliantComponent.tsx @@ -1,22 +1,22 @@ 'use client' -import { IconProp } from '@fortawesome/fontawesome-svg-core' -import { faCertificate } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Tooltip } from '@heroui/tooltip' import clsx from 'clsx' import { FC } from 'react' +import type { IconType } from 'react-icons' +import { FaCertificate } from 'react-icons/fa6' +import { IconWrapper } from 'wrappers/IconWrapper' const GeneralCompliantComponent: FC<{ readonly compliant: boolean - readonly icon: IconProp + readonly icon: IconType readonly title: string }> = ({ icon, compliant, title }) => { return (
- - Open main menu - {mobileMenuOpen ? ( - - ) : ( - - )} + {mobileMenuOpen ? : }
@@ -217,16 +215,16 @@ export default function Header({ isGitHubAuthEnabled }: { readonly isGitHubAuthE {isMobile && } = ({ data }) => { }, ]} labels={labels} - icon={faExclamationCircle} + icon={FaExclamationCircle} /> = ({ data }) => { }, ]} labels={labels} - icon={faCodePullRequest} + icon={FaCodePullRequest} />
@@ -66,7 +61,7 @@ const HealthMetrics: React.FC<{ data: HealthMetricsProps[] }> = ({ data }) => { }, ]} labels={labels} - icon={faStar} + icon={FaStar} /> = ({ data }) => { }, ]} labels={labels} - icon={faCodeFork} + icon={FaCodeFork} />
- +
{label &&
{label}
} diff --git a/frontend/src/components/InfoItem.tsx b/frontend/src/components/InfoItem.tsx index 9bc10ca295..aa268c2bd6 100644 --- a/frontend/src/components/InfoItem.tsx +++ b/frontend/src/components/InfoItem.tsx @@ -1,7 +1,7 @@ -import { IconDefinition } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Tooltip } from '@heroui/tooltip' import millify from 'millify' +import type { IconType } from 'react-icons' +import { IconWrapper } from 'wrappers/IconWrapper' import { pluralize } from 'utils/pluralize' const InfoItem = ({ @@ -11,7 +11,7 @@ const InfoItem = ({ unit, value, }: { - icon: IconDefinition + icon: IconType pluralizedName?: string precision?: number unit: string @@ -24,7 +24,7 @@ const InfoItem = ({ return (
- + {name} @@ -39,13 +39,13 @@ export const TextInfoItem = ({ label, value, }: { - icon: IconDefinition + icon: IconType label: string value: string }) => { return (
- + {label}: {value}
) diff --git a/frontend/src/components/ItemCardList.tsx b/frontend/src/components/ItemCardList.tsx index 99a55f97fa..c8fd4c7403 100644 --- a/frontend/src/components/ItemCardList.tsx +++ b/frontend/src/components/ItemCardList.tsx @@ -1,8 +1,8 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core' import { Tooltip } from '@heroui/tooltip' import Image from 'next/image' import Link from 'next/link' import React, { JSX } from 'react' +import type { IconType } from 'react-icons' import type { Issue } from 'types/issue' import type { Milestone } from 'types/milestone' import type { PullRequest } from 'types/pullRequest' @@ -20,7 +20,7 @@ const ItemCardList = ({ }: { title: React.ReactNode data: Issue[] | Milestone[] | PullRequest[] | Release[] - icon?: IconProp + icon?: IconType showAvatar?: boolean showSingleColumn?: boolean renderDetails: (item: { diff --git a/frontend/src/components/Leaders.tsx b/frontend/src/components/Leaders.tsx index e619f83355..9f824b005e 100644 --- a/frontend/src/components/Leaders.tsx +++ b/frontend/src/components/Leaders.tsx @@ -1,7 +1,7 @@ -import { faPersonWalkingArrowRight, faRightToBracket } from '@fortawesome/free-solid-svg-icons' import { useRouter } from 'next/navigation' import React from 'react' -import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper' +import { FaPersonWalkingArrowRight, FaRightToBracket } from 'react-icons/fa6' +import { IconWrapper } from 'wrappers/IconWrapper' import type { Leader } from 'types/leader' import AnchorTitle from 'components/AnchorTitle' import SecondaryCard from 'components/SecondaryCard' @@ -23,14 +23,14 @@ const Leaders: React.FC = ({ users }) => { } return ( - }> + }>
{users.map((user) => ( , + icon: , label: 'View Profile', onclick: () => handleButtonClick(user), }} diff --git a/frontend/src/components/LineChart.tsx b/frontend/src/components/LineChart.tsx index cecb5df6ec..65ba1a3700 100644 --- a/frontend/src/components/LineChart.tsx +++ b/frontend/src/components/LineChart.tsx @@ -1,7 +1,7 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core' import dynamic from 'next/dynamic' import { useTheme } from 'next-themes' import React from 'react' +import type { IconType } from 'react-icons' import type { ApexLineChartSeries } from 'types/healthMetrics' import AnchorTitle from 'components/AnchorTitle' import SecondaryCard from 'components/SecondaryCard' @@ -14,7 +14,7 @@ const LineChart: React.FC<{ title: string series: ApexLineChartSeries[] labels?: string[] - icon?: IconProp + icon?: IconType round?: boolean }> = ({ title, series, labels, icon, round }) => { const { theme } = useTheme() diff --git a/frontend/src/components/LoginPageContent.tsx b/frontend/src/components/LoginPageContent.tsx index 29e0e30e01..281200b453 100644 --- a/frontend/src/components/LoginPageContent.tsx +++ b/frontend/src/components/LoginPageContent.tsx @@ -1,12 +1,10 @@ 'use client' -import { faGithub } from '@fortawesome/free-brands-svg-icons' -import { faSpinner } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { addToast } from '@heroui/toast' import { useRouter } from 'next/navigation' import { useSession, signIn } from 'next-auth/react' import { FC, useCallback, useEffect } from 'react' +import { FaGithub, FaSpinner } from 'react-icons/fa' import { userAuthStatus } from 'utils/constants' type LoginPageContentProps = { @@ -45,7 +43,7 @@ const LoginPageContent: FC = ({ isGitHubAuthEnabled }) => if (status === userAuthStatus.LOADING) { return (
- + Checking session...
) @@ -54,7 +52,7 @@ const LoginPageContent: FC = ({ isGitHubAuthEnabled }) => if (status === userAuthStatus.AUTHENTICATED) { return (
- + Redirecting...
) @@ -75,7 +73,7 @@ const LoginPageContent: FC = ({ isGitHubAuthEnabled }) => onClick={() => signIn('github', { callbackUrl: '/' })} className="flex w-full items-center justify-center gap-2 rounded-lg bg-black px-4 py-2 font-medium text-white transition-colors hover:bg-gray-900/90" > - + Sign In with GitHub
diff --git a/frontend/src/components/MenteeContributorsList.tsx b/frontend/src/components/MenteeContributorsList.tsx index 9517cd38d0..9c1d94f9b4 100644 --- a/frontend/src/components/MenteeContributorsList.tsx +++ b/frontend/src/components/MenteeContributorsList.tsx @@ -1,8 +1,8 @@ -import type { IconProp } from '@fortawesome/fontawesome-svg-core' 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' @@ -12,7 +12,7 @@ interface MenteeContributorsListProps { contributors: Contributor[] label?: string maxInitialDisplay?: number - icon?: IconProp + icon?: IconType programKey: string moduleKey: string } diff --git a/frontend/src/components/MenteeIssues.tsx b/frontend/src/components/MenteeIssues.tsx index b793284a06..71c4f230fd 100644 --- a/frontend/src/components/MenteeIssues.tsx +++ b/frontend/src/components/MenteeIssues.tsx @@ -1,7 +1,7 @@ -import { faBug, faCheckCircle, faClock } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import type React from 'react' import { useState } from 'react' +import { FaBug, FaCheckCircle, FaClock } from 'react-icons/fa' +import { IconWrapper } from 'wrappers/IconWrapper' import type { Issue } from 'types/issue' import { formatDate } from 'utils/dateFormatter' import SecondaryCard from 'components/SecondaryCard' @@ -31,11 +31,11 @@ const MenteeIssues: React.FC = ({ openIssues, closedIssues, m const getStateIcon = (state: string) => { switch (state.toLowerCase()) { case 'open': - return faBug + return FaBug case 'closed': - return faCheckCircle + return FaCheckCircle default: - return faClock + return FaClock } } @@ -43,8 +43,8 @@ const MenteeIssues: React.FC = ({ openIssues, closedIssues, m
{issues.length === 0 ? (
-

No {title.toLowerCase()} issues

@@ -58,7 +58,7 @@ const MenteeIssues: React.FC = ({ openIssues, closedIssues, m
- @@ -113,7 +113,7 @@ const MenteeIssues: React.FC = ({ openIssues, closedIssues, m ) return ( - + {/* Tab Navigation */}
diff --git a/frontend/src/components/MetricsPDFButton.tsx b/frontend/src/components/MetricsPDFButton.tsx index 1a1db63d0d..bfe5d88aee 100644 --- a/frontend/src/components/MetricsPDFButton.tsx +++ b/frontend/src/components/MetricsPDFButton.tsx @@ -1,9 +1,8 @@ 'use client' -import { faFileArrowDown } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Tooltip } from '@heroui/tooltip' import { FC } from 'react' +import { FaFileArrowDown } from 'react-icons/fa6' import { fetchMetricsPDF } from 'server/fetchMetricsPDF' const MetricsPDFButton: FC<{ @@ -19,8 +18,7 @@ const MetricsPDFButton: FC<{ closeDelay={100} showArrow > - await fetchMetricsPDF(path, fileName)} /> diff --git a/frontend/src/components/Milestones.tsx b/frontend/src/components/Milestones.tsx index 2e0102fc38..ef02809d4f 100644 --- a/frontend/src/components/Milestones.tsx +++ b/frontend/src/components/Milestones.tsx @@ -1,13 +1,12 @@ -import { - faCalendar, - faFolderOpen, - faSignsPost, - faCircleCheck, - faCircleExclamation, -} from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { useRouter } from 'next/navigation' import React from 'react' +import { + FaCalendar, + FaFolderOpen, + FaSignsPost, + FaCircleCheck, + FaCircleExclamation, +} from 'react-icons/fa6' import type { Milestone } from 'types/milestone' import { formatDate } from 'utils/dateFormatter' import AnchorTitle from 'components/AnchorTitle' @@ -36,25 +35,25 @@ const Milestones: React.FC = ({ } data={data} showAvatar={showAvatar} - icon={faSignsPost} + icon={FaSignsPost} showSingleColumn={showSingleColumn} renderDetails={(item) => (
- + {formatDate(item.createdAt)}
- + {item.closedIssuesCount} closed
- + {item.openIssuesCount} open
{item?.repositoryName && (
- + diff --git a/frontend/src/components/ModuleCard.tsx b/frontend/src/components/ModuleCard.tsx index 75669f283d..5837b098a4 100644 --- a/frontend/src/components/ModuleCard.tsx +++ b/frontend/src/components/ModuleCard.tsx @@ -1,15 +1,8 @@ -import { - faChevronDown, - faChevronUp, - faLevelUpAlt, - faCalendarAlt, - faHourglassHalf, -} from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import upperFirst from 'lodash/upperFirst' import Link from 'next/link' import { usePathname } from 'next/navigation' import { useState } from 'react' +import { FaChevronDown, FaChevronUp, FaTurnUp, FaCalendar, FaHourglassHalf } from 'react-icons/fa6' import type { Module } from 'types/mentorship' import { formatDate } from 'utils/dateFormatter' import { TextInfoItem } from 'components/InfoItem' @@ -49,11 +42,11 @@ const ModuleCard = ({ modules, accessLevel, admins }: ModuleCardProps) => { > {showAllModule ? ( <> - Show less + Show less ) : ( <> - Show more + Show more )} @@ -73,10 +66,10 @@ const ModuleItem = ({ module, isAdmin }: { module: Module; isAdmin: boolean }) = > - - + + diff --git a/frontend/src/components/MultiSearch.tsx b/frontend/src/components/MultiSearch.tsx index b70fb4611a..8ea4a5cc8f 100644 --- a/frontend/src/components/MultiSearch.tsx +++ b/frontend/src/components/MultiSearch.tsx @@ -1,19 +1,11 @@ -import { faAlgolia } from '@fortawesome/free-brands-svg-icons' -import { - faSearch, - faTimes, - faUser, - faCalendar, - faFolder, - faBuilding, - faLocationDot, -} from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { sendGAEvent } from '@next/third-parties/google' import { debounce } from 'lodash' import { useRouter } from 'next/navigation' import type React from 'react' import { useState, useEffect, useMemo, useCallback, useRef } from 'react' +import { FaTimes, FaSearch } from 'react-icons/fa' +import { FaUser, FaCalendar, FaFolder, FaBuilding, FaLocationDot } from 'react-icons/fa6' +import { SiAlgolia } from 'react-icons/si' import { fetchAlgoliaData } from 'server/fetchAlgoliaData' import type { Chapter } from 'types/chapter' import type { Event } from 'types/event' @@ -207,17 +199,17 @@ const MultiSearchBar: React.FC = ({ const getIconForIndex = (indexName: string) => { switch (indexName) { case 'chapters': - return faLocationDot + return case 'events': - return faCalendar + return case 'organizations': - return faBuilding + return case 'projects': - return faFolder + return case 'users': - return faUser + return default: - return faSearch + return } } @@ -226,8 +218,7 @@ const MultiSearchBar: React.FC = ({
{isLoaded ? ( <> -
diff --git a/frontend/src/components/NavButton.tsx b/frontend/src/components/NavButton.tsx index 946b1f0913..bfc91998bb 100644 --- a/frontend/src/components/NavButton.tsx +++ b/frontend/src/components/NavButton.tsx @@ -1,6 +1,7 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import Link from 'next/link' import { useState } from 'react' +import type { IconType } from 'react-icons' +import { IconWrapper } from 'wrappers/IconWrapper' import type { NavButtonProps } from 'types/button' import { cn } from 'utils/utility' @@ -12,7 +13,7 @@ const NavButton = ({ hoverIconColor, text, className, -}: NavButtonProps) => { +}: NavButtonProps & { defaultIcon: IconType; hoverIcon: IconType }) => { const [isHovered, setIsHovered] = useState(false) return ( @@ -27,7 +28,7 @@ const NavButton = ({ onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > - sub.href).includes(pathname) && + link.submenu?.map((sub) => sub.href).includes(pathname) && 'font-bold text-blue-800 dark:text-white' )} > @@ -60,7 +59,7 @@ export default function NavDropdown({ link, pathname }: NavDropDownProps) { aria-hidden="true" style={{ transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)' }} > - + {isOpen && ( @@ -68,7 +67,7 @@ export default function NavDropdown({ link, pathname }: NavDropDownProps) { id={dropdownId} className="absolute top-full left-0 z-10 mt-1 w-48 overflow-hidden rounded-md bg-white shadow-lg dark:bg-slate-800" > - {link.submenu.map((submenu, idx) => ( + {link.submenu?.map((submenu, idx) => ( = ({ role="presentation" aria-label="More pages" > - + ) : (
} data={data} - icon={faCodePullRequest} + icon={FaCodePullRequest} showAvatar={showAvatar} renderDetails={(item) => (
- + {formatDate(item.createdAt)}
{item?.repositoryName && (
- +
- +
- +
- - + + - + ) } diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index 0d514e66f4..565db57798 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -1,10 +1,9 @@ -import { faSearch, faTimes } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Skeleton } from '@heroui/skeleton' import { sendGTMEvent } from '@next/third-parties/google' import { debounce } from 'lodash' import { usePathname } from 'next/navigation' import React, { useEffect, useRef, useState, useMemo } from 'react' +import { FaSearch, FaTimes } from 'react-icons/fa' interface SearchProps { isLoaded: boolean @@ -72,9 +71,10 @@ const SearchBar: React.FC = ({
{!isLoaded ? ( <> -