Skip to content
Closed
Changes from 9 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
199 changes: 111 additions & 88 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,24 @@ const Card = ({
social,
tooltipLabel,
}: CardProps) => {
const [isMobile, setIsMobile] = useState(isMobileInitial)
const hasSocial = social && social.length > 0
const hasContributors = topContributors && topContributors.length > 0

// 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)
}, [])
// Helper function to safely handle icons
const safeIcon = (icon: any) => {
if (typeof icon === 'string') return icon;
// For test environment, return a fallback icon name
return 'circle';
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix TypeScript any type warning.

The safeIcon helper function is a good safety check, but it's using an explicit any type which triggered a linter warning.

- const safeIcon = (icon: any) => {
+ const safeIcon = (icon: string | object) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Helper function to safely handle icons
const safeIcon = (icon: any) => {
if (typeof icon === 'string') return icon;
// For test environment, return a fallback icon name
return 'circle';
};
// Helper function to safely handle icons
const safeIcon = (icon: string | object) => {
if (typeof icon === 'string') return icon;
// For test environment, return a fallback icon name
return 'circle';
};
🧰 Tools
🪛 GitHub Actions: Run CI/CD

[warning] 32-32: Unexpected any. Specify a different type @typescript-eslint/no-explicit-any


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
id="level-tooltip"
Expand All @@ -58,29 +54,43 @@ 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" />
{/* Use safeIcon to handle potential object icons */}
<FontAwesomeIconWrapper icon={safeIcon(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 @@ -91,81 +101,94 @@ const Card = ({
) : null
)}
</div>
) : null}
</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}
/>
))}
)}

{/* 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>
{!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}`}
contributor={contributor}
/>
))}
</div>
</div>
</div>
Comment on lines +119 to +135

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract contributor avatar section to avoid duplication.

There's duplicate code for rendering contributors in both branches of the conditional rendering. This violates the DRY principle and could be extracted to a reusable component or function.

Consider creating a separate ContributorsSection component:

const ContributorsSection = ({ contributors }) => (
  <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">
      {contributors.map((contributor, index) => (
        <ContributorAvatar
          key={contributor.login || `contributor-${index}`}
          contributor={contributor}
        />
      ))}
    </div>
  </div>
);

Then use it in both places:

            {/* 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}`}
-                        contributor={contributor}
-                      />
-                    ))}
-                  </div>
-                </div>
+                <ContributorsSection contributors={topContributors} />
              </div>
            )}

            // ...

            {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}`}
-                      contributor={contributor}
-                    />
-                  ))}
-                </div>
-              </div>
+              <ContributorsSection contributors={topContributors} />
            ) : (

Also applies to: 167-180

)}
>
<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}
{/* Handle button icon safely */}
{typeof button.icon === 'string' ? <FontAwesomeIconWrapper icon={button.icon} /> : 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}`}
contributor={contributor}
/>
))}
</div>
</div>
) : (
<div></div>
)}
<div className="flex-shrink-0 self-end">
<ActionButton tooltipLabel={tooltipLabel} url={button.url} onClick={button.onclick}>
{/* Handle button icon safely */}
{typeof button.icon === 'string' ? <FontAwesomeIconWrapper icon={button.icon} /> : button.icon}
{button.label}
</ActionButton>
</div>
</div>
)}
</div>
</div>
Expand Down