Skip to content

feat: enhance HackathonPageClient and UI components#435

Closed
0xdevcollins wants to merge 1 commit into
mainfrom
feat/update,refactor,improve
Closed

feat: enhance HackathonPageClient and UI components#435
0xdevcollins wants to merge 1 commit into
mainfrom
feat/update,refactor,improve

Conversation

@0xdevcollins
Copy link
Copy Markdown
Collaborator

@0xdevcollins 0xdevcollins commented Mar 3, 2026

  • Introduced primary shadow variable in globals.css for improved styling.
  • Simplified tab visibility logic in HackathonPageClient by removing unnecessary checks.
  • Updated profile-data.tsx to improve type safety and state management.
  • Refactored submissions page components for better readability and performance.
  • Enhanced navigation and sidebar components for improved user experience.
  • Adjusted EmptyState and Navbar components for consistent styling across the application.

Summary by CodeRabbit

  • Bug Fixes

    • Improved date rendering with fallback values across submissions and earnings
    • Enhanced null value handling in earnings and vote counts
    • Fixed tab configuration validation with development warnings
    • Restricted "New Hackathon" creation to valid contexts
  • Improvements

    • Expanded hackathon tab options including resources, participants, and announcements
    • Better API request handling with cancellation support
  • Style

    • Standardized shadow effects using CSS variables for consistent theming

- Introduced primary shadow variable in globals.css for improved styling.
- Simplified tab visibility logic in HackathonPageClient by removing unnecessary checks.
- Updated profile-data.tsx to improve type safety and state management.
- Refactored submissions page components for better readability and performance.
- Enhanced navigation and sidebar components for improved user experience.
- Adjusted EmptyState and Navbar components for consistent styling across the application.
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
boundless-kd16 Ready Ready Preview, Comment Mar 3, 2026 7:08pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 3, 2026

📝 Walkthrough

Walkthrough

This PR refactors component signatures across the codebase to use arrow functions with explicit React.FC types, strengthens type safety with better interfaces, hardens data handling with nullish coalescing patterns for null/undefined values, expands API structures for richer submission and hackathon data, and aligns styling with CSS design tokens. Hackathon tab visibility logic is adjusted to improve gating and URL handling.

Changes

Cohort / File(s) Summary
Hackathon Tab Visibility & Logic
app/(landing)/hackathons/[slug]/HackathonPageClient.tsx
Removed gating logic for tabs, expanded tabIdToEnabledKey mapping, added development warnings, introduced defensive guards in URL handling effects, adjusted isTeamFormationEnabled logic, and clarified type annotations.
Component Signature Refactoring to Arrow Functions
app/me/hackathons/submissions/page.tsx, app/me/participating/page.tsx, components/profile/ProfileDataClient.tsx, components/hackathons/ProgressIndicator.tsx, components/landing-page/navbar.tsx, components/nav-main.tsx, components/nav-user.tsx
Converted function declarations to React.FC arrow functions with explicit type annotations; added supporting interfaces (NavMainProps, NavUserProps, MeLayoutProps); refactored exports to use const pattern.
Submission Data Refactoring & Type Strengthening
app/me/hackathons/submissions/submission-components.tsx, hooks/use-participant-submission.ts, components/hackathons/submissions/SubmissionDetailModal.tsx, components/organization/cards/ParticipantSubmission.tsx
Introduced getSafeUrl and formatDate helpers with explicit typing, expanded SubmissionRow type with disqualificationReason, updated status utilities to const arrow functions, added robust date fallbacks (submissionDate ?? submittedAt).
API Type Expansion & Null Safety
lib/api/hackathons.ts, lib/api/types.ts, types/hackathon/core.ts, hooks/hackathon/use-hackathon-transform.ts, hooks/hackathon/use-hackathons-list.ts
Expanded ParticipantSubmission and ExploreSubmissionsResponse with optional/nullable fields, added nested project and participant objects, introduced type-guard helpers (isHackathon, isHackathonError), updated prize tier calculations with fallback logic for prizeAmount/amount fields.
Nullish Coalescing & Data Handling
app/me/earnings/page.tsx, components/organization/cards/Participant.tsx, lib/providers/hackathonProvider.tsx
Applied nullish coalescing operators (\?\?) throughout for currency formatting, date handling, and submitter identity resolution; improved fallback chains for null/undefined values.
CSS Tokens & Styling Updates
app/globals.css, components/EmptyState.tsx, components/landing-page/navbar.tsx, components/landing-page/hackathon/HackathonCard.tsx
Added --primary-shadow CSS variable for light/dark themes, updated EmptyState to use CSS custom property shadow, refactored navbar styling with bg-background-main-bg and shrink-0 utilities, restructured HackathonCard with Link-based navigation and CategoriesDisplay component.
Navigation & Interaction Logic
components/landing-page/hackathon/HackathonCard.tsx, components/organization/hackathons/details/HackathonSelector.tsx, app/(landing)/profile/[username]/profile-data.tsx
Replaced programmatic router navigation with Next.js Link, introduced handleFilterSelect helpers for state management, gated "New Hackathon" option behind organizationId check, added Organizations tab visibility logic.
Hook Type Definitions & Validation
hooks/use-auth.ts, hooks/hackathon/use-organizer-submissions.ts, lib/api/api.ts, lib/api/earnings.ts
Added explicit return type annotations (UseOrganizerSubmissionsReturn, GetMeResponse), introduced AbortSignal support for request cancellation, added username validation with trimming, updated hook prop typing with dedicated interfaces.
Profile & Earnings Features
app/(landing)/profile/[username]/profile-data.tsx, components/profile/PublicEarningsTab.tsx, lib/api/earnings.ts, types/earnings.ts
Converted ProfileData to arrow function, introduced handleFilterSelect helper, added color grouping with bgColor in SOURCE_CONFIG, added AbortSignal support to getPublicEarnings, refactored EarningsBreakdown as type alias.
Layout & Minor Updates
app/me/layout.tsx, components/app-sidebar.tsx, components/profile/ProfileOverview.tsx, app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx, package.json
Introduced MeLayoutProps and typedProfile normalization in MeLayout, refined useMemo dependencies in app-sidebar, simplified displayName construction, updated Tailwind utilities (flex-shrink-0 → shrink-0), added dev:webpack script.
Debug & Type Refinement
components/hackathons/submissions/submissionTab.tsx, hooks/hackathon/use-submissions.ts
Added console.log statements for submissions debugging, added runtime logging for allSubmissions and filtered submissions tracking.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Benjtalkshow

Poem

