Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1e8e1a7
card component enhanced
yashgoyal0110 Mar 2, 2025
6a15a66
card component enhanced
yashgoyal0110 Mar 2, 2025
b1b11fd
card component enhanced
yashgoyal0110 Mar 2, 2025
e1d9912
card component enhanced
yashgoyal0110 Mar 2, 2025
101a65d
card component enhanced
yashgoyal0110 Mar 2, 2025
63fe7aa
commit
yashgoyal0110 Mar 2, 2025
fcee7c1
commit
yashgoyal0110 Mar 2, 2025
a236501
commit
yashgoyal0110 Mar 2, 2025
d2472b3
commit
yashgoyal0110 Mar 2, 2025
735ae39
Merge branch 'OWASP:main' into feat/card-component-enhanced
yashgoyal0110 Mar 3, 2025
9b98402
commit
yashgoyal0110 Mar 3, 2025
d6d47c1
Merge branch 'main' into feat/card-component-enhanced
yashgoyal0110 Mar 3, 2025
5e51ee9
Merge branch 'OWASP:main' into feat/card-component-enhanced
yashgoyal0110 Mar 4, 2025
30db1ca
commit
yashgoyal0110 Mar 4, 2025
7e3e27e
commit
yashgoyal0110 Mar 4, 2025
8c9f2a1
commit
yashgoyal0110 Mar 4, 2025
f19d93d
commit
yashgoyal0110 Mar 4, 2025
07b68ca
commit
yashgoyal0110 Mar 4, 2025
677f1da
commit
yashgoyal0110 Mar 4, 2025
6b3187d
commit
yashgoyal0110 Mar 4, 2025
02cf0df
Merge branch 'main' of https://github.com/yashgoyal0110/Nest into fea…
yashgoyal0110 Mar 6, 2025
77d2cd0
Merge branch 'main' into feat/card-component-enhanced
yashgoyal0110 Mar 6, 2025
f21c6f0
Merge branch 'feat/card-component-enhanced' of https://github.com/yas…
yashgoyal0110 Mar 6, 2025
673c359
commit
yashgoyal0110 Mar 6, 2025
93e2b57
commit
yashgoyal0110 Mar 6, 2025
3a37370
commit
yashgoyal0110 Mar 6, 2025
d0d4023
commit
yashgoyal0110 Mar 6, 2025
df24d38
commit
yashgoyal0110 Mar 6, 2025
1fd224a
Merge branch 'main' into feat/card-component-enhanced
yashgoyal0110 Mar 8, 2025
3493b43
commit
yashgoyal0110 Mar 8, 2025
8e72351
Merge branch 'main' into feat/card-component-enhanced
yashgoyal0110 Mar 10, 2025
1018a2b
ommit
yashgoyal0110 Mar 10, 2025
801b11b
Merge branch 'OWASP:main' into feat/card-component-enhanced
yashgoyal0110 Mar 15, 2025
57b7272
Update Contribute.test.tsx
yashgoyal0110 Mar 15, 2025
88b7f2c
commit
yashgoyal0110 Mar 15, 2025
27b1476
commit
yashgoyal0110 Mar 15, 2025
33a6c04
Merge branch 'main' into feat/card-component-enhanced
yashgoyal0110 Mar 15, 2025
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
25 changes: 9 additions & 16 deletions frontend/__tests__/unit/pages/Contribute.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ describe('Contribute Component', () => {
totalPages: 4,
})
render(<ContributePage />)
await waitFor(() => {
const nextPageButton = screen.getByText('Next Page')
fireEvent.click(nextPageButton)
})
const nextPageButton = await screen.findByText('Next Page')
expect(nextPageButton).toBeInTheDocument()
fireEvent.click(nextPageButton)
expect(window.scrollTo).toHaveBeenCalledWith({
top: 0,
behavior: 'auto',
Expand Down Expand Up @@ -155,10 +154,8 @@ describe('Contribute Component', () => {
})
render(<ContributePage />)

await waitFor(() => {
const readMoreButton = screen.getByText('Read More')
expect(readMoreButton).toBeInTheDocument()
})
const submitButton = await screen.findByRole('button', { name: /submit/i })
expect(submitButton).toBeInTheDocument()
})

