-
-
Notifications
You must be signed in to change notification settings - Fork 529
feat: Replace loading spinners with skeleton UI in mentorship portal #3814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
fasiho7
wants to merge
1
commit into
OWASP:main
from
fasiho7:feat/mentorship-skeleton-ui-replacement
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
frontend/src/components/skeletons/MentorshipPageSkeleton.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 existingissuescase to avoid the collision.Prompt for AI agents