Skip to content

chore: enable strict mode and refactor types#3652

Closed
hussainjamal760 wants to merge 2 commits intoOWASP:mainfrom
hussainjamal760:chore/strict-mode-v2
Closed

chore: enable strict mode and refactor types#3652
hussainjamal760 wants to merge 2 commits intoOWASP:mainfrom
hussainjamal760:chore/strict-mode-v2

Conversation

@hussainjamal760
Copy link
Contributor

Proposed change
Resolves #3407

This PR addresses extensive TypeScript strict mode errors and logic issues across the frontend codebase to ensure a clean build and improved type safety.

Key Changes:

Strict Mode fixes: Resolved nullability, implicit any, and type mismatch errors .
Bug Fixes:
Fixed duplicate keys and invalid URL generation in CommitteesPage when data is missing.
Fixed a typo (incubation -> incubator) in SnapshotDetailsPage.
Fixed syntax errors and malformed provider configuration in src/app/api/auth/[...nextauth]/route.ts.
Fixed unsafe type casts in ModuleDetailsPage and RepositoryDetailsPage by aligning generated types and using safer defaults.
Infrastructure:
Updated apolloClient to throw a proper error instead of failing silently when GRAPHQL_URL is missing.
Manually patched generated GraphQL types to include missing fields (url in IssueNode) required by UI components.
Checklist
Required: I followed the contributing workflow
Required: I verified that my code works as intended and resolves the issue as described
Required: I ran make check-test locally: all warnings addressed, tests passed

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved error resilience by adding null safety checks and fallback values throughout the application, preventing runtime crashes when data is missing or incomplete.
    • Enhanced metadata generation to provide proper fallback titles for missing pages.
    • Strengthened OAuth credential validation with runtime checks.
  • Chores

    • Enhanced type safety and null-handling across the codebase for more robust data processing.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This PR enhances TypeScript strict mode compliance across the frontend by adding explicit type guards, null safety checks, and defensive fallbacks throughout components and types. The changes prevent potential runtime errors from null/undefined values through optional chaining, nullish coalescing, explicit type narrowing, and property widening to accommodate nullable values.

Changes

