Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ErrorDisplay, handleAppError } from 'app/global-error'
import { GetProgramAdminsAndModulesDocument } from 'types/__generated__/moduleQueries.generated'
import { formatDate } from 'utils/dateFormatter'
import DetailsCard from 'components/CardDetailsPage'
import LoadingSpinner from 'components/LoadingSpinner'
import MentorshipPageSkeleton from 'components/skeletons/MentorshipPageSkeleton'
import { getSimpleDuration } from 'components/ModuleCard'

const ModuleDetailsPage = () => {
Expand All @@ -35,7 +35,7 @@ const ModuleDetailsPage = () => {
}
}, [error])

if (isLoading && !data) return <LoadingSpinner />
if (isLoading && !data) return <MentorshipPageSkeleton type="modules" />

if (error) {
return (
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/mentorship/programs/[programKey]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { GetProgramAndModulesDocument } from 'types/__generated__/programsQuerie
import { titleCaseWord } from 'utils/capitalize'
import { formatDate } from 'utils/dateFormatter'
import DetailsCard from 'components/CardDetailsPage'
import LoadingSpinner from 'components/LoadingSpinner'
import MentorshipPageSkeleton from 'components/skeletons/MentorshipPageSkeleton'

const ProgramDetailsPage = () => {
const { programKey } = useParams<{ programKey: string }>()
Expand All @@ -32,7 +32,7 @@ const ProgramDetailsPage = () => {
}
}, [graphQLRequestError])

if (isLoading && !data) return <LoadingSpinner />
if (isLoading && !data) return <MentorshipPageSkeleton type="programs" />

if (graphQLRequestError) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { ExtendedSession } from 'types/auth'
import { titleCaseWord } from 'utils/capitalize'
import { formatDate } from 'utils/dateFormatter'
import DetailsCard from 'components/CardDetailsPage'
import LoadingSpinner from 'components/LoadingSpinner'
import MentorshipPageSkeleton from 'components/skeletons/MentorshipPageSkeleton'

const ProgramDetailsPage = () => {
const { programKey } = useParams<{ programKey: string }>()
Expand Down Expand Up @@ -81,7 +81,7 @@ const ProgramDetailsPage = () => {
}
}

if (isLoading && !data) return <LoadingSpinner />
if (isLoading && !data) return <MentorshipPageSkeleton type="programs" />

if (!program && !isLoading) {
return (
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/components/SkeletonsBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import MemberDetailsPageSkeleton from 'components/skeletons/MemberDetailsPageSke
import OrganizationDetailsPageSkeleton from 'components/skeletons/OrganizationDetailsPageSkeleton'
import SnapshotSkeleton from 'components/skeletons/SnapshotSkeleton'
import UserCardSkeleton from 'components/skeletons/UserCard'
import MentorshipPageSkeleton from 'components/skeletons/MentorshipPageSkeleton'
function userCardRender() {
const cardCount = 12
return (
Expand Down Expand Up @@ -74,6 +75,14 @@ const SkeletonBase = ({
return <MemberDetailsPageSkeleton />
case 'organizations-details':
return <OrganizationDetailsPageSkeleton />
case 'programs':
return <MentorshipPageSkeleton type="programs" />
case 'modules':
return <MentorshipPageSkeleton type="modules" />
case 'issues':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Duplicate case 'issues' makes the mentorship issues skeleton unreachable; indexName === 'issues' will always use the earlier CardSkeleton branch. Rename this case or update the existing issues case to avoid the collision.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/SkeletonsBase.tsx, line 82:

<comment>Duplicate `case 'issues'` makes the mentorship issues skeleton unreachable; `indexName === 'issues'` will always use the earlier CardSkeleton branch. Rename this case or update the existing `issues` case to avoid the collision.</comment>

<file context>
@@ -74,6 +75,14 @@ const SkeletonBase = ({
+      return <MentorshipPageSkeleton type="programs" />
+    case 'modules':
+      return <MentorshipPageSkeleton type="modules" />
+    case 'issues':
+      return <MentorshipPageSkeleton type="issues" />
+    case 'mentees':
</file context>

return <MentorshipPageSkeleton type="issues" />
case 'mentees':
return <MentorshipPageSkeleton type="mentees" />
default:
return <LoadingSpinner imageUrl={loadingImageUrl} />
}
Expand All @@ -84,9 +93,6 @@ const SkeletonBase = ({
) : (
<Component />
)}
<Component />
<Component />
<Component />
</div>
)
}
Expand Down
137 changes: 137 additions & 0 deletions frontend/src/components/skeletons/MentorshipPageSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import React from 'react'
import ProgramCardSkeleton from './ProgramCardSkeleton'
import ModuleCardSkeleton from './ModuleCardSkeleton'

interface MentorshipPageSkeletonProps {
type: 'programs' | 'modules' | 'issues' | 'mentees'
className?: string
}

const MentorshipPageSkeleton: React.FC<MentorshipPageSkeletonProps> = ({
type,
className = ''
}) => {
const renderSkeletonContent = () => {
switch (type) {
case 'programs':
return (
<div className="mt-16 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3">
{Array.from({ length: 6 }, (_, index) => (
<ProgramCardSkeleton key={`program-skeleton-${index}`} />
))}
</div>
)

case 'modules':
return (
<div className="space-y-4">
{Array.from({ length: 8 }, (_, index) => (
<ModuleCardSkeleton key={`module-skeleton-${index}`} />
))}
</div>
)

case 'issues':
return (
<div className="space-y-4">
{Array.from({ length: 10 }, (_, index) => (
<div
key={`issue-skeleton-${index}`}
className="rounded-lg border border-gray-400 bg-white p-4 dark:border-gray-600 dark:bg-gray-800"
role="status"
aria-label="Loading issue card"
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="h-5 w-3/4 rounded bg-gray-300 dark:bg-gray-600 animate-pulse mb-2" />
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse mb-1" />
<div className="h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
<div className="h-6 w-16 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse ml-4" />
</div>
<div className="flex items-center space-x-4">
<div className="h-4 w-20 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-16 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-24 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>
))}
</div>
)

case 'mentees':
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{Array.from({ length: 12 }, (_, index) => (
<div
key={`mentee-skeleton-${index}`}
className="rounded-lg border border-gray-400 bg-white p-4 dark:border-gray-600 dark:bg-gray-800"
role="status"
aria-label="Loading mentee card"
>
<div className="flex items-center space-x-3 mb-3">
<div className="h-10 w-10 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="flex-1">
<div className="h-4 w-32 rounded bg-gray-300 dark:bg-gray-600 animate-pulse mb-1" />
<div className="h-3 w-24 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>
<div className="space-y-2">
<div className="h-3 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-5/6 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-4/5 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
<div className="flex items-center justify-between mt-4">
<div className="h-4 w-16 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-20 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>
))}
</div>
)

default:
return null
}
}

return (
<div className={`text-text flex min-h-screen w-full flex-col items-center justify-normal p-5 ${className}`}>
{/* Search Bar Skeleton */}
<div className="flex w-full items-center justify-center mb-8">
<div className="w-full max-w-2xl">
<div className="h-10 w-full rounded-lg bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Page Title Skeleton */}
<div className="w-full mb-6">
<div className="h-8 w-48 rounded bg-gray-300 dark:bg-gray-600 animate-pulse mb-2" />
<div className="h-4 w-64 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Filter/Sort Skeleton */}
<div className="flex w-full items-center justify-between mb-6">
<div className="h-8 w-32 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-8 w-24 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Main Content Skeleton */}
{renderSkeletonContent()}

{/* Pagination Skeleton */}
<div className="flex w-full items-center justify-center mt-8">
<div className="flex items-center space-x-2">
{Array.from({ length: 5 }, (_, index) => (
<div
key={`pagination-skeleton-${index}`}
className="h-8 w-8 rounded bg-gray-300 dark:bg-gray-600 animate-pulse"
/>
))}
</div>
</div>
</div>
)
}

export default MentorshipPageSkeleton
66 changes: 66 additions & 0 deletions frontend/src/components/skeletons/ModuleCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react'

interface ModuleCardSkeletonProps {
className?: string
}

const ModuleCardSkeleton: React.FC<ModuleCardSkeletonProps> = ({ className = '' }) => {
return (
<div
className={`rounded-lg border border-gray-400 bg-white p-6 text-left text-inherit transition-transform duration-300 hover:scale-[1.02] hover:brightness-105 dark:border-gray-600 dark:bg-gray-800 ${className}`}
role="status"
aria-label="Loading module card"
>
{/* Header Section */}
<div className="mb-4 flex items-center justify-between">
{/* Drag Handle Skeleton */}
<div className="h-6 w-6 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />

{/* Actions Menu Skeleton */}
<div className="h-8 w-8 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Title Section */}
<div className="mb-4">
<div className="h-6 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse mb-2" />
<div className="h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Description Section */}
<div className="mb-4 space-y-2">
<div className="h-3 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-5/6 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-4/5 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Stats Section */}
<div className="mb-4 grid grid-cols-2 gap-4">
<div className="flex items-center space-x-2">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-8 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
<div className="flex items-center space-x-2">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-8 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Progress Section */}
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<div className="h-4 w-16 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-8 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
<div className="h-2 w-full rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Footer Section */}
<div className="flex items-center justify-between">
<div className="h-4 w-20 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-16 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>
)
}

export default ModuleCardSkeleton
82 changes: 82 additions & 0 deletions frontend/src/components/skeletons/ProgramCardSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react'

interface ProgramCardSkeletonProps {
className?: string
}

const ProgramCardSkeleton: React.FC<ProgramCardSkeletonProps> = ({ className = '' }) => {
return (
<div
className={`group block h-72 w-full max-w-sm rounded-lg border border-gray-400 bg-white p-6 text-left text-inherit transition-transform duration-300 hover:scale-[1.02] hover:brightness-105 md:h-80 md:w-80 lg:h-80 lg:w-96 dark:border-gray-600 dark:bg-gray-800 ${className}`}
role="status"
aria-label="Loading program card"
>
{/* Header Section */}
<div className="mb-4 flex items-center justify-between">
{/* Status Badge Skeleton */}
<div className="h-6 w-16 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />

{/* Actions Menu Skeleton */}
<div className="h-8 w-8 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>

{/* Title Section */}
<div className="mb-4">
<div className="mr-1 line-clamp-2 h-12 overflow-hidden break-words text-wrap">
<div className="h-6 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse mb-2" />
<div className="h-6 w-3/4 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Organization and Date Section */}
<div className="mb-4 space-y-2">
<div className="flex items-center space-x-2">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-32 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
<div className="flex items-center space-x-2">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-24 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Description Section */}
<div className="mb-4 flex-1">
<div className="line-clamp-8 break-words overflow-hidden text-wrap space-y-2">
<div className="h-3 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-full rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-5/6 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-3 w-4/5 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Footer Section */}
<div className="flex items-center justify-between">
{/* Mentors/Mentees Count */}
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-1">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-8 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
<div className="flex items-center space-x-1">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-8 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Modules Count */}
<div className="flex items-center space-x-1">
<div className="h-4 w-4 rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
<div className="h-4 w-12 rounded bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>

{/* Progress Bar Skeleton */}
<div className="mt-4">
<div className="h-2 w-full rounded-full bg-gray-300 dark:bg-gray-600 animate-pulse" />
</div>
</div>
)
}

export default ProgramCardSkeleton