feat: enhance HackathonPageClient and UI components#435
Conversation
- 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 whenitemsis 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. WhileGetMeResponseandUsertypes declare these as optional arrays, defensive validation withArray.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:webpackscript is identical todev— both executenext dev. If this is a placeholder for future webpack-specific configuration (e.g.,next dev --no-turboor 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.
MeLayoutProfileandprofile as ...introduce drift risk asGetMeResponseevolves. 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 forcnfrom@/lib/utilsand 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.
📒 Files selected for processing (38)
app/(landing)/hackathons/[slug]/HackathonPageClient.tsxapp/(landing)/organizations/[id]/hackathons/[hackathonId]/page.tsxapp/(landing)/profile/[username]/profile-data.tsxapp/globals.cssapp/me/earnings/page.tsxapp/me/hackathons/submissions/page.tsxapp/me/hackathons/submissions/submission-components.tsxapp/me/layout.tsxapp/me/participating/page.tsxcomponents/EmptyState.tsxcomponents/app-sidebar.tsxcomponents/hackathons/ProgressIndicator.tsxcomponents/hackathons/submissions/SubmissionDetailModal.tsxcomponents/hackathons/submissions/submissionTab.tsxcomponents/landing-page/hackathon/HackathonCard.tsxcomponents/landing-page/navbar.tsxcomponents/nav-main.tsxcomponents/nav-user.tsxcomponents/organization/cards/Participant.tsxcomponents/organization/cards/ParticipantSubmission.tsxcomponents/organization/hackathons/details/HackathonSelector.tsxcomponents/profile/ProfileDataClient.tsxcomponents/profile/ProfileOverview.tsxcomponents/profile/PublicEarningsTab.tsxhooks/hackathon/use-hackathon-transform.tshooks/hackathon/use-hackathons-list.tshooks/hackathon/use-organizer-submissions.tshooks/hackathon/use-submissions.tshooks/use-auth.tshooks/use-participant-submission.tslib/api/api.tslib/api/earnings.tslib/api/hackathons.tslib/api/types.tslib/providers/hackathonProvider.tsxpackage.jsontypes/earnings.tstypes/hackathon/core.ts
| {isAuthenticated && isOwnProfile && ( | ||
| <TabsTrigger | ||
| value='organizations' | ||
| className={clsx(TAB_CLASS, 'md:hidden')} | ||
| > | ||
| Organizations | ||
| </TabsTrigger> | ||
| )} |
There was a problem hiding this comment.
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.
| {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.
| className='flex items-center gap-2 rounded-lg border border-white/5 bg-white/3 px-4 py-2 text-sm' | ||
| > |
There was a problem hiding this comment.
🧩 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"
fiRepository: 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 -50Repository: 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 2Repository: 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.
| <span> | ||
| Submitted:{' '} | ||
| {formatDate( | ||
| submission.submissionDate ?? submission.submittedAt ?? '' | ||
| )} |
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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.
| 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.
| 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; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the relevant files
fd -t f "(types|auth|navbar)" | grep -E "\.(ts|tsx)$" | head -20Repository: 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.tsxRepository: 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.tsxRepository: 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 15Repository: 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 -20Repository: 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 -50Repository: 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.tsRepository: boundlessfi/boundless
Length of output: 2720
🏁 Script executed:
# Get the rest of useAuthStatus
sed -n '100,181p' hooks/use-auth.tsRepository: 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.tsxRepository: 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.tsRepository: 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).
| console.log({ exploreSubmissions }); | ||
| const submissions = useMemo(() => { | ||
| if (isOrganizer) return allSubmissions; | ||
|
|
||
| console.log({ allSubmissions }); |
There was a problem hiding this comment.
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.
| submissionDate: | ||
| participant.submission.submissionDate ?? | ||
| participant.submission.submittedAt ?? | ||
| '', |
There was a problem hiding this comment.
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.
| export interface RequestConfig { | ||
| headers?: Record<string, string>; | ||
| timeout?: number; | ||
| signal?: AbortSignal; |
There was a problem hiding this comment.
🧩 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 -50Repository: 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.
| const body = res.data; | ||
| if (!body?.data?.submissions) return []; | ||
| return body.data.submissions; |
There was a problem hiding this comment.
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.
| 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.
| sub.participant?.name ?? | ||
| sub.teamName ?? | ||
| sub.teamMembers?.[0]?.name ?? | ||
| 'Unknown Participant', | ||
| submitterAvatar: | ||
| sub.participant?.image ?? | ||
| sub.teamMembers?.[0]?.avatar ?? | ||
| sub.logo ?? | ||
| '', |
There was a problem hiding this comment.
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.
Summary by CodeRabbit
Bug Fixes
Improvements
Style