🐰 Tabs now filter true without the gate,
Types dance sharp, no more "any" fate,
Null and void bow to the coalesce,
Arrows fly where functions blessed,
Shadow tokens glow, our code's caress!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is partially related to the changeset, referring to real aspects of the change (HackathonPageClient and UI components) but lacks specificity about the primary objectives like tab visibility logic simplification, type safety improvements, and shadow CSS variable additions.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/update,refactor,improve

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.

Copy link
Copy Markdown

@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: 13

🧹 Nitpick comments (8)
components/nav-user.tsx (1)

53-56: Consider extracting initials once to avoid duplicated render logic.

The same initials computation appears twice; computing once improves readability and reduces repeated work.

Refactor sketch
 export const NavUser = ({ user }: NavUserProps): React.ReactElement => {
   const { isMobile } = useSidebar();
+  const userInitials = user.name
+    .split(' ')
+    .map(part => part[0] ?? '')
+    .join('');

   return (
@@
-                <AvatarFallback className='from-primary/20 to-primary/10 text-primary rounded-lg bg-linear-to-br font-semibold'>
-                  {user.name
-                    .split(' ')
-                    .map(n => n[0])
-                    .join('')}
-                </AvatarFallback>
+                <AvatarFallback className='from-primary/20 to-primary/10 text-primary rounded-lg bg-linear-to-br font-semibold'>
+                  {userInitials}
+                </AvatarFallback>
@@
-                  <AvatarFallback className='from-primary/20 to-primary/10 text-primary rounded-lg bg-linear-to-br font-semibold'>
-                    {user.name
-                      .split(' ')
-                      .map(n => n[0])
-                      .join('')}
-                  </AvatarFallback>
+                  <AvatarFallback className='from-primary/20 to-primary/10 text-primary rounded-lg bg-linear-to-br font-semibold'>
+                    {userInitials}
+                  </AvatarFallback>

Also applies to: 81-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/nav-user.tsx` around lines 53 - 56, Extract the repeated initials
computation into a single variable (e.g., initials) near the top of the NavUser
component so both JSX locations reuse it instead of repeating user.name.split('
').map(n => n[0]).join(''); update the two places that currently compute
initials (the occurrences using user.name.split(...).map(...).join('')) to
render the initials variable instead, ensuring the logic remains identical and
handles empty or undefined name safely if needed.
components/nav-main.tsx (1)

35-39: Optional: early return when items is empty.

A small early return keeps this component simpler when there is nothing to render.

As per coding guidelines, "Use early returns to improve code clarity".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/nav-main.tsx` around lines 35 - 39, The NavMain component should
return early when there are no items to render: inside the NavMain function
(which already reads items and calls usePathname), add a guard like checking if
items is falsy or items.length === 0 and return null (or an empty React
fragment) before rendering <SidebarGroup>; this keeps NavMain simple and avoids
rendering an empty sidebar when items is empty.
app/me/participating/page.tsx (1)

51-56: Consider adding runtime array validation for defensive programming, though types indicate these should be arrays.

Lines 51–56 use type assertions on optional fields that are immediately followed by .filter() calls. While GetMeResponse and User types declare these as optional arrays, defensive validation with Array.isArray() guards would protect against unexpected API contract violations. That said, similar patterns appear elsewhere in the codebase without such checks (e.g., ActivityFeed.tsx), so this is an optional hardening measure rather than a critical fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/participating/page.tsx` around lines 51 - 56, The code uses type
assertions to treat optional fields as arrays (joinedHackathons,
hackathonsAsParticipant, submissions) but then calls array methods; replace
those assertions with runtime guards by checking Array.isArray(...) and falling
back to [] (e.g., const joinedHackathons =
Array.isArray(profile.user?.joinedHackathons) ? profile.user.joinedHackathons :
[]; do the same for hackathonsAsParticipant and submissions) so subsequent
.filter()/.map() calls are safe if the API returns something unexpected.
package.json (1)

6-7: Redundant script definition.

The dev:webpack script is identical to dev — both execute next dev. If this is a placeholder for future webpack-specific configuration (e.g., next dev --no-turbo or environment variables), consider adding a differentiating flag or a comment clarifying intent. Otherwise, this adds confusion without functional benefit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 6 - 7, The package.json defines two identical npm
scripts ("dev" and "dev:webpack") which is redundant; either remove the
"dev:webpack" entry or change it to a distinct command that reflects intended
webpack-specific behavior (for example add a distinguishing flag or env var),
and if you intend to keep it as a placeholder add a brief comment in the
package.json or README clarifying its purpose; locate the "dev" and
"dev:webpack" script entries and update or delete the "dev:webpack" key
accordingly.
app/me/layout.tsx (1)

14-29: Prefer shared API profile types over local mirrored interfaces + assertion cast.

MeLayoutProfile and profile as ... introduce drift risk as GetMeResponse evolves. Reuse the shared API type directly to keep this layout aligned with source contracts.

As per coding guidelines, "Use provided Types from Trustless Work documentation when applicable."

Also applies to: 38-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/layout.tsx` around lines 14 - 29, Replace the local duplicated
interfaces (ProfileItemWithId and MeLayoutProfile) and any "profile as ..."
assertion casts by importing and using the shared API response type (e.g.,
GetMeResponse) from the central types package; update the layout.tsx
props/variables to use GetMeResponse (or the exact exported type name) instead
of the local MeLayoutProfile so the component consumes the canonical API
contract and remove the redundant local interfaces.
components/profile/ProfileDataClient.tsx (1)

113-117: Use a class helper function for this conditional block per coding guidelines.

This conditional className can be cleaner and more maintainable with cn() per the repo's guidance for conditional classes in JSX. Add the import for cn from @/lib/utils and refactor the ternary operator accordingly.

💡 Suggested refactor
+import { cn } from '@/lib/utils';
@@
-                      className={
-                        selectedFilter === filter
-                          ? 'bg-zinc-800'
-                          : 'hover:bg-zinc-600/50! hover:text-white!'
-                      }
+                      className={cn(
+                        selectedFilter === filter ? 'bg-zinc-800' : 'hover:bg-zinc-600/50! hover:text-white!'
+                      )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/profile/ProfileDataClient.tsx` around lines 113 - 117, Refactor
the conditional className on the element using selectedFilter === filter to use
the cn helper: add an import for cn from "@/lib/utils" at the top of
ProfileDataClient.tsx, then replace the ternary in the className prop (the block
referencing selectedFilter and filter) with a call to cn that always includes
the base classes and conditionally applies 'bg-zinc-800' when selectedFilter ===
filter and 'hover:bg-zinc-600/50 hover:text-white' otherwise; ensure you remove
the inline ternary and use cn(...) for readability and maintainability.
app/(landing)/hackathons/[slug]/HackathonPageClient.tsx (1)

194-201: Scope dev warning to mapping gaps, not intentionally hidden tabs.

Line 194 currently warns for any hidden tab, including valid “disabled by config” cases. This can flood dev logs and obscure real mapping misses.

🛠️ Suggested refinement
       return tabs.filter(tab => {
         if (tab.id === 'overview') return true;
-        const key = (tabIdToEnabledKey[tab.id] ?? tab.id) as EnabledTab;
+        const mappedKey = tabIdToEnabledKey[tab.id];
+        const key = (mappedKey ?? tab.id) as EnabledTab;
         const isVisible = enabledSet.has(key);
-        if (
-          !isVisible &&
-          process.env.NODE_ENV === 'development' &&
-          currentHackathon?.enabledTabs
-        ) {
+        if (!mappedKey && process.env.NODE_ENV === 'development') {
           console.warn(
-            `[HackathonPageClient] Tab "${tab.id}" (enabled key: ${key}) is not in currentHackathon.enabledTabs and will be hidden. Add the tab id to tabIdToEnabledKey and ensure the backend includes the key in enabledTabs when the tab should be visible.`
+            `[HackathonPageClient] Tab "${tab.id}" has no entry in tabIdToEnabledKey; it may be hidden when enabledTabs is configured.`
           );
         }
         return isVisible;
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/hackathons/[slug]/HackathonPageClient.tsx around lines 194 -
201, The dev warning should only fire when there is a missing mapping between
the tab id and its enabled-key, not for tabs purposely hidden by config; update
the condition around the console.warn in HackathonPageClient.tsx to check that
the mapping for the tab is absent (e.g., key === undefined or
!tabIdToEnabledKey[tab.id]) instead of using !isVisible, while still ensuring
process.env.NODE_ENV === 'development' and currentHackathon?.enabledTabs are
true; reference tab.id, key, tabIdToEnabledKey, isVisible and
currentHackathon?.enabledTabs when making this change.
hooks/hackathon/use-organizer-submissions.ts (1)

38-41: Convert hook to const arrow function to align with TS guidelines.

Line 38 uses a function declaration; the TS guideline for this file prefers const arrow functions with explicit type annotations. The return type is already explicitly defined.

♻️ Suggested refactor
-export function useOrganizerSubmissions(
+export const useOrganizerSubmissions = (
   hackathonId: string,
   initialLimit = 12
-): UseOrganizerSubmissionsReturn {
+): UseOrganizerSubmissionsReturn => {
@@
-}
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hackathon/use-organizer-submissions.ts` around lines 38 - 41, Change
the function declaration for useOrganizerSubmissions into a const arrow function
with the same explicit return type: replace "export function
useOrganizerSubmissions(hackathonId: string, initialLimit = 12):
UseOrganizerSubmissionsReturn {" with "export const useOrganizerSubmissions =
(hackathonId: string, initialLimit = 12): UseOrganizerSubmissionsReturn => {"
and keep the exported name and parameter types identical; ensure any references
to useOrganizerSubmissions remain unchanged and that the surrounding code
(closures/hoisting) still works with the const declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/`(landing)/profile/[username]/profile-data.tsx:
- Around line 126-133: The Organizations tab trigger (TabsTrigger with value
'organizations') is hidden on md+ screens due to the 'md:hidden' class, making
the tab unreachable on desktop while its TabsContent still renders; remove the
'md:hidden' responsive utility (or change it so the trigger is visible on md+
e.g., use no responsive hide or use 'hidden md:block' if you intended
mobile-only) from the TabsTrigger that uses TAB_CLASS so the 'organizations'
TabsTrigger is available when isAuthenticated && isOwnProfile.

In `@app/me/hackathons/submissions/page.tsx`:
- Around line 228-229: Replace invalid Tailwind opacity shorthands used in the
className (bg-white/3 and bg-white/2.5) with Tailwind arbitrary opacity syntax;
for example change bg-white/3 to bg-white/[0.3] and bg-white/2.5 to
bg-white/[0.25] wherever they appear in the JSX (the className string on the
element in page.tsx), so the classes are valid Tailwind utilities.

In `@components/hackathons/submissions/SubmissionDetailModal.tsx`:
- Around line 275-279: In SubmissionDetailModal, avoid passing an empty string
into formatDate by first checking submission.submissionDate and
submission.submittedAt for a valid value; if neither exists, render a clear
fallback (e.g., '—' or 'N/A') instead of calling formatDate(''), otherwise call
formatDate with the chosen timestamp. Update the JSX around formatDate(...) to
use this guarded value (referencing submission.submissionDate,
submission.submittedAt and formatDate) so the UI shows the fallback when both
timestamps are missing.

In `@components/hackathons/submissions/submissionTab.tsx`:
- Line 84: Remove the debug console.log by deleting the console.log({
submissions }) statement in the submissionTab.tsx render path (or replace it
with a conditional/dev-only logger), so that the variable submissions is not
logged on every render in production; locate the console.log call inside the
SubmissionTab/component render and either remove it or wrap it with a guard like
a development-only check or use your app's logger that respects NODE_ENV.

In `@components/landing-page/navbar.tsx`:
- Around line 58-64: The profile prop's type union includes an unused
UserProfile type which is never produced at runtime by useAuthStatus()/getMe();
update the declaration in navbar.tsx to remove UserProfile and make profile?:
import('@/lib/api/types').GetMeResponse | null so the type reflects actual
runtime values (locate the profile?: UserProfile |
import('@/lib/api/types').GetMeResponse | null line and replace the union to
only include GetMeResponse | null).

In `@components/nav-main.tsx`:
- Around line 48-51: The active-route check in the isActive assignment can
produce false negatives when item.url ends with a slash; normalize item.url
before the prefix match by trimming any trailing slash (except when item.url is
just "/") and then use that normalized value in the pathname.startsWith check
(still keep the direct equality check against pathname and the PROFILE_ROUTE
exclusion). Update the logic around isActive to compute a normalizedUrl (derived
from item.url) and use pathname?.startsWith(`${normalizedUrl}/`) so sub-route
matching works reliably.

In `@components/organization/cards/ParticipantSubmission.tsx`:
- Around line 73-81: The current rendering in ParticipantSubmission.tsx uses new
Date(participant.submission.submissionDate ?? participant.submission.submittedAt
?? '') which can produce "Invalid Date"; change the logic in the render path
that uses participant.submission.submissionDate /
participant.submission.submittedAt to first determine a candidateDate (e.g.,
submission.submissionDate ?? submission.submittedAt), validate it (e.g., ensure
it's truthy and new Date(candidateDate).toString() !== 'Invalid Date'), and if
invalid render a fallback string (like '—' or 'No date') instead of formatting;
update the expression that calls toLocaleDateString to only run when the date is
valid and otherwise output the fallback.

In `@hooks/hackathon/use-hackathons-list.ts`:
- Around line 82-86: The reducer that sums prize amounts (the arrow function
"(sum, tier) => sum + Number(tier.prizeAmount ?? (tier as { amount?: string
}).amount ?? 0)") can produce NaN for legacy string values; update it to parse
the candidate value into a number, check for NaN/Infinity (e.g., with
Number.isFinite or isNaN) and use 0 as a fallback so the addition never
propagates NaN. Target the reducer used to compute prize_pool_low in
use-hackathons-list.ts and replace the direct Number(...) expression with a
guarded parsed value (parsed = Number(...); if !Number.isFinite(parsed) parsed =
0) before adding to sum.

In `@hooks/hackathon/use-submissions.ts`:
- Around line 26-29: Remove the leftover debug console.log statements in this
hook: delete the console.log({ exploreSubmissions }) and console.log({
allSubmissions }) that run around the submissions useMemo (and also remove the
third debug console.log mentioned later in the file). Locate the submissions
useMemo block (references: submissions, isOrganizer, allSubmissions) and any
other console.log calls in this file (e.g., the extra one near the end of the
hook) and delete them so no debug logging runs during renders.

In `@hooks/use-participant-submission.ts`:
- Around line 97-100: The code sets submissionDate to '' which is an invalid
sentinel; update the mapping in the useParticipantSubmission hook (the
transformed object with submissionDate) to return undefined when no date exists:
use participant.submission.submissionDate ?? participant.submission.submittedAt
?? undefined instead of ''. Also update the related return type/interface for
useParticipantSubmission (or the transformed submission type) so submissionDate
is optional/nullable (e.g., string | undefined or Date | undefined) and
consumers handle the absent field rather than an empty string.

In `@lib/api/api.ts`:
- Line 35: The 429-retry flow must respect the request AbortSignal: update the
429 interceptor/handler (the function that reads response.status === 429 and
schedules retry) to check requestOptions.signal?.aborted before scheduling any
sleep and immediately after the sleep returns, and propagate cancellation by
throwing the appropriate abort error (or rethrowing if you detect ERR_CANCELED).
Also update the shared sleep/delay helper used by that retry flow to accept an
AbortSignal and reject early when signal.aborted (so the delay can be
interrupted). Ensure the code paths reference the request option named signal
and handle ERR_CANCELED/AbortError in the retry catch to stop further retries.

In `@lib/api/hackathons.ts`:
- Around line 1746-1748: The current code in lib/api/hackathons.ts reads const
body = res.data and returns [] when body?.data?.submissions is missing, silently
hiding API failures; change this to surface the error instead of returning an
empty array—check body and res (e.g., if (!body || !body.data ||
!body.data.submissions)) and throw a descriptive Error (include res.status,
res.statusText and JSON-stringified body) or return a Result/Error object so
callers can handle it; update the function that uses body.data.submissions
(reference the local variables body and res and the consumer of submissions) to
expect thrown errors or an error result.

In `@lib/providers/hackathonProvider.tsx`:
- Around line 252-260: The mapping uses the nullish coalescing operator (??) so
empty strings are treated as valid values and produce blank names/avatars;
replace these expressions (e.g., sub.participant?.name ?? sub.teamName ??
sub.teamMembers?.[0]?.name ?? 'Unknown Participant' and the submitterAvatar
expression sub.participant?.image ?? sub.teamMembers?.[0]?.avatar ?? sub.logo ??
'') with a small helper that returns the first non-empty string (for example
coalesceNonEmpty(...values) or a utility that checks value && value.trim() !==
''), then use that helper for both submitter name and submitter avatar (and any
other similar coalescing expressions) so empty strings fall back to the intended
defaults.

---

Nitpick comments:
In `@app/`(landing)/hackathons/[slug]/HackathonPageClient.tsx:
- Around line 194-201: The dev warning should only fire when there is a missing
mapping between the tab id and its enabled-key, not for tabs purposely hidden by
config; update the condition around the console.warn in HackathonPageClient.tsx
to check that the mapping for the tab is absent (e.g., key === undefined or
!tabIdToEnabledKey[tab.id]) instead of using !isVisible, while still ensuring
process.env.NODE_ENV === 'development' and currentHackathon?.enabledTabs are
true; reference tab.id, key, tabIdToEnabledKey, isVisible and
currentHackathon?.enabledTabs when making this change.

In `@app/me/layout.tsx`:
- Around line 14-29: Replace the local duplicated interfaces (ProfileItemWithId
and MeLayoutProfile) and any "profile as ..." assertion casts by importing and
using the shared API response type (e.g., GetMeResponse) from the central types
package; update the layout.tsx props/variables to use GetMeResponse (or the
exact exported type name) instead of the local MeLayoutProfile so the component
consumes the canonical API contract and remove the redundant local interfaces.

In `@app/me/participating/page.tsx`:
- Around line 51-56: The code uses type assertions to treat optional fields as
arrays (joinedHackathons, hackathonsAsParticipant, submissions) but then calls
array methods; replace those assertions with runtime guards by checking
Array.isArray(...) and falling back to [] (e.g., const joinedHackathons =
Array.isArray(profile.user?.joinedHackathons) ? profile.user.joinedHackathons :
[]; do the same for hackathonsAsParticipant and submissions) so subsequent
.filter()/.map() calls are safe if the API returns something unexpected.

In `@components/nav-main.tsx`:
- Around line 35-39: The NavMain component should return early when there are no
items to render: inside the NavMain function (which already reads items and
calls usePathname), add a guard like checking if items is falsy or items.length
=== 0 and return null (or an empty React fragment) before rendering
<SidebarGroup>; this keeps NavMain simple and avoids rendering an empty sidebar
when items is empty.

In `@components/nav-user.tsx`:
- Around line 53-56: Extract the repeated initials computation into a single
variable (e.g., initials) near the top of the NavUser component so both JSX
locations reuse it instead of repeating user.name.split(' ').map(n =>
n[0]).join(''); update the two places that currently compute initials (the
occurrences using user.name.split(...).map(...).join('')) to render the initials
variable instead, ensuring the logic remains identical and handles empty or
undefined name safely if needed.

In `@components/profile/ProfileDataClient.tsx`:
- Around line 113-117: Refactor the conditional className on the element using
selectedFilter === filter to use the cn helper: add an import for cn from
"@/lib/utils" at the top of ProfileDataClient.tsx, then replace the ternary in
the className prop (the block referencing selectedFilter and filter) with a call
to cn that always includes the base classes and conditionally applies
'bg-zinc-800' when selectedFilter === filter and 'hover:bg-zinc-600/50
hover:text-white' otherwise; ensure you remove the inline ternary and use
cn(...) for readability and maintainability.

In `@hooks/hackathon/use-organizer-submissions.ts`:
- Around line 38-41: Change the function declaration for useOrganizerSubmissions
into a const arrow function with the same explicit return type: replace "export
function useOrganizerSubmissions(hackathonId: string, initialLimit = 12):
UseOrganizerSubmissionsReturn {" with "export const useOrganizerSubmissions =
(hackathonId: string, initialLimit = 12): UseOrganizerSubmissionsReturn => {"
and keep the exported name and parameter types identical; ensure any references
to useOrganizerSubmissions remain unchanged and that the surrounding code
(closures/hoisting) still works with the const declaration.

In `@package.json`:
- Around line 6-7: The package.json defines two identical npm scripts ("dev" and
"dev:webpack") which is redundant; either remove the "dev:webpack" entry or
change it to a distinct command that reflects intended webpack-specific behavior
(for example add a distinguishing flag or env var), and if you intend to keep it
as a placeholder add a brief comment in the package.json or README clarifying
its purpose; locate the "dev" and "dev:webpack" script entries and update or
delete the "dev:webpack" key accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0060643 and 9d1f1a9.

📒 Files selected for processing (38)
  • app/(landing)/hackathons/[slug]/HackathonPageClient.tsx
  • app/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsx
  • app/(landing)/profile/[username]/profile-data.tsx
  • app/globals.css
  • app/me/earnings/page.tsx
  • app/me/hackathons/submissions/page.tsx
  • app/me/hackathons/submissions/submission-components.tsx
  • app/me/layout.tsx
  • app/me/participating/page.tsx
  • components/EmptyState.tsx
  • components/app-sidebar.tsx
  • components/hackathons/ProgressIndicator.tsx
  • components/hackathons/submissions/SubmissionDetailModal.tsx
  • components/hackathons/submissions/submissionTab.tsx
  • components/landing-page/hackathon/HackathonCard.tsx
  • components/landing-page/navbar.tsx
  • components/nav-main.tsx
  • components/nav-user.tsx
  • components/organization/cards/Participant.tsx
  • components/organization/cards/ParticipantSubmission.tsx
  • components/organization/hackathons/details/HackathonSelector.tsx
  • components/profile/ProfileDataClient.tsx
  • components/profile/ProfileOverview.tsx
  • components/profile/PublicEarningsTab.tsx
  • hooks/hackathon/use-hackathon-transform.ts
  • hooks/hackathon/use-hackathons-list.ts
  • hooks/hackathon/use-organizer-submissions.ts
  • hooks/hackathon/use-submissions.ts
  • hooks/use-auth.ts
  • hooks/use-participant-submission.ts
  • lib/api/api.ts
  • lib/api/earnings.ts
  • lib/api/hackathons.ts
  • lib/api/types.ts
  • lib/providers/hackathonProvider.tsx
  • package.json
  • types/earnings.ts
  • types/hackathon/core.ts

Comment on lines +126 to +133
{isAuthenticated && isOwnProfile && (
<TabsTrigger
value='organizations'
className={clsx(TAB_CLASS, 'md:hidden')}
>
Organizations
</TabsTrigger>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Organizations tab becomes unreachable on desktop.

Line 129 adds md:hidden, so on md+ screens there is no trigger for the organizations tab, even though its TabsContent still renders (Line 180 onward).

Proposed fix
- className={clsx(TAB_CLASS, 'md:hidden')}
+ className={TAB_CLASS}
📝 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
{isAuthenticated && isOwnProfile && (
<TabsTrigger
value='organizations'
className={clsx(TAB_CLASS, 'md:hidden')}
>
Organizations
</TabsTrigger>
)}
{isAuthenticated && isOwnProfile && (
<TabsTrigger
value='organizations'
className={clsx(TAB_CLASS)}
>
Organizations
</TabsTrigger>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/profile/[username]/profile-data.tsx around lines 126 - 133,
The Organizations tab trigger (TabsTrigger with value 'organizations') is hidden
on md+ screens due to the 'md:hidden' class, making the tab unreachable on
desktop while its TabsContent still renders; remove the 'md:hidden' responsive
utility (or change it so the trigger is visible on md+ e.g., use no responsive
hide or use 'hidden md:block' if you intended mobile-only) from the TabsTrigger
that uses TAB_CLASS so the 'organizations' TabsTrigger is available when
isAuthenticated && isOwnProfile.

Comment on lines +228 to 229
className='flex items-center gap-2 rounded-lg border border-white/5 bg-white/3 px-4 py-2 text-sm'
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Tailwind config files =="
fd -HI '^tailwind\.config\.(js|cjs|mjs|ts)$'

echo
echo "== Look for custom opacity/theme extensions =="
fd -HI '^tailwind\.config\.(js|cjs|mjs|ts)$' -x cat

echo
echo "== Occurrences of potentially unsupported classes =="
rg -n --type=tsx "bg-white/(2\.5|3)\b"

Repository: boundlessfi/boundless

Length of output: 239


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Root directory structure =="
ls -la | head -30

echo
echo "== Search for tailwind config (broader) =="
find . -maxdepth 2 -type f -name "*tailwind*" 2>/dev/null || true

echo
echo "== Search for next.config or tsconfig =="
ls -la next.config.* 2>/dev/null || echo "No next.config found"

echo
echo "== Check if tailwind is in package.json =="
cat package.json | grep -A5 -B5 "tailwind" || echo "No tailwind reference found"

echo
echo "== Look for opacity classes in tsx files (no type flag) =="
rg -n "bg-white/(2\.5|3)\b" --include="*.tsx"

echo
echo "== Check the specific file =="
if [ -f "app/me/hackathons/submissions/page.tsx" ]; then
  wc -l "app/me/hackathons/submissions/page.tsx"
  echo "--- Lines 225-235 ---"
  sed -n '225,235p' "app/me/hackathons/submissions/page.tsx"
  echo
  echo "--- Lines 245-255 ---"
  sed -n '245,255p' "app/me/hackathons/submissions/page.tsx"
else
  echo "File not found"
fi

Repository: boundlessfi/boundless

Length of output: 3719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for CSS/global files =="
find . -type f \( -name "globals.css" -o -name "global.css" -o -name "*.css" \) | head -20

echo
echo "== Check next.config.ts for Tailwind theme =="
if [ -f "next.config.ts" ]; then
  echo "--- next.config.ts content ---"
  cat next.config.ts
fi

echo
echo "== Look for opacity classes in the target file =="
rg "bg-white/(2\.5|3)" app/me/hackathons/submissions/page.tsx -n

echo
echo "== Extract lines 225-235 from target file =="
sed -n '225,235p' app/me/hackathons/submissions/page.tsx | cat -n

echo
echo "== Extract lines 245-255 from target file =="
sed -n '245,255p' app/me/hackathons/submissions/page.tsx | cat -n

echo
echo "== Search for tailwind config in any TS/CSS files =="
rg "opacity.*[23]" . --type ts --type css -A 2 -B 2 | head -50

Repository: boundlessfi/boundless

Length of output: 5931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Read app/globals.css to check for opacity config =="
cat app/globals.css

echo
echo "== Search for `@layer` or `@config` directives in any CSS files =="
rg "@layer|@config|opacity" app/ --type css -A 2 -B 2

Repository: boundlessfi/boundless

Length of output: 17797


Replace invalid Tailwind opacity classes with arbitrary value syntax.

The opacity values bg-white/3 and bg-white/2.5 are not part of Tailwind's default opacity scale (which includes: 5, 10, 20, 25, 30, 40, 50, 60, 70, 75, 80, 90, 95) and are not extended in your configuration. Use arbitrary values instead:

Recommended changes
- className='flex items-center gap-2 rounded-lg border border-white/5 bg-white/3 px-4 py-2 text-sm'
+ className='flex items-center gap-2 rounded-lg border border-white/5 bg-white/[0.03] px-4 py-2 text-sm'
@@
- className='overflow-hidden rounded-xl border border-white/5 bg-white/2.5'
+ className='overflow-hidden rounded-xl border border-white/5 bg-white/[0.025]'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/page.tsx` around lines 228 - 229, Replace
invalid Tailwind opacity shorthands used in the className (bg-white/3 and
bg-white/2.5) with Tailwind arbitrary opacity syntax; for example change
bg-white/3 to bg-white/[0.3] and bg-white/2.5 to bg-white/[0.25] wherever they
appear in the JSX (the className string on the element in page.tsx), so the
classes are valid Tailwind utilities.

Comment on lines +275 to +279
<span>
Submitted:{' '}
{formatDate(
submission.submissionDate ?? submission.submittedAt ?? ''
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard date formatting when both submission timestamps are missing.

Line 277 can pass '' into formatDate, which results in an invalid date label in the UI. Add an empty/invalid-date fallback before formatting.

💡 Suggested fix
-  const formatDate = (dateString: string) => {
-    const date = new Date(dateString);
+  const formatDate = (dateString?: string) => {
+    if (!dateString) return '—';
+    const date = new Date(dateString);
+    if (Number.isNaN(date.getTime())) return '—';
     return date.toLocaleDateString('en-US', {
       month: 'long',
       day: 'numeric',
       year: 'numeric',
     });
   };
@@
-                  {formatDate(
-                    submission.submissionDate ?? submission.submittedAt ?? ''
-                  )}
+                  {formatDate(
+                    submission.submissionDate ?? submission.submittedAt
+                  )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/hackathons/submissions/SubmissionDetailModal.tsx` around lines 275
- 279, In SubmissionDetailModal, avoid passing an empty string into formatDate
by first checking submission.submissionDate and submission.submittedAt for a
valid value; if neither exists, render a clear fallback (e.g., '—' or 'N/A')
instead of calling formatDate(''), otherwise call formatDate with the chosen
timestamp. Update the JSX around formatDate(...) to use this guarded value
(referencing submission.submissionDate, submission.submittedAt and formatDate)
so the UI shows the fallback when both timestamps are missing.

setSelectedCategory,
} = useSubmissions();

console.log({ submissions });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug console.log before merging.

This debug statement will execute on every render in production, polluting the browser console. Remove it or, if logging is needed for observability, use a conditional logger that respects environment (e.g., only logs in development).

🧹 Proposed fix
-  console.log({ submissions });
📝 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
console.log({ submissions });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/hackathons/submissions/submissionTab.tsx` at line 84, Remove the
debug console.log by deleting the console.log({ submissions }) statement in the
submissionTab.tsx render path (or replace it with a conditional/dev-only
logger), so that the variable submissions is not logged on every render in
production; locate the console.log call inside the SubmissionTab/component
render and either remove it or wrap it with a guard like a development-only
check or use your app's logger that respects NODE_ENV.

Comment on lines +58 to +64
id?: string;
username?: string | null;
name?: string | null;
email?: string | null;
image?: string | null;
profile?: UserProfile;
role?: string;
profile?: UserProfile | import('@/lib/api/types').GetMeResponse | null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the relevant files
fd -t f "(types|auth|navbar)" | grep -E "\.(ts|tsx)$" | head -20

Repository: boundlessfi/boundless

Length of output: 782


🏁 Script executed:

# First, let's look at the navbar.tsx file to see the code in context
wc -l components/landing-page/navbar.tsx

Repository: boundlessfi/boundless

Length of output: 103


🏁 Script executed:

# Read the relevant sections
echo "=== Lines 50-75 (around type definition) ==="
sed -n '50,75p' components/landing-page/navbar.tsx

echo ""
echo "=== Lines 345-360 (profile.firstName access) ==="
sed -n '345,360p' components/landing-page/navbar.tsx

echo ""
echo "=== Lines 390-405 (profile.image access) ==="
sed -n '390,405p' components/landing-page/navbar.tsx

Repository: boundlessfi/boundless

Length of output: 2070


🏁 Script executed:

# Now let's check the GetMeResponse type definition
echo "=== Searching for GetMeResponse definition ==="
rg -n "interface GetMeResponse|type GetMeResponse" lib/api/types.ts lib/auth/types.ts -A 15

Repository: boundlessfi/boundless

Length of output: 878


🏁 Script executed:

# Let's check all accesses to user.profile in the component
echo "=== All profile accesses in navbar.tsx ==="
rg -n "profile\?" components/landing-page/navbar.tsx | head -20

Repository: boundlessfi/boundless

Length of output: 305


🏁 Script executed:

# Check the useAuthStatus hook to see what type it returns
echo "=== useAuthStatus implementation ==="
cat -n hooks/use-auth.ts | head -50

Repository: boundlessfi/boundless

Length of output: 1967


🏁 Script executed:

# Let's verify the complete useAuthStatus return type
echo "=== Complete useAuthStatus hook ==="
wc -l hooks/use-auth.ts
echo ""
sed -n '1,100p' hooks/use-auth.ts

Repository: boundlessfi/boundless

Length of output: 2720


🏁 Script executed:

# Get the rest of useAuthStatus
sed -n '100,181p' hooks/use-auth.ts

Repository: boundlessfi/boundless

Length of output: 1999


🏁 Script executed:

# Let's verify what the actual runtime shape would be
echo "=== Checking if GetMeResponse and UserProfile have compatible shapes ==="
echo "GetMeResponse fields:"
rg "firstName|lastName|image|username|isVerified" lib/api/types.ts -B2 -A1 | grep -A30 "export interface GetMeResponse"

echo ""
echo "UserProfile fields from navbar.tsx:"
sed -n '52,55p' components/landing-page/navbar.tsx

Repository: boundlessfi/boundless

Length of output: 276


🏁 Script executed:

# Let's verify the actual type that's being set in the hook vs what's declared in navbar.tsx
echo "=== Type mismatch analysis ==="
echo "In hooks/use-auth.ts, userProfile is typed as:"
sed -n '15p' hooks/use-auth.ts

echo ""
echo "And it's assigned to profile as:"
sed -n '27p' hooks/use-auth.ts

echo ""
echo "But in navbar.tsx, User.profile is typed as:"
sed -n '64p' components/landing-page/navbar.tsx

echo ""
echo "=== Checking if UserProfile is used anywhere else ==="
rg "UserProfile" components/landing-page/navbar.tsx
rg "UserProfile" hooks/use-auth.ts

Repository: boundlessfi/boundless

Length of output: 838


🏁 Script executed:

# Final verification: confirm no runtime code path provides UserProfile
echo "=== Checking all places where profile is assigned ==="
rg -n "profile.*=" hooks/use-auth.ts lib/api/auth.ts | grep -v "userProfile" | grep -v "//"

Repository: boundlessfi/boundless

Length of output: 468


Remove unused UserProfile from the profile type union.

Line 64 declares profile?: UserProfile | import('@/lib/api/types').GetMeResponse | null, but useAuthStatus() always provides a GetMeResponse | null (from getMe()). The UserProfile type is never assigned at runtime and creates misleading type information. Simplify to profile?: import('@/lib/api/types').GetMeResponse | null.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/landing-page/navbar.tsx` around lines 58 - 64, The profile prop's
type union includes an unused UserProfile type which is never produced at
runtime by useAuthStatus()/getMe(); update the declaration in navbar.tsx to
remove UserProfile and make profile?: import('@/lib/api/types').GetMeResponse |
null so the type reflects actual runtime values (locate the profile?:
UserProfile | import('@/lib/api/types').GetMeResponse | null line and replace
the union to only include GetMeResponse | null).

Comment on lines +26 to +29
console.log({ exploreSubmissions });
const submissions = useMemo(() => {
if (isOrganizer) return allSubmissions;

console.log({ allSubmissions });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statements before merging.

Multiple debug statements will execute on renders and memo recomputations, polluting the browser console in production. Remove all three occurrences (lines 26, 29, and 89).

🧹 Proposed fix
   const allSubmissions =
     exploreSubmissions.length > 0 ? exploreSubmissions : privateSubmissions;
-  console.log({ exploreSubmissions });
   const submissions = useMemo(() => {
     if (isOrganizer) return allSubmissions;
-    console.log({ allSubmissions });
     let filtered = allSubmissions;

Also remove line 89:

   const filteredAndSortedSubmissions = useMemo(() => {
-    console.log({ submissions });
     let filtered = submissions;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hackathon/use-submissions.ts` around lines 26 - 29, Remove the leftover
debug console.log statements in this hook: delete the console.log({
exploreSubmissions }) and console.log({ allSubmissions }) that run around the
submissions useMemo (and also remove the third debug console.log mentioned later
in the file). Locate the submissions useMemo block (references: submissions,
isOrganizer, allSubmissions) and any other console.log calls in this file (e.g.,
the extra one near the end of the hook) and delete them so no debug logging runs
during renders.

Comment on lines +97 to +100
submissionDate:
participant.submission.submissionDate ??
participant.submission.submittedAt ??
'',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid using empty string as a date sentinel in transformed submission data.

Returning '' for submissionDate propagates invalid date parsing to consumers. Prefer an optional date field and omit it when absent.

💡 Suggested fix
 export interface SubmissionData {
@@
-  submissionDate: string;
+  submissionDate?: string;
@@
-    submissionDate:
-      participant.submission.submissionDate ??
-      participant.submission.submittedAt ??
-      '',
+    submissionDate:
+      participant.submission.submissionDate ??
+      participant.submission.submittedAt,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/use-participant-submission.ts` around lines 97 - 100, The code sets
submissionDate to '' which is an invalid sentinel; update the mapping in the
useParticipantSubmission hook (the transformed object with submissionDate) to
return undefined when no date exists: use participant.submission.submissionDate
?? participant.submission.submittedAt ?? undefined instead of ''. Also update
the related return type/interface for useParticipantSubmission (or the
transformed submission type) so submissionDate is optional/nullable (e.g.,
string | undefined or Date | undefined) and consumers handle the absent field
rather than an empty string.

Comment thread lib/api/api.ts
export interface RequestConfig {
headers?: Record<string, string>;
timeout?: number;
signal?: AbortSignal;
Copy link
Copy Markdown

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
set -euo pipefail

# First, let's see the structure and find the exact lines mentioned
echo "=== Checking file info ==="
wc -l lib/api/api.ts

echo -e "\n=== Line 35 context ==="
sed -n '30,40p' lib/api/api.ts

echo -e "\n=== Lines 94-122 context (retry interceptor) ==="
sed -n '94,122p' lib/api/api.ts

echo -e "\n=== Lines 175-179 context (convertRequestConfig) ==="
sed -n '175,185p' lib/api/api.ts

echo -e "\n=== Search for signal/abort handling ==="
rg -n "signal|_retryCount|retry-after|ERR_CANCELED|aborted" lib/api/api.ts || echo "No matches found"

Repository: boundlessfi/boundless

Length of output: 2069


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Full error handler (lines 94-130 or more if needed) ==="
sed -n '94,140p' lib/api/api.ts

echo -e "\n=== Check for ERR_CANCELED anywhere in file ==="
rg "ERR_CANCELED|code.*CANCELED" lib/api/api.ts || echo "No ERR_CANCELED found"

echo -e "\n=== Check for any .aborted checks ==="
rg "\.aborted|signal\?.aborted" lib/api/api.ts || echo "No .aborted checks found"

echo -e "\n=== Check if signal is used anywhere else ==="
rg -A2 -B2 "signal" lib/api/api.ts | head -50

Repository: boundlessfi/boundless

Length of output: 2131


Abort support is incomplete: 429 retry flow ignores cancellation.

With Line 35/178 adding signal, canceled requests can still sleep and retry in the 429 interceptor. The current implementation has no abort checks before or after the delay, and no ERR_CANCELED handling, defeating user cancellation intent.

Proposed fix
+const waitWithAbort = (ms: number, signal?: AbortSignal): Promise<void> =>
+  new Promise((resolve, reject) => {
+    const timeout = setTimeout(resolve, ms);
+    if (!signal) return;
+    const onAbort = () => {
+      clearTimeout(timeout);
+      reject(new Error('Request aborted'));
+    };
+    if (signal.aborted) onAbort();
+    else signal.addEventListener('abort', onAbort, { once: true });
+  });

   if (error.response?.status === 429) {
     const retryCount = originalRequest._retryCount || 0;
     const maxRetries = 3;

     if (retryCount < maxRetries) {
+      if (originalRequest.signal?.aborted) return Promise.reject(error);
       originalRequest._retryCount = retryCount + 1;
       ...
-      await new Promise(resolve => setTimeout(resolve, delay));
+      await waitWithAbort(delay, originalRequest.signal);
+      if (originalRequest.signal?.aborted) return Promise.reject(error);
       return instance(originalRequest);
     }
   }
+
+  if (error.code === 'ERR_CANCELED') {
+    return Promise.reject(error);
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api/api.ts` at line 35, The 429-retry flow must respect the request
AbortSignal: update the 429 interceptor/handler (the function that reads
response.status === 429 and schedules retry) to check
requestOptions.signal?.aborted before scheduling any sleep and immediately after
the sleep returns, and propagate cancellation by throwing the appropriate abort
error (or rethrowing if you detect ERR_CANCELED). Also update the shared
sleep/delay helper used by that retry flow to accept an AbortSignal and reject
early when signal.aborted (so the delay can be interrupted). Ensure the code
paths reference the request option named signal and handle
ERR_CANCELED/AbortError in the retry catch to stop further retries.

Comment thread lib/api/hackathons.ts
Comment on lines +1746 to +1748
const body = res.data;
if (!body?.data?.submissions) return [];
return body.data.submissions;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t silently convert malformed/failed API responses into empty data.

Line 1747 returns [] for missing envelope data, which can hide backend failures and produce incorrect “no submissions” UI states.

✅ Suggested fix
   const body = res.data;
-  if (!body?.data?.submissions) return [];
-  return body.data.submissions;
+  if (!body?.success) {
+    throw new Error(body?.message || 'Failed to fetch explore submissions');
+  }
+  if (!Array.isArray(body.data?.submissions)) {
+    throw new Error('Invalid explore submissions response shape');
+  }
+  return body.data.submissions;
📝 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
const body = res.data;
if (!body?.data?.submissions) return [];
return body.data.submissions;
const body = res.data;
if (!body?.success) {
throw new Error(body?.message || 'Failed to fetch explore submissions');
}
if (!Array.isArray(body.data?.submissions)) {
throw new Error('Invalid explore submissions response shape');
}
return body.data.submissions;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api/hackathons.ts` around lines 1746 - 1748, The current code in
lib/api/hackathons.ts reads const body = res.data and returns [] when
body?.data?.submissions is missing, silently hiding API failures; change this to
surface the error instead of returning an empty array—check body and res (e.g.,
if (!body || !body.data || !body.data.submissions)) and throw a descriptive
Error (include res.status, res.statusText and JSON-stringified body) or return a
Result/Error object so callers can handle it; update the function that uses
body.data.submissions (reference the local variables body and res and the
consumer of submissions) to expect thrown errors or an error result.

Comment on lines +252 to +260
sub.participant?.name ??
sub.teamName ??
sub.teamMembers?.[0]?.name ??
'Unknown Participant',
submitterAvatar:
sub.participant?.image ??
sub.teamMembers?.[0]?.avatar ??
sub.logo ??
'',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle empty-string fallbacks in submission mapping.

With ??, '' is treated as valid. This can render blank submitter names/avatars/logos instead of intended fallbacks.

Proposed fix
+const firstNonEmpty = (...values: Array<string | null | undefined>) =>
+  values.find(v => typeof v === 'string' && v.trim().length > 0);

       const mappedSubmissions: SubmissionCardProps[] = submissions.map(sub => ({
         _id: sub.id,
         projectName: sub.projectName,
         description: sub.description,
-        submitterName:
-          sub.participant?.name ??
-          sub.teamName ??
-          sub.teamMembers?.[0]?.name ??
-          'Unknown Participant',
-        submitterAvatar:
-          sub.participant?.image ??
-          sub.teamMembers?.[0]?.avatar ??
-          sub.logo ??
-          '',
+        submitterName:
+          firstNonEmpty(
+            sub.participant?.name,
+            sub.teamName,
+            sub.teamMembers?.[0]?.name
+          ) ?? 'Unknown Participant',
+        submitterAvatar:
+          firstNonEmpty(
+            sub.participant?.image,
+            sub.teamMembers?.[0]?.avatar,
+            sub.logo
+          ) ?? '',
         category: sub.category,
         status: mapSubmissionStatus(sub.status),
         upvotes: 0,
-        submittedDate: sub.submittedAt ?? sub.submissionDate ?? '',
-        logo: sub.logo ?? '/placeholder.svg',
+        submittedDate: firstNonEmpty(sub.submittedAt, sub.submissionDate) ?? '',
+        logo: firstNonEmpty(sub.logo) ?? '/placeholder.svg',
       }));

Also applies to: 264-265

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/providers/hackathonProvider.tsx` around lines 252 - 260, The mapping uses
the nullish coalescing operator (??) so empty strings are treated as valid
values and produce blank names/avatars; replace these expressions (e.g.,
sub.participant?.name ?? sub.teamName ?? sub.teamMembers?.[0]?.name ?? 'Unknown
Participant' and the submitterAvatar expression sub.participant?.image ??
sub.teamMembers?.[0]?.avatar ?? sub.logo ?? '') with a small helper that returns
the first non-empty string (for example coalesceNonEmpty(...values) or a utility
that checks value && value.trim() !== ''), then use that helper for both
submitter name and submitter avatar (and any other similar coalescing
expressions) so empty strings fall back to the intended defaults.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant