From 547ec3c3528a82b2bb39aa33c0bae369f5b1af82 Mon Sep 17 00:00:00 2001
From: AbhayMishra <grabhaymishra@gmail.com>
Date: Sun, 26 Jan 2025 03:13:07 +0530
Subject: [PATCH 1/3] Implemented skeleton from chakra ui

---
 .../__tests__/src/pages/Chapters.test.tsx     | 12 +--
 .../src/pages/CommitteeDetails.test.tsx       |  6 +-
 .../__tests__/src/pages/Committees.test.tsx   | 13 +--
 .../__tests__/src/pages/Contribute.test.tsx   | 91 ++++--------------
 .../__tests__/src/pages/Projects.test.tsx     | 12 +--
 frontend/__tests__/src/pages/Users.test.tsx   | 12 +--
 frontend/jest.setup.ts                        |  2 +-
 frontend/src/components/SearchPageLayout.tsx  |  6 +-
 frontend/src/components/SkeletonsBase.tsx     | 53 +++++++++++
 frontend/src/components/skeletons/Card.tsx    | 92 +++++++++++++++++++
 .../src/components/skeletons/UserCard.tsx     | 42 +++++++++
 frontend/src/components/ui/skeleton.tsx       | 37 ++++++++
 frontend/src/pages/CommitteeDetails.tsx       |  8 +-
 frontend/src/wrappers/testUtil.tsx            |  7 +-
 14 files changed, 283 insertions(+), 110 deletions(-)
 create mode 100644 frontend/src/components/SkeletonsBase.tsx
 create mode 100644 frontend/src/components/skeletons/Card.tsx
 create mode 100644 frontend/src/components/skeletons/UserCard.tsx
 create mode 100644 frontend/src/components/ui/skeleton.tsx

diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx
index 820a4135f..bc0748a1e 100644
--- a/frontend/__tests__/src/pages/Chapters.test.tsx
+++ b/frontend/__tests__/src/pages/Chapters.test.tsx
@@ -35,11 +35,11 @@ describe('ChaptersPage Component', () => {
     jest.clearAllMocks()
   })
 
-  test('renders loading spinner initially', async () => {
+  test('renders skeleton initially', async () => {
     render(<ChaptersPage />)
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
 
@@ -86,9 +86,9 @@ describe('ChaptersPage Component', () => {
     })
     render(<ChaptersPage />)
 
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
+    const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
     })
     await waitFor(() => {
@@ -97,7 +97,7 @@ describe('ChaptersPage Component', () => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
 
-    expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
   })
   test('opens  window on View Details button click', async () => {
     const navigateMock = jest.fn()
diff --git a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx
index 9df0dd72b..4aa13e678 100644
--- a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx
+++ b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx
@@ -35,11 +35,11 @@ describe('Committees Component', () => {
     jest.clearAllMocks()
   })
 
-  test('renders loading spinner initially', async () => {
+  test('renders skeleton initially', async () => {
     render(<CommitteeDetailsPage />)
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
 
diff --git a/frontend/__tests__/src/pages/Committees.test.tsx b/frontend/__tests__/src/pages/Committees.test.tsx
index b7d7ff9d6..a1547a697 100644
--- a/frontend/__tests__/src/pages/Committees.test.tsx
+++ b/frontend/__tests__/src/pages/Committees.test.tsx
@@ -36,11 +36,11 @@ describe('Committees Component', () => {
     jest.clearAllMocks()
   })
 
-  test('renders loading spinner initially', async () => {
+  test('renders skeleton initially', async () => {
     render(<CommitteesPage />)
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
 
@@ -53,11 +53,8 @@ describe('Committees Component', () => {
 
     render(<CommitteesPage />)
 
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
-
-      expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
+      expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
     })
 
     await waitFor(() => {
@@ -65,7 +62,7 @@ describe('Committees Component', () => {
       expect(screen.getByText('Committee 1')).toBeInTheDocument()
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
-    expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
   })
 
   test('renders committee data correctly', async () => {
diff --git a/frontend/__tests__/src/pages/Contribute.test.tsx b/frontend/__tests__/src/pages/Contribute.test.tsx
index 562d82c17..c61f2badc 100644
--- a/frontend/__tests__/src/pages/Contribute.test.tsx
+++ b/frontend/__tests__/src/pages/Contribute.test.tsx
@@ -1,7 +1,6 @@
-import { fireEvent, render, screen, waitFor } from '@testing-library/react'
-
+import { fireEvent, screen, waitFor } from '@testing-library/react'
 import { fetchAlgoliaData } from 'api/fetchAlgoliaData'
-import { MemoryRouter } from 'react-router-dom'
+import { render } from 'wrappers/testUtil'
 
 import ContributePage from 'pages/Contribute'
 
@@ -30,15 +29,11 @@ describe('Contribute Component', () => {
     jest.clearAllMocks()
   })
 
-  test('renders loading spinner initially', async () => {
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
+  test('renders skeleton initially', async () => {
+    render(<ContributePage />)
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
 
@@ -48,11 +43,7 @@ describe('Contribute Component', () => {
       hits: mockContributeData.issues,
       totalPages: 1,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     await waitFor(() => {
       expect(screen.getByText('Contribution 1')).toBeInTheDocument()
@@ -68,11 +59,7 @@ describe('Contribute Component', () => {
       issues: [],
       totalPages: 0,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
     await waitFor(() => {
       expect(screen.getByText('No issues found')).toBeInTheDocument()
     })
@@ -85,11 +72,7 @@ describe('Contribute Component', () => {
       hits: mockContributeData.issues,
       totalPages: 4,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
     await waitFor(() => {
       const nextPageButton = screen.getByText('Next Page')
       fireEvent.click(nextPageButton)
@@ -105,11 +88,7 @@ describe('Contribute Component', () => {
       totalPages: 2,
       currentPage: 1,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
     await waitFor(() => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
@@ -121,11 +100,7 @@ describe('Contribute Component', () => {
       totalPages: 2,
       currentPage: 2,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
     await waitFor(() => {
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
     })
@@ -136,29 +111,21 @@ describe('Contribute Component', () => {
       ...mockContributeData,
       total_pages: 1,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
     await waitFor(() => {
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
     })
   })
 
   test('handles search functionality', async () => {
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     await waitFor(() => {
       const searchInput = screen.getByPlaceholderText('Search for OWASP issues...')
       fireEvent.change(searchInput, { target: { value: '' } })
     })
 
-    expect(fetchAlgoliaData).toHaveBeenCalledWith('issues', '', 1)
+    expect(fetchAlgoliaData).toHaveBeenCalledWith('issues', '', 2)
   })
 
   test('handles error states in card rendering', async () => {
@@ -174,11 +141,7 @@ describe('Contribute Component', () => {
     }
     ;(fetchAlgoliaData as jest.Mock).mockResolvedValue(mockErrorIssue)
 
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     await waitFor(() => {
       expect(screen.queryByText('Read More')).not.toBeInTheDocument()
@@ -191,11 +154,7 @@ describe('Contribute Component', () => {
       hits: mockContributeData.issues,
       totalPages: 1,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     await waitFor(() => {
       const readMoreButton = screen.getByText('Read More')
@@ -209,11 +168,7 @@ describe('Contribute Component', () => {
       hits: mockContributeData.issues,
       totalPages: 1,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     await waitFor(() => {
       const readMoreButton = screen.getByText('Read More')
@@ -232,11 +187,7 @@ describe('Contribute Component', () => {
       hits: mockContributeData.issues,
       totalPages: 1,
     })
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     await waitFor(() => {
       const readMoreButton = screen.getByText('Read More')
@@ -264,11 +215,7 @@ describe('Contribute Component', () => {
     }
     ;(fetchAlgoliaData as jest.Mock).mockResolvedValue(mockMultipleIssues)
 
-    render(
-      <MemoryRouter>
-        <ContributePage />
-      </MemoryRouter>
-    )
+    render(<ContributePage />)
 
     // Wait for both cards to be rendered
     await waitFor(() => {
diff --git a/frontend/__tests__/src/pages/Projects.test.tsx b/frontend/__tests__/src/pages/Projects.test.tsx
index 5f3104341..04ade3567 100644
--- a/frontend/__tests__/src/pages/Projects.test.tsx
+++ b/frontend/__tests__/src/pages/Projects.test.tsx
@@ -35,11 +35,11 @@ describe('ProjectPage Component', () => {
     jest.clearAllMocks()
   })
 
-  test('renders loading spinner initially', async () => {
+  test('renders skeleton initially', async () => {
     render(<ProjectsPage />)
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
 
@@ -52,9 +52,9 @@ describe('ProjectPage Component', () => {
 
     render(<ProjectsPage />)
 
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
+    const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
     })
     await waitFor(() => {
@@ -63,7 +63,7 @@ describe('ProjectPage Component', () => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
 
-    expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
   })
 
   test('renders project data correctly', async () => {
diff --git a/frontend/__tests__/src/pages/Users.test.tsx b/frontend/__tests__/src/pages/Users.test.tsx
index f12617140..4fe5680d5 100644
--- a/frontend/__tests__/src/pages/Users.test.tsx
+++ b/frontend/__tests__/src/pages/Users.test.tsx
@@ -39,11 +39,11 @@ describe('UsersPage Component', () => {
     jest.clearAllMocks()
   })
 
-  test('renders loading spinner initially', async () => {
+  test('renders skeleton initially', async () => {
     render(<UsersPage />)
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
 
@@ -52,9 +52,9 @@ describe('UsersPage Component', () => {
     render(<UsersPage />)
 
     // Check loading state
-    const loadingSpinner = screen.getAllByAltText('Loading indicator')
+    const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
     await waitFor(() => {
-      expect(loadingSpinner.length).toBeGreaterThan(0)
+      expect(skeletonLoaders.length).toBeGreaterThan(0)
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
     })
 
@@ -65,7 +65,7 @@ describe('UsersPage Component', () => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
 
-    expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
   })
 
   test('renders user cards correctly', async () => {
diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts
index 928fcbe07..154ad5abe 100644
--- a/frontend/jest.setup.ts
+++ b/frontend/jest.setup.ts
@@ -7,7 +7,7 @@ dotenv.config()
 
 global.React = React
 global.TextEncoder = TextEncoder
-
+global.structuredClone = (val) => JSON.parse(JSON.stringify(val))
 beforeEach(() => {
   jest.spyOn(console, 'error').mockImplementation((...args) => {
     throw new Error(`Console error: ${args.join(' ')}`)
diff --git a/frontend/src/components/SearchPageLayout.tsx b/frontend/src/components/SearchPageLayout.tsx
index c91f54fdc..ab33ae17c 100644
--- a/frontend/src/components/SearchPageLayout.tsx
+++ b/frontend/src/components/SearchPageLayout.tsx
@@ -1,7 +1,7 @@
 import React, { useState } from 'react'
-import LoadingSpinner from 'components/LoadingSpinner'
 import Pagination from 'components/Pagination'
 import SearchBar from 'components/Search'
+import SkeletonBase from 'components/SkeletonsBase'
 
 interface SearchPageLayoutProps {
   isLoaded: boolean
@@ -53,9 +53,7 @@ const SearchPageLayout = ({
         <div>{sortChildren}</div>
       </div>
       {!isSearchBarReady || !isLoaded ? (
-        <div className="mt-20 flex h-64 w-full items-center justify-center">
-          <LoadingSpinner imageUrl={loadingImageUrl} />
-        </div>
+        <SkeletonBase indexName={indexName} loadingImageUrl={loadingImageUrl} />
       ) : (
         <>
           <div>
diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx
new file mode 100644
index 000000000..12221aa20
--- /dev/null
+++ b/frontend/src/components/SkeletonsBase.tsx
@@ -0,0 +1,53 @@
+import LoadingSpinner from 'components/LoadingSpinner'
+import CardSkeleton from 'components/skeletons/Card'
+import UserCardSkeleton from 'components/skeletons/UserCard'
+import { Skeleton } from 'components/ui/skeleton'
+
+function userCardRender() {
+  const cardCount = 12
+  return (
+    <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
+      {Array.from({ length: cardCount }).map((_, index) => (
+        <UserCardSkeleton key={index} />
+      ))}
+    </div>
+  )
+}
+
+const SkeletonBase = ({ indexName, loadingImageUrl }) => {
+  let Component
+  switch (indexName) {
+    case 'chapters':
+      Component = () => <CardSkeleton showLevel={false} showIcons={1} showLink={false} />
+      break
+    case 'issues':
+      Component = () => (
+        <CardSkeleton showLevel={false} showIcons={2} showContributors={false} showSocial={false} />
+      )
+      break
+    case 'projects':
+      Component = () => <CardSkeleton showLink={false} showSocial={false} />
+      break
+    case 'committees':
+      Component = () => <CardSkeleton showLink={false} showLevel={false} showIcons={1} />
+      break
+    case 'users':
+      return userCardRender()
+    default:
+      return <LoadingSpinner imageUrl={loadingImageUrl} />
+  }
+  return (
+    <div className="flex w-full flex-col items-center justify-center">
+      {indexName == 'chapters' ? (
+        <Skeleton className="mb-2 w-full max-w-6xl" h={400} />
+      ) : (
+        <Component />
+      )}
+      <Component />
+      <Component />
+      <Component />
+    </div>
+  )
+}
+
+export default SkeletonBase
diff --git a/frontend/src/components/skeletons/Card.tsx b/frontend/src/components/skeletons/Card.tsx
new file mode 100644
index 000000000..253cb833b
--- /dev/null
+++ b/frontend/src/components/skeletons/Card.tsx
@@ -0,0 +1,92 @@
+import { Box, Flex } from '@chakra-ui/react'
+import type React from 'react'
+import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/skeleton'
+
+interface CardSkeletonProps {
+  showLevel?: boolean
+  showIcons?: number
+  showProjectName?: boolean
+  showSummary?: boolean
+  showLink?: boolean
+  showContributors?: boolean
+  showSocial?: boolean
+  showActionButton?: boolean
+}
+
+const CardSkeleton: React.FC<CardSkeletonProps> = ({
+  showLevel = true,
+  showIcons = 4,
+  showProjectName = true,
+  showSummary = true,
+  showLink = true,
+  showContributors = true,
+  showSocial = true,
+  showActionButton = true,
+}) => {
+  const NUM_CONTRIBUTORS = 8
+
+  return (
+    <div data-testid="skeleton-loader" className="flex w-full justify-center">
+      <Box className="mb-6 w-full rounded-lg border border-border bg-card p-6 transition-colors duration-300 ease-linear hover:bg-accent/10 md:max-w-6xl">
+        <Flex direction="column" className="flex flex-col sm:flex-row" gap={6}>
+          {/* Header Section */}
+          <Flex className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row">
+            <Flex className="items-center gap-4">
+              {showLevel && <SkeletonCircle className="h-10 w-10" />}
+              <Flex direction="column" gap={2}>
+                {showProjectName && <Skeleton className="h-8 w-[180px] sm:w-[250px]" />}
+              </Flex>
+            </Flex>
+
+            {showIcons && (
+              <Flex className="flex min-w-[30%] flex-grow flex-row items-center justify-end gap-2 overflow-auto">
+                {Array.from({ length: showIcons }).map((_, i) => (
+                  <Skeleton key={i} className="h-8 w-16" />
+                ))}
+                <Skeleton />
+              </Flex>
+            )}
+          </Flex>
+
+          {/* Link Section */}
+          {showLink && <SkeletonText className="w-[180px] md:w-[350px]" noOfLines={1} />}
+
+          {/* Description Section */}
+          {showSummary && <SkeletonText className="space-y-3" noOfLines={4} />}
+
+          {/* Footer Section */}
+          <Flex className="items-center justify-between gap-4 pt-3">
+            <div className="flex flex-col justify-start gap-2">
+              {showContributors && (
+                <Flex className="items-center space-x-2">
+                  {[...Array(NUM_CONTRIBUTORS)].map((_, i) => (
+                    <SkeletonCircle
+                      key={i}
+                      className="h-[30px] w-[30px] border-2 border-background"
+                    />
+                  ))}
+                </Flex>
+              )}
+              {showSocial && (
+                <Flex className="space-x-2">
+                  <SkeletonCircle className="h-5 w-5" />
+                  <SkeletonCircle className="h-5 w-5" />
+                  <SkeletonCircle className="h-5 w-5" />
+                  <SkeletonCircle className="h-5 w-5" />
+                  <SkeletonCircle className="h-5 w-5" />
+                  <SkeletonCircle className="h-5 w-5" />
+                </Flex>
+              )}
+            </div>
+
+            <Flex gap={4} className="ml-auto items-center">
+              {showActionButton && <Skeleton className="h-9 w-[100px]" />}
+            </Flex>
+          </Flex>
+        </Flex>
+      </Box>
+    </div>
+  )
+}
+
+export default CardSkeleton
diff --git a/frontend/src/components/skeletons/UserCard.tsx b/frontend/src/components/skeletons/UserCard.tsx
new file mode 100644
index 000000000..0792a5fa5
--- /dev/null
+++ b/frontend/src/components/skeletons/UserCard.tsx
@@ -0,0 +1,42 @@
+import { Box, Flex } from '@chakra-ui/react'
+import type React from 'react'
+import { Skeleton, SkeletonCircle } from 'components/ui/skeleton'
+
+interface UserCardSkeletonProps {
+  showAvatar?: boolean
+  showName?: boolean
+  showViewProfile?: boolean
+}
+
+const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({
+  showAvatar = true,
+  showName = true,
+  showViewProfile = true,
+}) => {
+  return (
+    <Box
+      data-testid="skeleton-loader"
+      className="group flex h-64 w-80 flex-col items-center rounded-lg bg-white p-6 text-left shadow-lg dark:bg-gray-800 dark:shadow-gray-900/30"
+    >
+      <Flex direction="column" className="w-full items-center space-y-4">
+        {showAvatar && (
+          <Box className="relative h-20 w-20 overflow-hidden rounded-full ring-2 ring-gray-100 dark:ring-gray-700">
+            <SkeletonCircle className="h-full w-full" />
+          </Box>
+        )}
+
+        <Flex direction="column" className="w-full items-center space-y-2">
+          {showName && <Skeleton className="h-7 w-40" />}
+        </Flex>
+      </Flex>
+
+      {showViewProfile && (
+        <Flex className="mt-auto items-center justify-center">
+          <Skeleton className="h-5 w-24" />
+        </Flex>
+      )}
+    </Box>
+  )
+}
+
+export default UserCardSkeleton
diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx
new file mode 100644
index 000000000..76b729ef5
--- /dev/null
+++ b/frontend/src/components/ui/skeleton.tsx
@@ -0,0 +1,37 @@
+import type { SkeletonProps as ChakraSkeletonProps, CircleProps } from '@chakra-ui/react'
+import { Skeleton as ChakraSkeleton, Circle, Stack } from '@chakra-ui/react'
+import * as React from 'react'
+
+export interface SkeletonCircleProps extends ChakraSkeletonProps {
+  size?: CircleProps['size']
+}
+
+export const SkeletonCircle = React.forwardRef<HTMLDivElement, SkeletonCircleProps>(
+  function SkeletonCircle(props, ref) {
+    const { size, ...rest } = props
+    return (
+      <Circle size={size} asChild ref={ref}>
+        <ChakraSkeleton {...rest} />
+      </Circle>
+    )
+  }
+)
+
+export interface SkeletonTextProps extends ChakraSkeletonProps {
+  noOfLines?: number
+}
+
+export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(
+  function SkeletonText(props, ref) {
+    const { noOfLines = 3, gap, ...rest } = props
+    return (
+      <Stack gap={gap} width="full" ref={ref}>
+        {Array.from({ length: noOfLines }).map((_, index) => (
+          <ChakraSkeleton height="4" key={index} {...props} _last={{ maxW: '80%' }} {...rest} />
+        ))}
+      </Stack>
+    )
+  }
+)
+
+export const Skeleton = ChakraSkeleton
diff --git a/frontend/src/pages/CommitteeDetails.tsx b/frontend/src/pages/CommitteeDetails.tsx
index c3eab91c2..b19596b14 100644
--- a/frontend/src/pages/CommitteeDetails.tsx
+++ b/frontend/src/pages/CommitteeDetails.tsx
@@ -5,7 +5,7 @@ import { getFilteredIcons, handleSocialUrls } from 'utils/utility'
 import { ErrorDisplay } from 'wrappers/ErrorWrapper'
 import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper'
 import Card from 'components/Card'
-import LoadingSpinner from 'components/LoadingSpinner'
+import CardSkeleton from 'components/skeletons/Card'
 
 const CommitteeDetailsPage = () => {
   const { committeeKey } = useParams()
@@ -26,8 +26,10 @@ const CommitteeDetailsPage = () => {
   }, [committeeKey])
   if (isLoading)
     return (
-      <div className="flex min-h-[60vh] items-center justify-center">
-        <LoadingSpinner imageUrl="/img/owasp_icon_white_sm.png" />
+      <div className="mt-16 flex w-full flex-col items-center justify-center">
+        <div className="w-full pt-12">
+          <CardSkeleton showLink={false} showLevel={false} showIcons={1} />
+        </div>
       </div>
     )
 
diff --git a/frontend/src/wrappers/testUtil.tsx b/frontend/src/wrappers/testUtil.tsx
index 600789582..32339e784 100644
--- a/frontend/src/wrappers/testUtil.tsx
+++ b/frontend/src/wrappers/testUtil.tsx
@@ -1,9 +1,14 @@
+import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
 import { render } from '@testing-library/react'
 import { ReactNode } from 'react'
 import { BrowserRouter } from 'react-router-dom'
 
 const customRender = (ui: ReactNode) => {
-  return render(<BrowserRouter>{ui}</BrowserRouter>)
+  return render(
+    <BrowserRouter>
+      <ChakraProvider value={defaultSystem}>{ui}</ChakraProvider>
+    </BrowserRouter>
+  )
 }
 
 export * from '@testing-library/react'

From 39174a86220d31715131196f129d5d139f051718 Mon Sep 17 00:00:00 2001
From: AbhayMishra <grabhaymishra@gmail.com>
Date: Sun, 26 Jan 2025 03:38:31 +0530
Subject: [PATCH 2/3] Added a separate type file for skeleton

---
 frontend/src/components/skeletons/Card.tsx     | 12 +-----------
 frontend/src/components/skeletons/UserCard.tsx |  7 +------
 frontend/src/types/skeleton.ts                 | 16 ++++++++++++++++
 3 files changed, 18 insertions(+), 17 deletions(-)
 create mode 100644 frontend/src/types/skeleton.ts

diff --git a/frontend/src/components/skeletons/Card.tsx b/frontend/src/components/skeletons/Card.tsx
index 253cb833b..a8b71ead8 100644
--- a/frontend/src/components/skeletons/Card.tsx
+++ b/frontend/src/components/skeletons/Card.tsx
@@ -1,18 +1,8 @@
 import { Box, Flex } from '@chakra-ui/react'
 import type React from 'react'
+import { CardSkeletonProps } from 'types/skeleton'
 import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/skeleton'
 
-interface CardSkeletonProps {
-  showLevel?: boolean
-  showIcons?: number
-  showProjectName?: boolean
-  showSummary?: boolean
-  showLink?: boolean
-  showContributors?: boolean
-  showSocial?: boolean
-  showActionButton?: boolean
-}
-
 const CardSkeleton: React.FC<CardSkeletonProps> = ({
   showLevel = true,
   showIcons = 4,
diff --git a/frontend/src/components/skeletons/UserCard.tsx b/frontend/src/components/skeletons/UserCard.tsx
index 0792a5fa5..c12a0e336 100644
--- a/frontend/src/components/skeletons/UserCard.tsx
+++ b/frontend/src/components/skeletons/UserCard.tsx
@@ -1,13 +1,8 @@
 import { Box, Flex } from '@chakra-ui/react'
 import type React from 'react'
+import { UserCardSkeletonProps } from 'types/skeleton'
 import { Skeleton, SkeletonCircle } from 'components/ui/skeleton'
 
-interface UserCardSkeletonProps {
-  showAvatar?: boolean
-  showName?: boolean
-  showViewProfile?: boolean
-}
-
 const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({
   showAvatar = true,
   showName = true,
diff --git a/frontend/src/types/skeleton.ts b/frontend/src/types/skeleton.ts
new file mode 100644
index 000000000..f4556d7de
--- /dev/null
+++ b/frontend/src/types/skeleton.ts
@@ -0,0 +1,16 @@
+export interface CardSkeletonProps {
+  showLevel?: boolean
+  showIcons?: number
+  showProjectName?: boolean
+  showSummary?: boolean
+  showLink?: boolean
+  showContributors?: boolean
+  showSocial?: boolean
+  showActionButton?: boolean
+}
+
+export interface UserCardSkeletonProps {
+  showAvatar?: boolean
+  showName?: boolean
+  showViewProfile?: boolean
+}

From 1e4fef1bd6f5849f3647b323c34f818337eacb5f Mon Sep 17 00:00:00 2001
From: AbhayMishra <grabhaymishra@gmail.com>
Date: Mon, 27 Jan 2025 23:20:33 +0530
Subject: [PATCH 3/3] Naming Convention and add testing from role

---
 frontend/__tests__/src/pages/Chapters.test.tsx            | 6 +++---
 frontend/__tests__/src/pages/CommitteeDetails.test.tsx    | 2 +-
 frontend/__tests__/src/pages/Committees.test.tsx          | 6 +++---
 frontend/__tests__/src/pages/Contribute.test.tsx          | 2 +-
 frontend/__tests__/src/pages/Projects.test.tsx            | 6 +++---
 frontend/__tests__/src/pages/Users.test.tsx               | 6 +++---
 frontend/src/components/SkeletonsBase.tsx                 | 2 +-
 frontend/src/components/skeletons/Card.tsx                | 4 ++--
 frontend/src/components/skeletons/UserCard.tsx            | 4 ++--
 frontend/src/components/ui/{skeleton.tsx => Skeleton.tsx} | 0
 10 files changed, 19 insertions(+), 19 deletions(-)
 rename frontend/src/components/ui/{skeleton.tsx => Skeleton.tsx} (100%)

diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx
index bc0748a1e..64189123d 100644
--- a/frontend/__tests__/src/pages/Chapters.test.tsx
+++ b/frontend/__tests__/src/pages/Chapters.test.tsx
@@ -38,7 +38,7 @@ describe('ChaptersPage Component', () => {
   test('renders skeleton initially', async () => {
     render(<ChaptersPage />)
     await waitFor(() => {
-      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      const skeletonLoaders = screen.getAllByRole('status')
       expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
@@ -86,7 +86,7 @@ describe('ChaptersPage Component', () => {
     })
     render(<ChaptersPage />)
 
-    const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+    const skeletonLoaders = screen.getAllByRole('status')
     await waitFor(() => {
       expect(skeletonLoaders.length).toBeGreaterThan(0)
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
@@ -97,7 +97,7 @@ describe('ChaptersPage Component', () => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
 
-    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('status')).not.toBeInTheDocument()
   })
   test('opens  window on View Details button click', async () => {
     const navigateMock = jest.fn()
diff --git a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx
index 4aa13e678..7edf1898d 100644
--- a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx
+++ b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx
@@ -38,7 +38,7 @@ describe('Committees Component', () => {
   test('renders skeleton initially', async () => {
     render(<CommitteeDetailsPage />)
     await waitFor(() => {
-      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      const skeletonLoaders = screen.getAllByRole('status')
       expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
diff --git a/frontend/__tests__/src/pages/Committees.test.tsx b/frontend/__tests__/src/pages/Committees.test.tsx
index a1547a697..0c50adec9 100644
--- a/frontend/__tests__/src/pages/Committees.test.tsx
+++ b/frontend/__tests__/src/pages/Committees.test.tsx
@@ -39,7 +39,7 @@ describe('Committees Component', () => {
   test('renders skeleton initially', async () => {
     render(<CommitteesPage />)
     await waitFor(() => {
-      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      const skeletonLoaders = screen.getAllByRole('status')
       expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
@@ -54,7 +54,7 @@ describe('Committees Component', () => {
     render(<CommitteesPage />)
 
     await waitFor(() => {
-      expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
+      expect(screen.queryByRole('status')).not.toBeInTheDocument()
     })
 
     await waitFor(() => {
@@ -62,7 +62,7 @@ describe('Committees Component', () => {
       expect(screen.getByText('Committee 1')).toBeInTheDocument()
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
-    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
+    expect(screen.queryByRole('status')).not.toBeInTheDocument()
   })
 
   test('renders committee data correctly', async () => {
diff --git a/frontend/__tests__/src/pages/Contribute.test.tsx b/frontend/__tests__/src/pages/Contribute.test.tsx
index c61f2badc..1b5f9efeb 100644
--- a/frontend/__tests__/src/pages/Contribute.test.tsx
+++ b/frontend/__tests__/src/pages/Contribute.test.tsx
@@ -32,7 +32,7 @@ describe('Contribute Component', () => {
   test('renders skeleton initially', async () => {
     render(<ContributePage />)
     await waitFor(() => {
-      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      const skeletonLoaders = screen.getAllByRole('status')
       expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
diff --git a/frontend/__tests__/src/pages/Projects.test.tsx b/frontend/__tests__/src/pages/Projects.test.tsx
index 04ade3567..ca7e0b682 100644
--- a/frontend/__tests__/src/pages/Projects.test.tsx
+++ b/frontend/__tests__/src/pages/Projects.test.tsx
@@ -38,7 +38,7 @@ describe('ProjectPage Component', () => {
   test('renders skeleton initially', async () => {
     render(<ProjectsPage />)
     await waitFor(() => {
-      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      const skeletonLoaders = screen.getAllByRole('status')
       expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
@@ -52,7 +52,7 @@ describe('ProjectPage Component', () => {
 
     render(<ProjectsPage />)
 
-    const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+    const skeletonLoaders = screen.getAllByRole('status')
     await waitFor(() => {
       expect(skeletonLoaders.length).toBeGreaterThan(0)
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
@@ -63,7 +63,7 @@ describe('ProjectPage Component', () => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
 
-    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('status')).not.toBeInTheDocument()
   })
 
   test('renders project data correctly', async () => {
diff --git a/frontend/__tests__/src/pages/Users.test.tsx b/frontend/__tests__/src/pages/Users.test.tsx
index 4fe5680d5..edbdceea8 100644
--- a/frontend/__tests__/src/pages/Users.test.tsx
+++ b/frontend/__tests__/src/pages/Users.test.tsx
@@ -42,7 +42,7 @@ describe('UsersPage Component', () => {
   test('renders skeleton initially', async () => {
     render(<UsersPage />)
     await waitFor(() => {
-      const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+      const skeletonLoaders = screen.getAllByRole('status')
       expect(skeletonLoaders.length).toBeGreaterThan(0)
     })
   })
@@ -52,7 +52,7 @@ describe('UsersPage Component', () => {
     render(<UsersPage />)
 
     // Check loading state
-    const skeletonLoaders = screen.getAllByTestId('skeleton-loader')
+    const skeletonLoaders = screen.getAllByRole('status')
     await waitFor(() => {
       expect(skeletonLoaders.length).toBeGreaterThan(0)
       expect(screen.queryByText('Next Page')).not.toBeInTheDocument()
@@ -65,7 +65,7 @@ describe('UsersPage Component', () => {
       expect(screen.getByText('Next Page')).toBeInTheDocument()
     })
 
-    expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('status')).not.toBeInTheDocument()
   })
 
   test('renders user cards correctly', async () => {
diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx
index 12221aa20..2a0fcd9e7 100644
--- a/frontend/src/components/SkeletonsBase.tsx
+++ b/frontend/src/components/SkeletonsBase.tsx
@@ -1,7 +1,7 @@
 import LoadingSpinner from 'components/LoadingSpinner'
 import CardSkeleton from 'components/skeletons/Card'
 import UserCardSkeleton from 'components/skeletons/UserCard'
-import { Skeleton } from 'components/ui/skeleton'
+import { Skeleton } from 'components/ui/Skeleton'
 
 function userCardRender() {
   const cardCount = 12
diff --git a/frontend/src/components/skeletons/Card.tsx b/frontend/src/components/skeletons/Card.tsx
index a8b71ead8..59b175df4 100644
--- a/frontend/src/components/skeletons/Card.tsx
+++ b/frontend/src/components/skeletons/Card.tsx
@@ -1,7 +1,7 @@
 import { Box, Flex } from '@chakra-ui/react'
 import type React from 'react'
 import { CardSkeletonProps } from 'types/skeleton'
-import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/skeleton'
+import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/Skeleton'
 
 const CardSkeleton: React.FC<CardSkeletonProps> = ({
   showLevel = true,
@@ -16,7 +16,7 @@ const CardSkeleton: React.FC<CardSkeletonProps> = ({
   const NUM_CONTRIBUTORS = 8
 
   return (
-    <div data-testid="skeleton-loader" className="flex w-full justify-center">
+    <div role="status" className="flex w-full justify-center">
       <Box className="mb-6 w-full rounded-lg border border-border bg-card p-6 transition-colors duration-300 ease-linear hover:bg-accent/10 md:max-w-6xl">
         <Flex direction="column" className="flex flex-col sm:flex-row" gap={6}>
           {/* Header Section */}
diff --git a/frontend/src/components/skeletons/UserCard.tsx b/frontend/src/components/skeletons/UserCard.tsx
index c12a0e336..fe9ef4f2c 100644
--- a/frontend/src/components/skeletons/UserCard.tsx
+++ b/frontend/src/components/skeletons/UserCard.tsx
@@ -1,7 +1,7 @@
 import { Box, Flex } from '@chakra-ui/react'
 import type React from 'react'
 import { UserCardSkeletonProps } from 'types/skeleton'
-import { Skeleton, SkeletonCircle } from 'components/ui/skeleton'
+import { Skeleton, SkeletonCircle } from 'components/ui/Skeleton'
 
 const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({
   showAvatar = true,
@@ -10,7 +10,7 @@ const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({
 }) => {
   return (
     <Box
-      data-testid="skeleton-loader"
+      role="status"
       className="group flex h-64 w-80 flex-col items-center rounded-lg bg-white p-6 text-left shadow-lg dark:bg-gray-800 dark:shadow-gray-900/30"
     >
       <Flex direction="column" className="w-full items-center space-y-4">
diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/Skeleton.tsx
similarity index 100%
rename from frontend/src/components/ui/skeleton.tsx
rename to frontend/src/components/ui/Skeleton.tsx