Cohort / File(s) Summary
Auth & API Routes
frontend/src/app/api/auth/[...nextauth]/route.ts, frontend/src/utils/helpers/apolloClient.ts
Added runtime validation for GitHub OAuth credentials, safer JWT/session mutations with optional chaining, and replaced silent failures with explicit error throws in Apollo client construction.
Layout Metadata Generation
frontend/src/app/chapters/[chapterKey]/layout.tsx, frontend/src/app/committees/[committeeKey]/layout.tsx, frontend/src/app/community/snapshots/[id]/layout.tsx, frontend/src/app/members/[memberKey]/layout.tsx, frontend/src/app/organizations/[organizationKey]/layout.tsx
Updated generateMetadata to return concrete Metadata objects with fallback titles instead of null when data is missing, ensuring consistent metadata generation.
Chapter Pages
frontend/src/app/chapters/[chapterKey]/page.tsx, frontend/src/app/chapters/page.tsx
Added fallback values for suggestedLocation and relatedUrls; defensively handled missing chapter data with optional chaining and empty array defaults.
Committee Pages
frontend/src/app/committees/page.tsx
Enhanced renderCommitteeCard with URL safety validation, unique key computation, warning logs for missing data, and conditional navigation based on data presence.
Organization Pages
frontend/src/app/organizations/[organizationKey]/page.tsx, frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx
Added location fallback, mapped nested fields to ensure author/organization are defined; introduced GetRepositoryDataQuery type for stronger type safety on repository state.
Project Pages
frontend/src/app/projects/[projectKey]/page.tsx, frontend/src/app/projects/dashboard/metrics/[projectKey]/page.tsx
Transformed pullRequests, recentIssues, recentReleases to guarantee author field presence; added metric defaults (0 fallbacks) for potentially undefined chart data; mapped repositories to ensure organization field is defined.
About & Leadership
frontend/src/app/about/page.tsx
Replaced filter(Boolean) with type-guarded filter narrowing user type; added explicit key cast for leaders access.
Board & Candidates
frontend/src/app/board/[year]/candidates/page.tsx
Widened Candidate type to allow null; made firstOwaspContributionAt nullable; added optional chaining for LinkedIn URL construction.
Community Snapshots
frontend/src/app/community/snapshots/[id]/page.tsx
Added fallback defaults for project/chapter cardKey, level, summary, and relatedUrls; safely defaults missing values to empty strings or arrays.
Contribution Pages
frontend/src/app/contribute/page.tsx
Updated Fragment/Card/DialogComp keys to use issue.objectID || index; added summary and hint fallbacks to empty strings.
Home & Layout
frontend/src/app/page.tsx, frontend/src/app/layout.tsx
Added optional chaining with nullish defaults for statsOverview and upcomingEvents; set gaId fallback to empty string.
Mentorship Program Pages
frontend/src/app/my/mentorship/page.tsx, frontend/src/app/my/mentorship/programs/create/page.tsx, frontend/src/app/my/mentorship/programs/[programKey]/page.tsx, frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx, frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx
Added explicit form data typing, userRole normalization, safer error messaging; introduced optional chaining and fallback defaults for program fields; typed admins/domains/mentors/tags for DetailsCard props.
Mentorship Mentees
frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx
Enhanced menteeDetails mapping to normalize undefined domains/tags; replaced array length checks with nullish coalescing guards; imported and typed Contributor array.
Module Pages
frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx
Added Contributor type import and explicit type casting for admins/mentors; mapped pullRequests to ensure author is defined; typed arrays for domains and tags.
Members Pages
frontend/src/app/members/[memberKey]/page.tsx
Mapped topRepositories to ensure organization field is defined or undefined; improved repository data normalization.
Card Component
frontend/src/components/Card.tsx
Conditionally renders title as a link only when url is provided; otherwise renders as gray non-link text while maintaining typographic structure.
CardDetailsPage Component
frontend/src/components/CardDetailsPage.tsx
Added default empty values for all DetailsCard props (arrays, strings); typed health metrics score fallback to 0; typed SocialLinks export explicitly; added fallback for milestone createdAt formatting.
ItemCardList Component
frontend/src/components/ItemCardList.tsx
Widened renderDetails item type to make all fields optional/nullable; author can now be null; avatar fallback to empty string with objectFit style.
HealthMetrics Component
frontend/src/components/HealthMetrics.tsx, frontend/src/types/healthMetrics.ts
Added nullish coalescing defaults (0) for count metrics; createdAt defaults to Date.now(); widened fill and meta types to any; score now allows null.
UserCard Component
frontend/src/components/UserCard.tsx
Added nullish coalescing for followersCount, repositoriesCount, badgeCount; updated conditionals to check both truthiness and > 0.
Type Definitions
frontend/src/types/chapter.ts, frontend/src/types/milestone.ts, frontend/src/types/project.ts, frontend/src/types/release.ts, frontend/src/types/healthMetrics.ts, frontend/src/types/declarations.d.ts
Broadened optional fields to allow explicit null values; added ambient type declarations for isomorphic-dompurify, lodash, and jest-axe; updated Jest global augmentation for accessibility testing.
GraphQL Queries
frontend/src/server/queries/repositoryQueries.ts
Added url field to issues sub-selection in GetRepositoryData query.
Utility Helpers
frontend/src/utils/helpers/githubHeatmap.ts, frontend/src/utils/metaconfig.ts, frontend/src/utils/structuredData.ts
Added explicit type annotations to arrow function callbacks; constrained getStaticMetadata pageKey to keyof typeof METADATA_CONFIG; updated formatISODate return type to allow undefined; added follower count and sameAs field fallbacks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

  • Created Module and program pages #1717 — Modifies the same JWT/session handling logic in the NextAuth route to safely mutate user properties and improve null-safety in session callbacks.
  • entity leader component added #1143 — Modifies CardDetailsPage component to add conditional rendering for Leaders and adjust component props, overlapping with defensive prop defaults added in this PR.
  • frontend for snapshotDetailsPage #981 — Implements community snapshot pages with metadata and type handling that aligns with the null-safety improvements made to snapshot layout pages in this PR.

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: enable strict mode and refactor types' directly and clearly summarizes the main objective of the PR: enabling TypeScript strict mode and refactoring types to fix related errors.
Description check ✅ Passed The description is well-related to the changeset, clearly explaining the issue addressed (#3407), key changes made, and the checklist of completed items.
Linked Issues check ✅ Passed The PR successfully addresses issue #3407's core objective: enabling strict mode and fixing TypeScript strict-mode compilation errors across frontend files with improved type safety and null/undefined handling.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with strict mode compliance: type narrowing, null coalescing, optional chaining, and safer casting. No unrelated features or refactoring outside the strict mode objective scope are introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
frontend/src/app/community/snapshots/[id]/page.tsx (2)

38-62: Guard routing when project.key is missing.
Line 43 and Line 61 still build URLs from project.key even though cardKey now defaults to ''. If the key is absent, the button navigates to an invalid route. Gate navigation and URL creation on a truthy key.

Proposed fix
 const renderProjectCard = (project: Project) => {
+  const projectKey = project.key ?? ''
   const params: string[] = ['forksCount', 'starsCount', 'contributorsCount']
   const filteredIcons = getFilteredIcons(project, params)

   const handleButtonClick = () => {
-    router.push(`/projects/${project.key}`)
+    if (!projectKey) return
+    router.push(`/projects/${projectKey}`)
   }

   const submitButton = {
     label: 'View Details',
     icon: <FaRightToBracket className="h-4 w-4" />,
     onclick: handleButtonClick,
   }

   return (
     <Card
       button={submitButton}
-      cardKey={project.key || ''}
+      cardKey={projectKey}
       icons={filteredIcons}
       level={level[(project.level || 'incubator').toLowerCase() as keyof typeof level] ?? level.incubator}
       summary={project.summary || ''}
       title={project.name}
       topContributors={project.topContributors}
-      url={`/projects/${project.key}`}
+      url={projectKey ? `/projects/${projectKey}` : ''}
     />
   )
 }

66-90: Avoid invalid chapter links when chapter.key is empty.
Line 72 and Line 89 still assume chapter.key exists while cardKey now defaults to ''. If the key is missing, the button routes to a broken URL. Guard navigation/URL similarly to the project card.

Proposed fix
 const renderChapterCard = (chapter: Chapter) => {
+  const chapterKey = chapter.key ?? ''
   const params: string[] = ['updatedAt']
   const filteredIcons = getFilteredIcons(chapter, params)
   const formattedUrls = handleSocialUrls(chapter.relatedUrls || [])

   const handleButtonClick = () => {
-    router.push(`/chapters/${chapter.key}`)
+    if (!chapterKey) return
+    router.push(`/chapters/${chapterKey}`)
   }

   const submitButton = {
     label: 'View Details',
     icon: <FaRightToBracket className="h-4 w-4" />,
     onclick: handleButtonClick,
   }

   return (
     <Card
       button={submitButton}
-      cardKey={chapter.key || ''}
+      cardKey={chapterKey}
       icons={filteredIcons}
       social={formattedUrls}
       summary={chapter.summary || ''}
       title={chapter.name}
-      url={`/chapters/${chapter.key}`}
+      url={chapterKey ? `/chapters/${chapterKey}` : ''}
     />
   )
 }
frontend/src/components/HealthMetrics.tsx (1)

9-15: Consider the Date.now() fallback semantics.

While the nullish coalescing for numeric values is appropriate, using Date.now() as a fallback for missing createdAt may produce misleading chart labels — a metric with no timestamp would appear as "today."

Given the parent component validates that data exists before rendering, this edge case is unlikely to occur in practice. If it's acceptable to show a "N/A" or skip items without timestamps, that would be more semantically accurate.

💡 Alternative: filter or label fallback
   const labels = data.map((item) => {
-    return new Date(item.createdAt ?? Date.now()).toLocaleDateString('en-US', {
+    if (!item.createdAt) return 'N/A'
+    return new Date(item.createdAt).toLocaleDateString('en-US', {
       month: 'short',
       day: 'numeric',
     })
   })
frontend/src/components/ItemCardList.tsx (1)

54-79: Guard avatar rendering to avoid invalid next/image src and malformed URLs.

When author is missing or incomplete, the current code passes src="" to next/image (line 70), which is invalid and can error. Additionally, href={/members/${item?.author?.login}} (line 65) generates /members/undefined when author is null or login is missing.

Render the avatar block only when both login and avatarUrl are present:

Suggested fix
-                {showAvatar && (
+                {showAvatar && item?.author?.login && item?.author?.avatarUrl && (
                   <Tooltip
                     closeDelay={100}
-                    content={item?.author?.name || item?.author?.login}
+                    content={item.author.name || item.author.login}
                     id={`avatar-tooltip-${index}`}
                     delay={100}
                     placement="bottom"
                     showArrow
                   >
                     <Link
                       className="shrink-0 text-blue-400 hover:underline"
-                      href={`/members/${item?.author?.login}`}
+                      href={`/members/${item.author.login}`}
                     >
                       <Image
                         height={24}
                         width={24}
-                        src={item?.author?.avatarUrl || ''}
+                        src={item.author.avatarUrl}
                         alt={
                           item.author && (item.author.name || item.author.login)
                             ? `${item.author.name || item.author.login}'s avatar`
                             : "Author's avatar"
                         }
                         className="mr-2 rounded-full"
                         style={{ objectFit: 'cover' }}
                       />
                     </Link>
                   </Tooltip>
                 )}
🤖 Fix all issues with AI agents
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx:
- Around line 53-62: The menteeIssuesData state is never updated after the API
response, so the issues list/pagination remains empty; after you
setMenteeDetails from data.getMenteeDetails, also set the menteeIssuesData state
from data.getMenteeModuleIssues (e.g., call the setter for menteeIssuesData with
the response payload or transformed shape expected by the UI, including items
and pagination fields) so the component receives the issues and paging info;
locate the update near setMenteeDetails and use data.getMenteeModuleIssues as
the source.

In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/page.tsx:
- Around line 63-69: Replace the unsafe "as any" casts for admins, mentees, and
mentors when passing to DetailsCard by either aligning DetailsCard's prop types
with the GraphQL-generated types or adding small, explicit mapping/typing
functions (e.g., mapAdminsToContributors, mapMenteesToContributors,
mapMentorsToContributors) that validate/transform mentorshipModule.admins,
mentorshipModule.mentees, and mentorshipModule.mentors into the exact
Contributor[] (or other specific) type expected; alternatively, if you know the
shape matches, replace "as any" with a precise assertion like "as Contributor[]"
and update DetailsCard props to accept Contributor[] so the compiler enforces
correctness.

In `@frontend/src/components/CardDetailsPage.tsx`:
- Around line 87-114: The default for topContributors being set to [] causes the
existing render guard to always pass and can render an empty block; either stop
defaulting topContributors (remove it from the destructured defaults in the
CardDetailsPage props) or change the render condition to check for content
(e.g., use topContributors && topContributors.length > 0) where the Top
Contributors block is rendered so it only renders when there are actual
contributors.

In `@frontend/src/types/declarations.d.ts`:
- Around line 1-4: The ambient module declaration for 'isomorphic-dompurify' is
invalid because it exports an object literal; replace it with a proper type-only
default export by declaring the sanitize symbol as a function or const type and
then exporting that identifier as the default (e.g. declare function
sanitize(...) or declare const sanitize: (...) and then export default
sanitize), or alternatively remove the custom declaration and install/use
`@types/dompurify` which is re-exported by isomorphic-dompurify to provide correct
types; ensure the module name 'isomorphic-dompurify' and the 'sanitize'
identifier are used to locate and update the declaration.
- Line 6: Remove the shorthand ambient declaration "declare module 'lodash';"
from declarations.d.ts so TypeScript can use the installed `@types/lodash` package
instead of treating lodash imports as any; locate and delete that exact line (or
the entire empty module declaration) in frontend/src/types/declarations.d.ts and
ensure there are no other conflicting lodash module declarations remaining.
- Around line 8-20: Remove the handwritten ambient module for 'jest-axe' (the
declare module block defining AxeResults, axe, and toHaveNoViolations) and
instead install and use the official typings: add the `@types/jest-axe` package to
devDependencies and remove this file; if you still need AxeResults in your code
import the proper type from 'axe-core' (e.g., AxeResults) rather than the
incomplete local interface, and let the installed `@types/jest-axe` expose axe and
toHaveNoViolations to Jest.
🧹 Nitpick comments (9)
frontend/src/app/organizations/[organizationKey]/layout.tsx (1)

31-32: Avoid a leading space in the fallback description.

When title is empty, the template yields " organization details". Consider a cleaner fallback string.

♻️ Suggested tweak
-        description: organization?.description ?? `${title || ''} organization details`,
-        title: title || 'Organization',
+        description:
+          organization?.description ??
+          (title ? `${title} organization details` : 'Organization details'),
+        title: title || 'Organization',
frontend/src/app/layout.tsx (1)

87-87: Consider conditional rendering instead of empty string fallback.

Passing an empty string to GoogleAnalytics may still render the GA script with an invalid measurement ID, potentially causing unnecessary network requests or console warnings in environments where GTM_ID is not configured.

A conditional render would be cleaner:

♻️ Proposed refactor using conditional rendering
       </body>
-      <GoogleAnalytics gaId={GTM_ID || ''} />
+      {GTM_ID && <GoogleAnalytics gaId={GTM_ID} />}
     </html>
frontend/src/app/chapters/[chapterKey]/page.tsx (1)

57-69: Fallback handling looks correct; consider consistency with region.

The || '' fallback for suggestedLocation properly handles the newly nullable type. However, chapter.region on line 60 is also optional in the type definition but lacks a similar fallback. If both fields can be absent, consider applying consistent defensive handling:

     { label: 'Location', value: chapter.suggestedLocation || '' },
-    { label: 'Region', value: chapter.region },
+    { label: 'Region', value: chapter.region || '' },

Alternatively, if the backend guarantees region is always present, the type could be tightened to reflect that.

frontend/src/app/api/auth/[...nextauth]/route.ts (1)

96-96: Consider using nullish coalescing for boolean values.

The || false works correctly here, but ?? false would be more precise since it only coerces null/undefined rather than all falsy values. This matters if isOwaspStaff could legitimately be false (which would be preserved with ?? but also coerced to false with ||, so functionally equivalent in this case).

♻️ Optional: Use nullish coalescing
-        token.isOwaspStaff = (session as ExtendedSession).user?.isOwaspStaff || false
+        token.isOwaspStaff = (session as ExtendedSession).user?.isOwaspStaff ?? false
frontend/src/components/UserCard.tsx (1)

62-78: Consider using consistent guard patterns to avoid rendering 0 as text.

The outer check (lines 62-64) uses (X ?? 0) > 0, which always evaluates to a boolean. The inner checks (lines 66, 72, 78) use X && X > 0, which could short-circuit to the number 0 if any count equals 0. In React/JSX, {0 && <Component />} renders the literal "0" as text rather than nothing.

For consistency and safety, consider using the same pattern throughout:

♻️ Suggested refactor for consistency
         <div className="flex flex-wrap justify-center gap-3 px-2">
-            {followersCount && followersCount > 0 && (
+            {(followersCount ?? 0) > 0 && (
               <div className="flex items-center gap-1 text-xs text-gray-600 sm:text-sm dark:text-gray-400">
                 <HiUserGroup className="h-4 w-4" />
-                <span>{millify(followersCount, { precision: 1 })}</span>
+                <span>{millify(followersCount!, { precision: 1 })}</span>
               </div>
             )}
-            {repositoriesCount && repositoriesCount > 0 && (
+            {(repositoriesCount ?? 0) > 0 && (
               <div className="flex items-center gap-1 text-xs text-gray-600 sm:text-sm dark:text-gray-400">
                 <FaFolderOpen className="h-3.5 w-3.5" />
-                <span>{millify(repositoriesCount, { precision: 1 })}</span>
+                <span>{millify(repositoriesCount!, { precision: 1 })}</span>
               </div>
             )}
-            {badgeCount && badgeCount > 0 && (
+            {(badgeCount ?? 0) > 0 && (
               <div className="flex items-center gap-1 text-xs text-gray-600 sm:text-sm dark:text-gray-400">
                 <FaMedal className="h-3.5 w-3.5" aria-label="badges" />
-                <span>{millify(badgeCount, { precision: 1 })}</span>
+                <span>{millify(badgeCount!, { precision: 1 })}</span>
               </div>
             )}
           </div>

Note: The non-null assertion (!) inside the guarded block is safe since the condition already ensures the value is positive.

frontend/src/utils/metaconfig.ts (1)

68-83: Avoid the as cast by typing METADATA_CONFIG.

Line 82's cast is unnecessary and bypasses type inference. Since TypeScript 5.9.3 is in use, define a MetadataConfigEntry type and apply as const satisfies to METADATA_CONFIG so config.type is properly narrowed and the cast can be removed.

🔧 Proposed refactor
diff --git a/frontend/src/utils/metadata.ts b/frontend/src/utils/metadata.ts
+export type MetadataConfigEntry = {
+  description: string
+  keywords: string[]
+  pageTitle: string
+  type: 'website' | 'article' | 'profile'
+}
+
-export const METADATA_CONFIG = {
+export const METADATA_CONFIG = {
   home: {
     description:
       'OWASP Nest is a comprehensive, community-first platform built to enhance collaboration and contribution across the OWASP community.',
     keywords: ['OWASP', 'security', 'open source', 'web security', 'application security'],
     pageTitle: 'Home',
     type: 'website',
   },
   // ...
-}
+} as const satisfies Record<string, MetadataConfigEntry>
diff --git a/frontend/src/utils/metaconfig.ts b/frontend/src/utils/metaconfig.ts
-    type: config.type as 'website' | 'article' | 'profile',
+    type: config.type,
frontend/src/app/page.tsx (2)

325-337: Avoid as any casts; align types instead.

Using as any for contributors, recentIssues, recentMilestones, recentPullRequests, recentReleases, and sponsors undermines the type safety goal of this strict mode PR. These casts hide potential type mismatches and can lead to runtime errors.

Consider aligning the generated GraphQL types with the component prop types, or creating explicit type mappings/adapters to ensure type-safe data flow.


405-405: Same concern: avoid as any for sponsors.

This cast bypasses type checking. If the MovingLogos component expects a different shape than what the GraphQL query returns, consider updating either the query or the component's expected type.

frontend/src/app/organizations/[organizationKey]/repositories/[repositoryKey]/page.tsx (1)

75-79: Redundant optional chaining inside truthy branch.

The condition repository?.url ? already confirms url is truthy, so repository?.url || '#' on line 76 is redundant—the || '#' fallback will never trigger.

🔧 Suggested simplification
     {
       label: 'URL',
       value: repository?.url ? (
-        <Link href={repository?.url || '#'} className="text-blue-400 hover:underline">
+        <Link href={repository.url} className="text-blue-400 hover:underline">
           {repository.url}
         </Link>
       ) : '',
     },

Comment on lines +53 to +62
setMenteeDetails(
data.getMenteeDetails
? {
...data.getMenteeDetails,
bio: data.getMenteeDetails.bio || undefined,
domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined,
tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined,
}
: null
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

menteeIssuesData is no longer populated, so issues will always render empty.
menteeIssuesData is initialized but never set from data.getMenteeModuleIssues after this change. This regresses the issues list and pagination.

🛠️ Proposed fix
   if (data) {
     setMenteeDetails(
       data.getMenteeDetails
         ? {
             ...data.getMenteeDetails,
             bio: data.getMenteeDetails.bio || undefined,
             domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined,
             tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined,
           }
         : null
     )
+    setMenteeIssuesData(data.getMenteeModuleIssues ?? [])
   }
📝 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
setMenteeDetails(
data.getMenteeDetails
? {
...data.getMenteeDetails,
bio: data.getMenteeDetails.bio || undefined,
domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined,
tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined,
}
: null
)
if (data) {
setMenteeDetails(
data.getMenteeDetails
? {
...data.getMenteeDetails,
bio: data.getMenteeDetails.bio || undefined,
domains: (data.getMenteeDetails.domains || undefined) as string[] | undefined,
tags: (data.getMenteeDetails.tags || undefined) as string[] | undefined,
}
: null
)
setMenteeIssuesData(data.getMenteeModuleIssues ?? [])
}
🤖 Prompt for AI Agents
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx
around lines 53 - 62, The menteeIssuesData state is never updated after the API
response, so the issues list/pagination remains empty; after you
setMenteeDetails from data.getMenteeDetails, also set the menteeIssuesData state
from data.getMenteeModuleIssues (e.g., call the setter for menteeIssuesData with
the response payload or transformed shape expected by the UI, including items
and pagination fields) so the component receives the issues and paging info;
locate the update near setMenteeDetails and use data.getMenteeModuleIssues as
the source.

Comment on lines +63 to +69
admins={admins as any}
details={moduleDetails}
domains={mentorshipModule.domains}
domains={mentorshipModule.domains || undefined}
entityKey={moduleKey}
labels={mentorshipModule.labels}
mentees={mentorshipModule.mentees}
mentors={mentorshipModule.mentors}
labels={mentorshipModule.labels || undefined}
mentees={mentorshipModule.mentees as any}
mentors={mentorshipModule.mentors as any}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid as any casts — they undermine strict mode benefits.

Using as any on admins, mentees, and mentors bypasses TypeScript's type checking entirely, which contradicts the PR's objective of improving type safety. These casts can hide legitimate type mismatches that may cause runtime errors.

Consider these alternatives:

  1. Align the DetailsCard prop types with the GraphQL-generated types
  2. Create explicit mapping functions that safely transform the data
  3. Use specific type assertions (e.g., as Contributor[]) if the types are known to be compatible
♻️ Example of safer type handling
-      admins={admins as any}
+      admins={admins ?? []}
       details={moduleDetails}
       domains={mentorshipModule.domains || undefined}
       entityKey={moduleKey}
       labels={mentorshipModule.labels || undefined}
-      mentees={mentorshipModule.mentees as any}
-      mentors={mentorshipModule.mentors as any}
+      mentees={mentorshipModule.mentees ?? []}
+      mentors={mentorshipModule.mentors ?? []}

If type incompatibility exists, align the types at the source (GraphQL types or DetailsCard props) rather than silencing the compiler.

📝 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
admins={admins as any}
details={moduleDetails}
domains={mentorshipModule.domains}
domains={mentorshipModule.domains || undefined}
entityKey={moduleKey}
labels={mentorshipModule.labels}
mentees={mentorshipModule.mentees}
mentors={mentorshipModule.mentors}
labels={mentorshipModule.labels || undefined}
mentees={mentorshipModule.mentees as any}
mentors={mentorshipModule.mentors as any}
admins={admins ?? []}
details={moduleDetails}
domains={mentorshipModule.domains || undefined}
entityKey={moduleKey}
labels={mentorshipModule.labels || undefined}
mentees={mentorshipModule.mentees ?? []}
mentors={mentorshipModule.mentors ?? []}
🤖 Prompt for AI Agents
In
`@frontend/src/app/my/mentorship/programs/`[programKey]/modules/[moduleKey]/page.tsx
around lines 63 - 69, Replace the unsafe "as any" casts for admins, mentees, and
mentors when passing to DetailsCard by either aligning DetailsCard's prop types
with the GraphQL-generated types or adding small, explicit mapping/typing
functions (e.g., mapAdminsToContributors, mapMenteesToContributors,
mapMentorsToContributors) that validate/transform mentorshipModule.admins,
mentorshipModule.mentees, and mentorshipModule.mentors into the exact
Contributor[] (or other specific) type expected; alternatively, if you know the
shape matches, replace "as any" with a precise assertion like "as Contributor[]"
and update DetailsCard props to accept Contributor[] so the compiler enforces
correctness.

Comment on lines +87 to +114
tags = [],
domains = [],
entityLeaders = [],
labels = [],
modules = [],
mentors = [],
mentees = [],
admins = [],
entityKey = '',
geolocationData = [],
healthMetricsData = [],
isActive = true,
isArchived = false,
languages,
programKey,
projectName,
pullRequests,
recentIssues,
recentMilestones,
recentReleases,
languages = [],
programKey = '',
projectName = '',
pullRequests = [],
recentIssues = [],
recentMilestones = [],
recentReleases = [],
repositories = [],
showAvatar = true,
socialLinks,
stats,
summary,
title,
topContributors,
topics,
socialLinks = [],
stats = [],
summary = '',
title = '',
topContributors = [],
topics = [],
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid rendering an empty Top Contributors block after defaulting to [].
Because topContributors now defaults to [] (Line 113), the truthy guard at Line 325 will always pass and can render an empty list. Consider checking length (or keeping undefined) to preserve previous behavior.

♻️ Suggested fix
-        {topContributors && (
+        {topContributors?.length > 0 && (
           <ContributorsList
             contributors={topContributors}
             icon={HiUserGroup}
             maxInitialDisplay={12}
             label="Top Contributors"
             getUrl={getMemberUrl}
           />
         )}
🤖 Prompt for AI Agents
In `@frontend/src/components/CardDetailsPage.tsx` around lines 87 - 114, The
default for topContributors being set to [] causes the existing render guard to
always pass and can render an empty block; either stop defaulting
topContributors (remove it from the destructured defaults in the CardDetailsPage
props) or change the render condition to check for content (e.g., use
topContributors && topContributors.length > 0) where the Top Contributors block
is rendered so it only renders when there are actual contributors.

Comment on lines +1 to +4
declare module 'isomorphic-dompurify' {
const sanitize: (content: string) => string
export default { sanitize }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Invalid syntax in ambient module declaration.

export default { sanitize } is not valid TypeScript in a declaration file—object literals cannot appear in ambient contexts. This will cause a compilation error.

🛠️ Proposed fix
 declare module 'isomorphic-dompurify' {
-  const sanitize: (content: string) => string
-  export default { sanitize }
+  const DOMPurify: {
+    sanitize: (content: string) => string
+  }
+  export default DOMPurify
 }

Alternatively, consider installing @types/dompurify which isomorphic-dompurify re-exports, providing complete and accurate types.

🤖 Prompt for AI Agents
In `@frontend/src/types/declarations.d.ts` around lines 1 - 4, The ambient module
declaration for 'isomorphic-dompurify' is invalid because it exports an object
literal; replace it with a proper type-only default export by declaring the
sanitize symbol as a function or const type and then exporting that identifier
as the default (e.g. declare function sanitize(...) or declare const sanitize:
(...) and then export default sanitize), or alternatively remove the custom
declaration and install/use `@types/dompurify` which is re-exported by
isomorphic-dompurify to provide correct types; ensure the module name
'isomorphic-dompurify' and the 'sanitize' identifier are used to locate and
update the declaration.

export default { sanitize }
}

declare module 'lodash';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat frontend/package.json | jq '.devDependencies["@types/lodash"] // .dependencies["@types/lodash"] // "not found"'

Repository: OWASP/Nest

Length of output: 64


🏁 Script executed:

cat frontend/src/types/declarations.d.ts

Repository: OWASP/Nest

Length of output: 652


Remove the shorthand declare module 'lodash'; declaration.

This shorthand declaration types all lodash imports as any, defeating strict type checking. Since @types/lodash is already installed as a dependency (v^4.17.23), remove this declaration to allow the comprehensive types from @types/lodash to be used instead.

🤖 Prompt for AI Agents
In `@frontend/src/types/declarations.d.ts` at line 6, Remove the shorthand ambient
declaration "declare module 'lodash';" from declarations.d.ts so TypeScript can
use the installed `@types/lodash` package instead of treating lodash imports as
any; locate and delete that exact line (or the entire empty module declaration)
in frontend/src/types/declarations.d.ts and ensure there are no other
conflicting lodash module declarations remaining.

Comment on lines +8 to +20
declare module 'jest-axe' {
interface AxeResults {
violations: Array<{
id: string
impact?: string
description: string
nodes: Array<{ html: string }>
}>
}
const axe: (html: Element | string) => Promise<AxeResults>
const toHaveNoViolations: jest.CustomMatcher
export { axe, toHaveNoViolations }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if jest-axe has bundled types or if `@types/jest-axe` is installed
echo "=== Checking jest-axe package.json ==="
if [ -f frontend/node_modules/jest-axe/package.json ]; then
  cat frontend/node_modules/jest-axe/package.json | jq '.types // .typings // "no bundled types"'
else
  echo "jest-axe not found in node_modules, checking package.json for version"
fi

echo "=== Checking for `@types/jest-axe` in package.json ==="
cat frontend/package.json | jq '.devDependencies["@types/jest-axe"] // .dependencies["@types/jest-axe"] // "not found"'

echo "=== Checking jest-axe version in dependencies ==="
cat frontend/package.json | jq '.devDependencies["jest-axe"] // .dependencies["jest-axe"] // "not found"'

echo "=== Checking if jest-axe has a types index file ==="
if [ -d frontend/node_modules/jest-axe ]; then
  ls -la frontend/node_modules/jest-axe/ | grep -E "\.d\.ts|index"
fi

Repository: OWASP/Nest

Length of output: 339


🌐 Web query:

jest-axe v10.0.0 typescript types bundled

💡 Result:

jest-axe@10.0.0 does not bundle TypeScript declaration files (.d.ts). Its docs still direct TypeScript users to install the community-maintained DefinitelyTyped package instead: @types/jest-axe. [1] [2]

The typings live in @types/jest-axe (e.g., index.d.ts), published separately on npm. [3]

Install:

npm i -D jest-axe `@types/jest-axe`

Sources: npm page for jest-axe [1], jest-axe GitHub README [2], npm page for @types/jest-axe [3].


🌐 Web query:

jest-axe 10.0.0 toHaveNoViolations AxeResults API

💡 Result:

In jest-axe@10.0.0, the matcher toHaveNoViolations() expects to receive an axe-core “results” object (i.e., AxeResults from axe-core.run(...)) and it will error if results.violations is missing/undefined. [1]

AxeResults shape (axe-core)

The AxeResults interface includes (at least) these fields: toolOptions, passes, violations, incomplete, inapplicable. [2]

TypeScript: what to type as “AxeResults”

jest-axe itself doesn’t ship TS types; the community types (@types/jest-axe) type the matcher as:

  • (results?: Partial<AxeResults>) => AssertionsResult [3]

So in TS you typically do:

  • import type { AxeResults } from 'axe-core' (and pass the actual AxeResults returned by axe(...) / axeCore.run(...)), or
  • if you’re calling the matcher “manually”, pass an object that at minimum has violations: [] (because the runtime matcher checks that property). [1][3]

Why this matters in v10

jest-axe@10.0.0 is mainly an axe-core bump (to axe-core@4.10.2). [4]

Sources: [1] [2] [3] [4]


Install @types/jest-axe instead of using manual type declarations.

The jest-axe@10.0.0 package does not bundle TypeScript definitions. The recommended solution is to install @types/jest-axe from DefinitelyTyped rather than maintain custom declarations. Additionally, the manual AxeResults interface is incomplete—it's missing fields like toolOptions, passes, incomplete, and inapplicable that axe-core results include. The AxeResults type should come from axe-core, not redeclared here.

🤖 Prompt for AI Agents
In `@frontend/src/types/declarations.d.ts` around lines 8 - 20, Remove the
handwritten ambient module for 'jest-axe' (the declare module block defining
AxeResults, axe, and toHaveNoViolations) and instead install and use the
official typings: add the `@types/jest-axe` package to devDependencies and remove
this file; if you still need AxeResults in your code import the proper type from
'axe-core' (e.g., AxeResults) rather than the incomplete local interface, and
let the installed `@types/jest-axe` expose axe and toHaveNoViolations to Jest.

@arkid15r arkid15r marked this pull request as draft January 29, 2026 17:01
@arkid15r
Copy link
Collaborator

arkid15r commented Feb 9, 2026

Implemented in #3776

@arkid15r arkid15r closed this Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: enhance frontend code quality and enable strict mode

2 participants