test('opens modal when SubmitButton is clicked', async () => {
Expand All @@ -169,10 +166,8 @@ describe('Contribute Component', () => {
})
render(<ContributePage />)

await waitFor(() => {
const readMoreButton = screen.getByText('Read More')
fireEvent.click(readMoreButton)
})
const readMoreButton = await screen.findByText('Read More')
fireEvent.click(readMoreButton)

await waitFor(() => {
const modalTitle = screen.getByText('Close')
Expand All @@ -188,10 +183,8 @@ describe('Contribute Component', () => {
})
render(<ContributePage />)

await waitFor(() => {
const readMoreButton = screen.getByText('Read More')
fireEvent.click(readMoreButton)
})
const readMoreButton = await screen.findByText('Read More')
fireEvent.click(readMoreButton)

await waitFor(() => {
const closeButton = screen.getByText('Close')
Expand Down
184 changes: 103 additions & 81 deletions frontend/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { HStack, Link } from '@chakra-ui/react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useState, useEffect } from 'react'
import { CardProps } from 'types/card'
import { desktopViewMinWidth } from 'utils/constants'
import { Icons } from 'utils/data'
import { TooltipRecipe } from 'utils/theme'
import { getSocialIcon } from 'utils/urlIconMappings'
Expand All @@ -14,9 +12,6 @@ import DisplayIcon from 'components/DisplayIcon'
import Markdown from 'components/MarkdownWrapper'
import { Tooltip } from 'components/ui/tooltip'

// Initial check for mobile screen size
const isMobileInitial = typeof window !== 'undefined' && window.innerWidth < desktopViewMinWidth

const Card = ({
title,
url,
Expand All @@ -30,23 +25,17 @@ const Card = ({
social,
tooltipLabel,
}: CardProps) => {
const [isMobile, setIsMobile] = useState(isMobileInitial)

// Resize listener to adjust display based on screen width
useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth < desktopViewMinWidth
setIsMobile(mobile)
}
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
const hasSocial = social && social.length > 0
const hasContributors = topContributors && topContributors.length > 0

return (
<div className="mb-2 mt-4 flex w-full flex-col items-start rounded-md border border-border bg-white pb-4 pl-4 dark:bg-[#212529] md:max-w-6xl">
<div className="mt-2 flex w-full flex-col items-start gap-4 pt-2 sm:flex-col sm:gap-4 md:pt-0">
<div className="flex items-center gap-3">
{/* Display project level badge (if available) */}
<div
className={cn(
'relative mb-4 mt-6 flex w-full flex-col items-start rounded-lg border border-border bg-white p-2 shadow-sm transition-all duration-300 dark:bg-slate-800 sm:p-3 md:max-w-6xl'
)}
>
<div className="flex w-full flex-col items-start gap-1.5">
<div className="flex w-full items-center gap-2">
{level && (
<Tooltip
closeDelay={100}
Expand All @@ -59,29 +48,42 @@ const Card = ({
>
<span
className={cn(
'flex h-8 w-8 items-center justify-center rounded-full text-xs shadow'
'flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full text-xs shadow-md ring-2 ring-white dark:ring-slate-700 sm:text-sm'
)}
style={{ backgroundColor: level.color }}
>
<FontAwesomeIconWrapper icon={level.icon} className="text-white" />
</span>
</Tooltip>
)}
{/* Project title and link */}
<Link href={url} target="_blank" rel="noopener noreferrer" className="flex-1">
<h1
className="max-w-full break-words text-base font-semibold dark:text-sky-600 sm:break-normal sm:text-lg lg:text-2xl"
style={{
transition: 'color 0.3s ease',
}}
>

{/* Project Title */}
<Link
href={url}
target="_blank"
rel="noopener noreferrer"
className="group w-full flex-1 no-underline"
>
<h1 className="max-w-full text-lg font-bold text-gray-800 transition-colors duration-300 group-hover:text-blue-600 dark:text-white dark:group-hover:text-blue-400 sm:text-xl md:text-2xl">
{title}
</h1>
</Link>
</div>
{/* Icons associated with the project */}
{icons && Object.keys(Icons).some((key) => icons[key]) ? (
<div className="-ml-1.5 flex flex-grow">

{projectName && (
<Link
href={projectLink}
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 sm:text-sm"
>
<FontAwesomeIconWrapper icon="link" className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
{projectName}
</Link>
)}

{/* Technology Icons*/}
{icons && Object.keys(Icons).some((key) => icons[key]) && (
<div className="-ml-1 mt-1 flex flex-wrap overflow-x-auto py-1 sm:-ml-1.5">
{Object.keys(Icons).map((key, index) =>
icons[key] ? (
<DisplayIcon
Expand All @@ -92,82 +94,102 @@ const Card = ({
) : null
)}
</div>
) : null}
)}

{/* Summary section */}
<div className={cn('my-1 w-full rounded-md bg-gray-50 p-1 dark:bg-slate-700/30 sm:p-3')}>
<Markdown
content={summary}
className="prose prose-xs sm:prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-200"
/>
</div>
</div>
{/* Link to project name if provided */}
{projectName && (
<Link href={projectLink} rel="noopener noreferrer" className="mt-2 font-medium">
{projectName}
</Link>
)}
{/* Render project summary using Markdown */}
<Markdown content={summary} className="py-2 pr-4 text-gray-600 dark:text-gray-300" />

<div
className={
social && social.length > 0
? 'flex w-full flex-col gap-2 pr-4'
: 'flex w-full items-center justify-between'
}
>
{/* Render top contributors as avatars */}
<div className="mt-3 flex w-full flex-wrap items-center gap-2">
{topContributors?.map((contributor, index) => (
<ContributorAvatar
key={contributor.login || `contributor-${index}`}
contributor={contributor}
uniqueKey={index.toString()}
/>
))}
</div>
{!social || social.length === 0 ? (
<div
className={cn(
'mt-3 flex items-center pr-4',
isMobile && 'mt-4 w-full justify-end pr-4'
)}
>
<ActionButton tooltipLabel={tooltipLabel} url={button.url} onClick={button.onclick}>
{button.icon}
{button.label}
</ActionButton>
</div>
) : (
<div
className={cn(
'flex w-full flex-wrap items-center justify-between gap-6',
isMobile && 'items-start'
{/* Footer section */}
{hasSocial ? (
<div className="mt-2 w-full">
{/* First row: Contributors only when social links are present */}
{hasContributors && (
<div className="mb-2">
<div className="flex flex-col gap-1">
<span className="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Contributors
</span>
<div className="flex flex-wrap items-center gap-1">
{topContributors.map((contributor, index) => (
<ContributorAvatar
key={contributor.login || `contributor-${index}`}
uniqueKey={contributor.login || `contributor-${index}`}
contributor={contributor}
/>
))}
</div>
</div>
</div>
)}
>
<div
className={cn('flex w-full items-center justify-between', isMobile && 'flex-wrap')}
>
{/* Render social links if available */}
{social && social.length > 0 && (
<HStack id="social" mt={2}>

{/* Second row: Social links and button on the same line */}
<div className="flex w-full items-end justify-between">
<div className="flex flex-col gap-1">
<span className="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Connect
</span>
<HStack className="flex-wrap">
{social.map((item) => (
<Link
key={`${item.title}-${item.url}`}
href={item.url}
target="_blank"
rel="noopener noreferrer"
display="flex"
alignItems="center"
gap={2}
className="flex h-7 w-7 items-center justify-center rounded-full bg-gray-100 text-gray-600 transition-colors hover:bg-blue-100 hover:text-blue-600 dark:bg-slate-700 dark:text-gray-300 dark:hover:bg-blue-900 dark:hover:text-blue-300 sm:h-8 sm:w-8"
>
<FontAwesomeIcon icon={getSocialIcon(item.url)} className="h-5 w-5" />
</Link>
))}
</HStack>
)}
{/* Action Button */}
<div className="flex items-center">
</div>
<div className="flex-shrink-0 self-end">
<ActionButton tooltipLabel={tooltipLabel} url={button.url} onClick={button.onclick}>
{button.icon}
{button.label}
</ActionButton>
</div>
</div>
</div>
) : (
<div className="mt-2 flex w-full items-end justify-between">
{hasContributors ? (
<div className="flex flex-col gap-1">
<span className="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Contributors
</span>
<div className="flex flex-wrap items-center gap-1">
{topContributors.map((contributor, index) => (
<ContributorAvatar
key={contributor.login || `contributor-${index}`}
uniqueKey={contributor.login || `contributor-${index}`}
contributor={contributor}
/>
))}
</div>
</div>
) : (
<div></div>
)}
<div className="flex-shrink-0 self-end">
<ActionButton tooltipLabel={tooltipLabel} url={button.url} onClick={button.onclick}>
{button.icon}
{button.label}
</ActionButton>
</div>
</div>
)}
</div>
</div>
Expand Down
Loading