diff --git a/frontend/__tests__/unit/components/ModuleList.test.tsx b/frontend/__tests__/unit/components/ModuleList.test.tsx
index ca151c466d..290e9ea4d8 100644
--- a/frontend/__tests__/unit/components/ModuleList.test.tsx
+++ b/frontend/__tests__/unit/components/ModuleList.test.tsx
@@ -5,12 +5,17 @@ import ModuleList from 'components/ModuleList'
// Mock FontAwesome icons
jest.mock('@fortawesome/react-fontawesome', () => ({
- FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => (
-
- ),
+ FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => {
+ let iconName = 'unknown'
+
+ if (icon === faChevronDown) {
+ iconName = 'chevron-down'
+ } else if (icon === faChevronUp) {
+ iconName = 'chevron-up'
+ }
+
+ return
+ },
}))
// Mock HeroUI Button component
diff --git a/frontend/__tests__/unit/components/ProgramCard.test.tsx b/frontend/__tests__/unit/components/ProgramCard.test.tsx
index b7fb66cc9e..cc4a6def7a 100644
--- a/frontend/__tests__/unit/components/ProgramCard.test.tsx
+++ b/frontend/__tests__/unit/components/ProgramCard.test.tsx
@@ -7,12 +7,17 @@ import type { Program } from 'types/mentorship'
import ProgramCard from 'components/ProgramCard'
jest.mock('@fortawesome/react-fontawesome', () => ({
- FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => (
-
- ),
+ FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => {
+ let iconName = 'unknown'
+
+ if (icon === faEye) {
+ iconName = 'eye'
+ } else if (icon === faEdit) {
+ iconName = 'edit'
+ }
+
+ return
+ },
}))
jest.mock('components/ActionButton', () => ({
diff --git a/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx b/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx
index c2323e1a7b..d9f47c0aa7 100644
--- a/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx
+++ b/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx
@@ -148,6 +148,15 @@ describe('MetricsPage', () => {
})
})
+ test('SortableColumnHeader applies correct alignment classes', async () => {
+ render()
+ const sortButton = await screen.findByTitle('Sort by Stars')
+ const wrapperDiv = sortButton.closest('div')
+ expect(wrapperDiv).not.toBeNull()
+ expect(wrapperDiv).toHaveClass('justify-center')
+ expect(sortButton).toHaveClass('text-center')
+ })
+
test('handles sorting state and URL updates', async () => {
const mockReplace = jest.fn()
const { useRouter, useSearchParams } = jest.requireMock('next/navigation')
diff --git a/frontend/__tests__/unit/utils/milestoneProgress.test.ts b/frontend/__tests__/unit/utils/milestoneProgress.test.ts
new file mode 100644
index 0000000000..e08e3b7935
--- /dev/null
+++ b/frontend/__tests__/unit/utils/milestoneProgress.test.ts
@@ -0,0 +1,33 @@
+import { faCircleCheck, faClock, faUserGear } from '@fortawesome/free-solid-svg-icons'
+
+import { getMilestoneProgressIcon, getMilestoneProgressText } from 'utils/milestoneProgress'
+
+describe('milestone progress helpers', () => {
+ describe('getMilestoneProgressText', () => {
+ test('returns "Completed" when progress is 100', () => {
+ expect(getMilestoneProgressText(100)).toBe('Completed')
+ })
+
+ test('returns "In Progress" when progress is between 1 and 99', () => {
+ expect(getMilestoneProgressText(50)).toBe('In Progress')
+ })
+
+ test('returns "Not Started" when progress is 0', () => {
+ expect(getMilestoneProgressText(0)).toBe('Not Started')
+ })
+ })
+
+ describe('getMilestoneProgressIcon', () => {
+ test('returns faCircleCheck when progress is 100', () => {
+ expect(getMilestoneProgressIcon(100)).toBe(faCircleCheck)
+ })
+
+ test('returns faUserGear when progress is between 1 and 99', () => {
+ expect(getMilestoneProgressIcon(50)).toBe(faUserGear)
+ })
+
+ test('returns faClock when progress is 0', () => {
+ expect(getMilestoneProgressIcon(0)).toBe(faClock)
+ })
+ })
+})
diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx
index 0f0b9549fb..5c8e8891ad 100644
--- a/frontend/src/app/about/page.tsx
+++ b/frontend/src/app/about/page.tsx
@@ -3,7 +3,6 @@ import { useQuery } from '@apollo/client/react'
import {
faCircleCheck,
faClock,
- faUserGear,
faMapSigns,
faScroll,
faUsers,
@@ -34,6 +33,7 @@ import {
projectTimeline,
projectStory,
} from 'utils/aboutData'
+import { getMilestoneProgressIcon, getMilestoneProgressText } from 'utils/milestoneProgress'
import AnchorTitle from 'components/AnchorTitle'
import AnimatedCounter from 'components/AnimatedCounter'
import Leaders from 'components/Leaders'
@@ -218,28 +218,14 @@ const About = () => {
0
- ? 'In Progress'
- : 'Not Started'
- }
+ content={getMilestoneProgressText(milestone.progress)}
id={`tooltip-state-${index}`}
delay={100}
placement="top"
showArrow
>
- 0
- ? faUserGear
- : faClock
- }
- />
+
diff --git a/frontend/src/app/global-error.tsx b/frontend/src/app/global-error.tsx
index e984ccf804..69d7b766fc 100644
--- a/frontend/src/app/global-error.tsx
+++ b/frontend/src/app/global-error.tsx
@@ -64,10 +64,14 @@ export class AppError extends Error {
}
export const handleAppError = (error: unknown) => {
- const appError =
- error instanceof AppError
- ? error
- : new AppError(500, error instanceof Error ? error.message : ERROR_CONFIGS['500'].message)
+ let appError: AppError
+
+ if (error instanceof AppError) {
+ appError = error
+ } else {
+ const message = error instanceof Error ? error.message : ERROR_CONFIGS['500'].message
+ appError = new AppError(500, message)
+ }
if (appError.statusCode >= 500) {
Sentry.captureException(error instanceof Error ? error : appError)
diff --git a/frontend/src/app/projects/dashboard/metrics/page.tsx b/frontend/src/app/projects/dashboard/metrics/page.tsx
index af4048040a..22b9bb864b 100644
--- a/frontend/src/app/projects/dashboard/metrics/page.tsx
+++ b/frontend/src/app/projects/dashboard/metrics/page.tsx
@@ -77,10 +77,35 @@ const SortableColumnHeader: FC<{
}
}
- const alignmentClass =
- align === 'center' ? 'justify-center' : align === 'right' ? 'justify-end' : 'justify-start'
- const textAlignClass =
- align === 'center' ? 'text-center' : align === 'right' ? 'text-right' : 'text-left'
+ const alignmentClass = (() => {
+ if (align === 'center') {
+ return 'justify-center'
+ } else if (align === 'right') {
+ return 'justify-end'
+ } else {
+ return 'justify-start'
+ }
+ })()
+
+ const textAlignClass = (() => {
+ if (align === 'center') {
+ return 'text-center'
+ } else if (align === 'right') {
+ return 'text-right'
+ } else {
+ return 'text-left'
+ }
+ })()
+
+ const fontAwesomeIconType = (() => {
+ if (isActiveSortDesc) {
+ return faSortDown
+ } else if (isActiveSortAsc) {
+ return faSortUp
+ } else {
+ return faSort
+ }
+ })()
return (
@@ -93,7 +118,7 @@ const SortableColumnHeader: FC<{
>
{label}
diff --git a/frontend/src/components/CardDetailsPage.tsx b/frontend/src/components/CardDetailsPage.tsx
index ca2055cebb..99fce967e3 100644
--- a/frontend/src/components/CardDetailsPage.tsx
+++ b/frontend/src/components/CardDetailsPage.tsx
@@ -73,6 +73,17 @@ const DetailsCard = ({
}: DetailsCardProps) => {
const { data } = useSession()
const router = useRouter()
+
+ // compute styles based on type prop
+ const secondaryCardStyles = (() => {
+ if (type === 'program' || type === 'module') {
+ return 'gap-2 md:col-span-7'
+ } else if (type === 'chapter') {
+ return 'gap-2 md:col-span-3'
+ }
+ return 'gap-2 md:col-span-5'
+ })()
+
return (
@@ -122,13 +133,7 @@ const DetailsCard = ({
}
- className={
- type === 'program' || type === 'module'
- ? 'gap-2 md:col-span-7'
- : type !== 'chapter'
- ? 'gap-2 md:col-span-5'
- : 'gap-2 md:col-span-3'
- }
+ className={secondaryCardStyles}
>
{details?.map((detail) =>
detail?.label === 'Leaders' ? (
diff --git a/frontend/src/components/ProgramCard.tsx b/frontend/src/components/ProgramCard.tsx
index d6b335e3d6..e5fe1c46b5 100644
--- a/frontend/src/components/ProgramCard.tsx
+++ b/frontend/src/components/ProgramCard.tsx
@@ -31,6 +31,17 @@ const ProgramCard: React.FC
= ({ program, onEdit, onView, acce
? `${program.description.slice(0, 100)}...`
: program.description || 'No description available.'
+ // computes a formatted date string for the program based on its start and end dates.
+ const dateInfo = (() => {
+ if (program.startedAt && program.endedAt) {
+ return `${formatDate(program.startedAt)} – ${formatDate(program.endedAt)}`
+ } else if (program.startedAt) {
+ return `Started: ${formatDate(program.startedAt)}`
+ } else {
+ return 'No dates set'
+ }
+ })()
+
return (
@@ -47,13 +58,7 @@ const ProgramCard: React.FC
= ({ program, onEdit, onView, acce
)}
-
- {program.startedAt && program.endedAt
- ? `${formatDate(program.startedAt)} – ${formatDate(program.endedAt)}`
- : program.startedAt
- ? `Started: ${formatDate(program.startedAt)}`
- : 'No dates set'}
-
+
{dateInfo}
{description}
diff --git a/frontend/src/hooks/useSearchPage.ts b/frontend/src/hooks/useSearchPage.ts
index dd92aa9545..0b37d6bd3b 100644
--- a/frontend/src/hooks/useSearchPage.ts
+++ b/frontend/src/hooks/useSearchPage.ts
@@ -82,10 +82,19 @@ export function useSearchPage({
const fetchData = async () => {
try {
+ let computedIndexName = indexName
+
+ // check if valid sort option is selected
+ const hasValidSort = sortBy && sortBy !== 'default' && sortBy[0] !== 'default'
+
+ if (hasValidSort) {
+ // if sorting is active then appends the sort field and order to the base index name.
+ const orderSuffix = order && order !== '' ? `_${order}` : ''
+ computedIndexName = `${indexName}_${sortBy}${orderSuffix}`
+ }
+
const response = await fetchAlgoliaData(
- sortBy && sortBy !== 'default' && sortBy[0] !== 'default'
- ? `${indexName}_${sortBy}${order && order !== '' ? `_${order}` : ''}`
- : indexName,
+ computedIndexName,
searchQuery,
currentPage,
hitsPerPage
diff --git a/frontend/src/utils/milestoneProgress.ts b/frontend/src/utils/milestoneProgress.ts
new file mode 100644
index 0000000000..544c547149
--- /dev/null
+++ b/frontend/src/utils/milestoneProgress.ts
@@ -0,0 +1,22 @@
+import { faCircleCheck, faClock, faUserGear } from '@fortawesome/free-solid-svg-icons'
+
+// helper functions used in about/page.tsx
+export const getMilestoneProgressText = (progress: number): string => {
+ if (progress === 100) {
+ return 'Completed'
+ } else if (progress > 0) {
+ return 'In Progress'
+ } else {
+ return 'Not Started'
+ }
+}
+
+export const getMilestoneProgressIcon = (progress: number) => {
+ if (progress === 100) {
+ return faCircleCheck
+ } else if (progress > 0) {
+ return faUserGear
+ } else {
+ return faClock
+ }
+}