Skip to content

Conversation

@0xdevcollins
Copy link
Collaborator

@0xdevcollins 0xdevcollins commented Feb 11, 2026

Summary by CodeRabbit

  • New Features

    • Rich participant management: table/grid views, toolbar, filters, pagination, and new grid/table components.
    • Expanded submission flow: team participation, create/invite workflow, and an expandable submission screen for submissions.
    • Improved review experience: animated tabbed reviewer modal with Details/Team/Links/Voters and enhanced voters UI and counts.
  • Bug Fixes

    • Funding inputs clear validation errors on edit.
    • Voting failures now show user-facing error toasts and gated voting for owners/team members.
  • Refactor

    • Simplified hackathon publish flow to a single backend publish action.

…s, dedicated UI components like Family Wallet, and integration across the application.
…ation, and improved button state handling for connected/disconnected wallets.
…atus check from 'Funding' to 'CAMPAIGNING'.
… the `WalletTrigger`, alongside minor landing page styling adjustments and code cleanup.
…n projects, refactor vote submission logic, and improve funding modal error handling.
@vercel
Copy link

vercel bot commented Feb 11, 2026

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

Project Deployment Actions Updated (UTC)
boundless-kd16 Ready Ready Preview, Comment Feb 12, 2026 2:19am

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds vote gating and toast error handling, passes crowdfund into voter UI, clears funding-modal validation on input edit, replaces client-side escrow with a backend publish call, introduces participant toolbar/table/grid and expandable submission flows, and migrates many motion imports.

Changes

Cohort / File(s) Summary
Voting & Sidebar
components/project-details/project-voters/index.tsx, components/project-details/project-voters/Empty.tsx, components/project-details/project-sidebar/index.tsx
Adds auth/owner/team gating (isGated), extends ProjectVoters props to accept crowdfund, disables vote controls when gated, replaces voteProject with createVote, and surfaces toast errors; Empty gains isGated prop and conditional UI.
Project Layout
components/project-details/project-layout.tsx
Passes crowdfund={crowdfund} to ProjectVoters in mobile and desktop render paths.
Funding Modal
components/project-details/funding-modal.tsx
Input onChange handlers for Amount and Message now clear existing validation errors when edited.
Hackathon Publish
hooks/use-hackathon-publish.ts
Removes client-side escrow orchestration and related imports; replaces with single publishDraftAction(draftId, organizationId) backend call and simpler error/state handling; adds draftId validation.
Participants & Participant UI
app/.../participants/page.tsx, components/organization/hackathons/ParticipantToolbar.tsx, components/organization/hackathons/ParticipantsGrid.tsx, components/organization/hackathons/ParticipantsTable.tsx
Rewrites participants page: adds toolbar (search/filters/view), grid and table components, pagination integration, server-driven stats, and modal-driven actions (review, grade, view team).
Submission Flow & Tabs
components/hackathons/submissions/SubmissionForm.tsx, components/hackathons/submissions/submissionTab.tsx
Replaces modal-based submission with ExpandableScreen/SubmissionScreenWrapper, adds team participation flow (team creation/invites), new form fields/validation, and refactors submission tab to wrapper/content pattern.
Team Hooks & Posts
hooks/hackathon/use-team-invite.ts, hooks/hackathon/use-team-posts.ts, components/hackathons/team-formation/*.tsx
Adds useTeamInvite (create team + invite flow), useTeamPosts exposes isLoadingMyTeam and drops organizationId from some calls; components updated accordingly.
Expandable Screen UI
components/ui/expandable-screen.tsx
New controlled ExpandableScreen with context hook, Trigger/Content/Background primitives, shared-layout morph animations, optional scroll-lock, and exported types/hooks.
Review Submission Modal & Tabs
components/organization/cards/ReviewSubmissionModal/*.tsx
Major UI refactor: animated tabbed modal (Details, Team, Links, Voters), motion-based transitions, header redesign (share/report), voters and team UI updates, and submission shape adjustments (video/introduction fields added/removed in places).
Submission Action Buttons
components/organization/cards/SubmissionActionButtons.tsx
Replaced button primitives/icons, added onShortlist prop, adjusted layout and destructive styling for Disqualify.
Participant Submission Utils & API types
hooks/use-participant-submission.ts, lib/api/hackathons.ts
Extracts transformParticipantToSubmission and exported SubmissionData; CreateSubmissionRequest.teamMembers now allows userId?: string and email?: string.
Stepper & Pagination tweaks
components/stepper/Stepper.tsx, components/ui/data-table-pagination.tsx
Active step title adds text-white; page size options now include 12.
Types & Form Data
features/projects/types/index.ts, hooks/hackathon/use-submission.ts
TeamMember adds optional id?: string; SubmissionFormData extended with optional teamName and teamMembers.
Motion import migrations & misc UI tweaks
multiple components/*, lib/motion.ts, package.json
Switched many motion imports from framer-motion to motion/react, removed framer-motion dependency and bumped motion version; several UI tweaks (iframe border removal, icon/class changes).
CI workflow
.github/workflows/ci.yml
Large CI jobs commented out, leaving Code Quality and Commit Message Validation active; many build/test/deploy jobs disabled as comments.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as ProjectVoters UI
    participant Auth as useOptionalAuth
    participant API as createVote API
    participant Toast as Toast Notification
    participant State as UI State

    User->>UI: Click upvote/downvote
    UI->>Auth: Request user context
    Auth-->>UI: Return user or null
    UI->>State: Compute isGated (owner/team member?)
    alt isGated = true
        UI-->>User: Disable vote controls / show gated notice
    else isGated = false
        UI->>API: createVote({ projectId, entityType, voteType })
        API-->>UI: success / error
        alt success
            UI->>State: Update vote counts/UI
        else error
            UI->>Toast: Show error message
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feat/abstract wallet #379: Directly related — similar refactor in hooks/use-hackathon-publish.ts replacing client-side escrow steps with a backend publish action.
  • Feat/abstract wallet #377: Related — touches components/project-details/funding-modal.tsx and funding input/validation flows.
  • Fix hackathon #374: Related — overlaps team-invite APIs and client hooks used by the new useTeamInvite flow.

Poem

🐰 I hopped through props and gates so wide,

I nudged the errors till they had to hide,
Votes now check who owns the patch,
One backend publish — no more escrow batch,
Expandable screens make submissions glide.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'Feat/minor ux fixes' is vague and generic, using non-descriptive terms that don't convey meaningful information about the actual changes made. Replace with a specific, concise title summarizing the main change. For example: 'Add validation error clearing, gated voting, and expandable submission form' or identify the primary feature being introduced.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/minor-ux-fixes

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

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

🤖 Fix all issues with AI agents
In `@components/project-details/project-voters/index.tsx`:
- Around line 362-363: The Empty branch for when voters.length === 0 renders an
Upvote button but doesn't receive the upvote handler; update the JSX that
renders Empty (the line with Empty and props project?.status and isGated) to
pass the component's existing upvote handler (e.g., onVote or handleVote) via an
onVote prop so clicking the button triggers the same upvote logic used elsewhere
in this component.
- Around line 50-57: The code casts crowdfund.team to any[] because TeamMember
lacks an id field used in the isTeamMember check; fix by updating the TeamMember
type in features/projects/types/index.ts to include id?: string (or string) so
the existing check (member.id === user?.id) is properly typed, then remove the
as any[] cast in project-voters/index.ts and keep the isTeamMember logic using
member.id and/or member.username as currently written.
🧹 Nitpick comments (2)
components/project-details/project-voters/Empty.tsx (1)

6-12: projectStatus prop is declared but unused.

projectStatus is defined in EmptyProps (line 7) and passed from the parent (line 363 of index.tsx), but it's not destructured or referenced in the component body. Consider removing it from the interface if it's no longer needed, or destructure it if it should influence rendering.

♻️ Proposed cleanup
 interface EmptyProps {
-  projectStatus: string;
   onVote?: () => void;
   isGated?: boolean;
 }
components/project-details/project-voters/index.tsx (1)

463-474: style jsx block uses plain CSS instead of TailwindCSS.

This is pre-existing, but per coding guidelines, TailwindCSS classes should be used for styling. The fadeInUp animation could be replaced with Tailwind's animate- utilities or a custom animation in tailwind.config. Low priority since it's not part of this PR's changes.

…able/grid views, filtering, pagination, and an enhanced submission review experience.
Copy link

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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (7)
app/(landing)/hackathons/[slug]/page.tsx (2)

52-58: ⚠️ Potential issue | 🟡 Minor

Badge count will cap at 100 due to pagination limit.

discussionComments.comments.length (used at Line 121 for the tab badge) will never exceed 100 now. If the hackathon has more than 100 comments, the badge will be misleading. Consider using a totalCount field from the API response (if available) instead of relying on the array length of a paginated result.

#!/bin/bash
# Check if useCommentSystem returns a totalCount or similar field
ast-grep --pattern $'useCommentSystem($$$)'
rg -n 'totalCount\|total_count\|totalComments\|count' --type=ts --type=tsx -g '*comment*'

367-401: ⚠️ Potential issue | 🟡 Minor

<HackathonResources /> renders twice when the resources tab is active.

Lines 367-368 and Lines 399-401 both render <HackathonResources /> when activeTab === 'resources' and resources exist. The two conditions (resources?.length > 0 vs resources?.[0]) are effectively equivalent, causing a duplicate mount. One of these blocks should be removed.

🐛 Suggested fix — remove the duplicate block
-            {activeTab === 'resources' && currentHackathon?.resources?.[0] && (
-              <HackathonResources />
-            )}
hooks/use-hackathons.ts (1)

231-231: ⚠️ Potential issue | 🟡 Minor

Remove console.trace — debug artifact left in production code.

This will dump a full stack trace to the console on every hackathon fetch, which is noisy for users and leaks internal call-site information.

Proposed fix
-        console.trace('response.data', response.data?.hackathons);
hooks/hackathon/use-team-posts.ts (1)

118-139: ⚠️ Potential issue | 🟡 Minor

organizationId is in the dependency array but not passed to getMyTeam.

Line 126 calls getMyTeam(hackathonSlugOrId) without organizationId, yet organizationId remains in the useCallback dependency array (line 139). This causes unnecessary callback re-creation without changing behavior.

If dropping organizationId from the call is intentional, also remove it from the dependency array:

🔧 Suggested fix
-  }, [hackathonSlugOrId, organizationId, isAuthenticated]);
+  }, [hackathonSlugOrId, isAuthenticated]);

If the organization-scoped endpoint is still needed for some users, pass organizationId to the call.

package.json (1)

109-117: ⚠️ Potential issue | 🟡 Minor

Remove unused motion dependency or complete migration to it.

The codebase imports from framer-motion in 31 files but doesn't import from motion anywhere, even though both are listed in package.json. motion (v12+) is the successor to framer-motion and appears unused. Either remove the motion dependency to eliminate bloat, or migrate all imports from framer-motion to motion/react and remove framer-motion.

components/hackathons/submissions/SubmissionForm.tsx (2)

855-942: ⚠️ Potential issue | 🔴 Critical

Critical bug: duplicate case 1: — Media & Links step never renders.

The renderStepContent switch has two case 1: labels (Lines 855 and 941). JavaScript/TypeScript only matches the first, so the second block (Media & Links: logo, video, links) is unreachable dead code. Additionally, case 2: (Line 1124) renders Review, but with 4 steps (indices 0–3), Review should be case 3:.

This was likely caused by inserting the new Participation step at index 0 without incrementing the subsequent case numbers.

🐛 Fix: renumber the cases
-      case 1:
+      case 2:
         return (
-          <div key='step-1' className='space-y-6'>
+          <div key='step-2' className='space-y-6'>
             <FormField
               control={form.control}
               name='logo'
-      case 2:
+      case 3:
         return (
           <div className='space-y-6'>
             <div className='rounded-lg border border-gray-700 bg-gray-800/50 p-6'>
               <h3 className='mb-4 text-lg font-semibold text-white'>
                 Review Your Submission

556-565: ⚠️ Potential issue | 🟠 Major

Validation failure navigates to the wrong step.

On submission validation failure, setCurrentStep(0) sends the user to the Participation step, not Basic Info where the required fields (projectName, category, description) live. This should be setCurrentStep(1).

🐛 Proposed fix
       if (
         !isValid ||
         !safeData.projectName ||
         !safeData.category ||
         !safeData.description
       ) {
         toast.error('Please fill in all required fields');
-        setCurrentStep(0);
-        updateStepState(0, 'active');
+        setCurrentStep(1);
+        updateStepState(1, 'active');
         return;
       }
🤖 Fix all issues with AI agents
In
`@app/`(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx:
- Around line 336-340: The currentIndex prop calculation uses
participants.filter(...).findIndex(...) || 0 which fails to guard against
findIndex returning -1; instead compute the index into the filtered array (e.g.,
let idx = participants.filter(p => !!p.submission).findIndex(p => p.id ===
selectedParticipant.id)) and pass a non-negative value to ReviewSubmissionModal
by using idx === -1 ? 0 : idx (or Math.max(idx, 0)); update the currentIndex
expression to use that guarded value so a negative index is never passed.

In `@components/hackathons/submissions/SubmissionForm.tsx`:
- Around line 168-175: The hardcoded "const open = true" makes the "!open"
branch in the effect unreachable; replace it by wiring open to the actual
expandable state from useExpandableScreen (e.g., derive an "expanded" or
"isOpen" value from useExpandableScreen and set const open = expanded/isOpen) so
the effect runs on open/close, or remove the unused "open" variable and delete
the entire "!open" effect branch (the reset/fetch logic) if closing behavior
should not be handled here; update references to open in the effect that resets
the form/fetches submission data (the useEffect that currently checks !open) and
to any cleanup logic in this component (e.g.,
SubmissionForm/ExpandableScreenContent related code).

In `@components/organization/cards/ReviewSubmissionModal.tsx`:
- Line 109: In ReviewSubmissionModal, the wrapper div rendering inside the
component contains an invalid Tailwind class "h-"; update that class to a valid
utility (likely "h-full") on the div with className 'h- flex w-full flex-col' so
the container gains the intended height constraint and tailwind styles apply
correctly.

In
`@components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx`:
- Around line 50-52: The handleReport function in SubmissionModalHeader
currently only shows a success toast and performs no reporting action; replace
the misleading no-op by either wiring it to the real report flow (call the
report API or dispatch reportSubmission with the submission id, then on success
call toast.success and on failure toast.error) or explicitly disable the report
menu item and add a TODO comment in handleReport indicating the feature is
unimplemented; update the MenuItem that uses handleReport to reflect the
disabled state or call the real handler so users aren’t shown a false success
message.
- Around line 99-109: The progress-dot highlight breaks when currentIndex >= 5
because the dots are rendered with Array.from({ length:
Math.min(totalSubmissions, 5) }) but the active check uses i === currentIndex;
update the logic to compute a visibleIndex (e.g., const visibleCount =
Math.min(totalSubmissions, 5); const activeIndex = Math.min(currentIndex,
visibleCount - 1)) and use that in the active class condition inside the map
(replace i === currentIndex with i === activeIndex), or implement a
sliding-window calculation if you want the active dot to shift within the 5
visible dots.

In `@components/organization/cards/ReviewSubmissionModal/TeamSection.tsx`:
- Line 8: This file imports the motion symbol from 'framer-motion' while the
project is migrating to 'motion/react' and mixing both libs risks bundle
duplication; update the import in TeamSection.tsx to import { motion } from
'motion/react' (matching the other components ui/multi-step-loader.tsx,
ui/glowing-effect.tsx, ui/expandable-screen.tsx) and then run a quick grep
across the codebase to convert remaining 'framer-motion' imports to
'motion/react' (or, if you decide to remove the old package, remove
'framer-motion' from package.json and consolidate all imports to 'motion/react'
before reinstalling).

In `@components/organization/hackathons/ParticipantsGrid.tsx`:
- Around line 35-41: The component is declared with a function declaration;
change it to a const arrow function with an explicit type annotation. Replace
the "export function ParticipantsGrid(...)" declaration by exporting a const
named ParticipantsGrid typed as React.FC<ParticipantsGridProps> (or equivalent
explicit component type) and assign it an arrow function that takes the same
props ({ data, onReview, onViewTeam, onGrade, loading }) and returns the same
body so behavior is unchanged.

In `@components/organization/hackathons/ParticipantToolbar.tsx`:
- Around line 39-43: The reset button doesn't update the Select dropdowns
because the Selects use defaultValue (uncontrolled); update ParticipantToolbar
by adding local state variables (e.g., statusFilter and typeFilter initialized
to 'all') and use those as the Selects' value props, update them in the Select
onChange handlers (and still call onStatusFilterChange/onTypeFilterChange), and
modify handleReset to setSearchValue(''), set statusFilter('all') and
typeFilter('all') and call the parent callbacks so both UI and parent state stay
in sync (refer to handleReset, setSearchValue, onStatusFilterChange,
onTypeFilterChange and the Select components).
- Around line 25-31: Replace the current function declaration for
ParticipantToolbar with a const arrow function using an explicit type
annotation; locate the declaration "export function ParticipantToolbar({ ... }:
ParticipantToolbarProps)" and change it to an exported const like "export const
ParticipantToolbar: React.FC<ParticipantToolbarProps> = ({ ... }) => { ... }"
(or use React.FunctionComponent) so it follows the coding guideline preferring
const arrow functions with explicit types.

In `@components/ui/expandable-screen.tsx`:
- Around line 71-79: The effect in expandable-screen.tsx using useEffect with
dependencies [isExpanded, lockScroll] sets document.body.style.overflow but
doesn’t restore it on unmount; update the effect to return a cleanup function
that restores the body's overflow (preferably to the previous value captured
when the effect runs) and also ensure that when lockScroll is false you don't
modify overflow; reference the existing useEffect, lockScroll, isExpanded, and
document.body.style.overflow to implement the cleanup so the body scroll is
always restored when the component unmounts or when isExpanded/lockScroll
change.

In `@hooks/hackathon/use-team-invite.ts`:
- Around line 17-21: Convert the hook declaration useTeamInvite from a function
declaration to a const arrow function with an explicit type annotation for its
parameters and return type (keep the existing UseTeamInviteOptions type for the
parameter). Replace "export function useTeamInvite({ ... }:
UseTeamInviteOptions) {" with a const-exported arrow: "export const
useTeamInvite = ({ ... }: UseTeamInviteOptions): ReturnType => {" and ensure the
function body is closed with "};" (preserve all existing internal logic and
callbacks such as onSuccess).
- Around line 70-74: The success toast is shown unconditionally after awaiting
Promise.all(invitePromises) even when some invites failed; change the logic in
the block that awaits invitePromises/newTeamId to detect invite failures (use
Promise.allSettled(invitePromises) or have each invite return a success flag
instead of null) and count failed invites, then: only call toast.success('Team
created successfully!') and onSuccess?.(newTeamId) when failures === 0; if some
invites failed, call an appropriate toast (toast.error or toast.info) that
describes partial success and include the number or details of failed invites so
the user isn’t shown both error and success messages.

In `@hooks/use-hackathons.ts`:
- Line 34: The type change to participantFilters.status caused a mismatch:
either revert the union back to include 'not_submitted' or update all consumers
to the new values ('shortlisted'/'disqualified'); specifically either re-add
'not_submitted' to the status type in use-hackathons.ts or update
components/hackathons/participants/participantFilter.tsx (the 'Not Submitted'
option), hooks/hackathon/use-participants.ts (the submissionFilter checks), and
the API types in lib/api/hackathons.ts and lib/api/hackathons/participants.ts to
use the new statuses consistently; also remove the stray console.trace call
found in the same area (console.trace) before merging.

In `@lib/api/hackathons.ts`:
- Line 2511: Remove the debug console.trace call that leaks full stack and
response data; locate the call inside the getMyTeam handler/function in
lib/api/hackathons.ts (the line with console.trace(res)) and delete it or
replace it with a safe log that does not print sensitive response content (e.g.,
a sanitized/processLogger.debug message or no-op), ensuring no raw API response
is written to the browser console.
🟡 Minor comments (10)
components/organization/cards/ReviewSubmissionModal/SubmissionDetailsTab.tsx-14-18 (1)

14-18: ⚠️ Potential issue | 🟡 Minor

introduction is destructured but never used in the JSX.

It's listed in the interface (line 10) and destructured (line 16) but not rendered anywhere in the template. If it was intentionally removed from the UI, clean up the destructuring (and consider removing it from the interface). If it was accidentally dropped, it should be rendered — the old code likely displayed it.

Proposed fix (if intentionally unused)
 export const SubmissionDetailsTab: React.FC<SubmissionDetailsTabProps> = ({
   projectName,
-  introduction,
   description,
 }) => {
components/stepper/Stepper.tsx-29-29 (1)

29-29: ⚠️ Potential issue | 🟡 Minor

Conflicting Tailwind color classes: text-card and text-white.

Both text-card and text-white set the CSS color property. The winning value depends on Tailwind's generated stylesheet order, not the order in the class string. If the intent is white text for the active step, remove text-card; otherwise the result is non-deterministic.

🔧 Suggested fix
-          title: 'text-card text-white',
+          title: 'text-white',
components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx-32-32 (1)

32-32: ⚠️ Potential issue | 🟡 Minor

submissionName prop is accepted but never used.

The prop is declared in the interface (line 32) and destructured (line 43) but never rendered or consumed anywhere in the component. Either use it (e.g., display it in the header) or remove it to avoid confusion.

Also applies to: 43-43

components/organization/cards/ReviewSubmissionModal/SubmissionInfo.tsx-104-106 (1)

104-106: ⚠️ Potential issue | 🟡 Minor

Potentially misleading + suffix on exact comment count.

{submission.comments.toLocaleString()}+ always appends a +, even when the count is exact (e.g., 0+ or 3+). If the API returns the full count, this implies there are more than shown. Consider removing the + or only showing it when the count is actually truncated/capped.

components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx-45-48 (1)

45-48: ⚠️ Potential issue | 🟡 Minor

handleShare doesn't handle clipboard API failure.

navigator.clipboard.writeText returns a Promise and can fail (e.g., insecure context, permission denied). The function should await it and handle errors.

Proposed fix
-  const handleShare = () => {
-    navigator.clipboard.writeText(window.location.href);
-    toast.success('Link copied to clipboard');
+  const handleShare = async () => {
+    try {
+      await navigator.clipboard.writeText(window.location.href);
+      toast.success('Link copied to clipboard');
+    } catch {
+      toast.error('Failed to copy link');
+    }
   };
components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx-83-92 (1)

83-92: ⚠️ Potential issue | 🟡 Minor

Undefined voteType renders as a negative (red, rotated) vote.

voteType is optional in the Voter interface (line 16), but the styling defaults to the negative branch (red + rotate-180) when it's undefined. This means voters without a voteType look like they downvoted. Consider handling the undefined case explicitly:

Proposed fix
                 <ThumbsUp
                   className={cn(
                     'h-4 w-4 transition-transform group-hover:scale-110',
-                    voter.voteType === 'positive'
-                      ? 'text-green-500'
-                      : 'rotate-180 text-red-500'
+                    voter.voteType === 'positive' ? 'text-green-500'
+                      : voter.voteType === 'negative' ? 'rotate-180 text-red-500'
+                      : 'text-gray-500'
                   )}
                 />
components/organization/hackathons/ParticipantsGrid.tsx-208-243 (1)

208-243: ⚠️ Potential issue | 🟡 Minor

Empty footer for "Registered" participants with no submission.

When a participant has no submission and is not on a team, none of the footer buttons render, leaving an empty CardFooter with visible borders. Consider either hiding the footer entirely or showing a disabled/placeholder action.

Option: conditionally render the footer
-            <CardFooter className='bg-background/20 mt-auto flex gap-2 border-t border-gray-900/60 p-3'>
+            {(isTeam || hasSubmission) && (
+            <CardFooter className='bg-background/20 mt-auto flex gap-2 border-t border-gray-900/60 p-3'>
               {/* ... buttons ... */}
             </CardFooter>
+            )}
components/hackathons/submissions/SubmissionForm.tsx-227-232 (1)

227-232: ⚠️ Potential issue | 🟡 Minor

Duplicate useEffect for auto-selecting TEAM participation.

Lines 227–232 and 607–611 contain identical effects that set participationType to 'TEAM' when myTeam exists. Only one is needed.

🔧 Remove the duplicate
-  // Auto-select TEAM if user is already in a team
-  useEffect(() => {
-    if (myTeam && !submissionId) {
-      form.setValue('participationType', 'TEAM');
-    }
-  }, [myTeam, form, submissionId]);
-

Keep only the first one (Lines 227–232).

Also applies to: 607-611

components/organization/cards/ReviewSubmissionModal/TeamSection.tsx-67-70 (1)

67-70: ⚠️ Potential issue | 🟡 Minor

Social icons are non-functional — misleading clickable appearance.

The Github and Twitter icons have cursor-pointer and hover effects but no onClick handler or navigation target. The TeamMember interface has no social link fields, so these are purely decorative yet appear interactive.

Either wire them to actual data (add social fields to TeamMember) or remove the interactive styling.

components/organization/hackathons/ParticipantsTable.tsx-136-145 (1)

136-145: ⚠️ Potential issue | 🟡 Minor

format(new Date(date), ...) can throw on invalid dates.

If registeredAt is null, undefined, or an invalid date string, new Date(date) produces Invalid Date and date-fns format will throw a RangeError.

🛡️ Suggested safe formatting
         cell: ({ row }) => {
           const date = row.getValue('registeredAt') as string;
+          if (!date) return <span className='text-sm text-gray-400'>—</span>;
           return (
             <span className='text-sm text-gray-400'>
               {format(new Date(date), 'MMM d, yyyy')}
             </span>
           );
         },
🧹 Nitpick comments (16)
components/organization/cards/SubmissionActionButtons.tsx (1)

9-12: Prefer a const arrow function with explicit return type.

Per coding guidelines, const arrow functions with explicit type annotations are preferred over function declarations for .tsx files.

♻️ Suggested refactor
-export default function SubmissionActionButtons({
+const SubmissionActionButtons = ({
   onDisqualify,
   onShortlist,
-}: SubmissionActionButtonsProps) {
+}: SubmissionActionButtonsProps): JSX.Element => {
   return (
     ...
   );
-}
+};
+
+export default SubmissionActionButtons;

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

components/organization/cards/ReviewSubmissionModal/SubmissionDetailsTab.tsx (1)

21-25: Consider adding viewport={{ once: true }} to avoid repeated fade-ins on scroll.

Inside a ScrollArea, whileInView re-triggers every time the element scrolls back into view, causing a repeated fade-in flicker. Adding viewport={{ once: true }} limits the animation to the first appearance.

Proposed tweak
       <motion.div
         initial={{ opacity: 0, y: 10 }}
         whileInView={{ opacity: 1, y: 0 }}
+        viewport={{ once: true }}
         className='space-y-10'
       >
app/(landing)/hackathons/[slug]/page.tsx (1)

141-152: Stale dependency: currentHackathon?.organizationId is no longer used inside the memo.

Now that organizationId has been removed from the useTeamPosts call, this dependency has no effect on the memoized value. It can be safely removed to keep the dependency array accurate.

♻️ Suggested cleanup
   ], [
     currentHackathon?.participants,
     currentHackathon?.resources,
     currentHackathon?.participantType,
     currentHackathon?.enabledTabs,
-    currentHackathon?.organizationId,
     submissions,
     discussionComments.comments.length,
     teamPosts.length,
     hackathonId,
     winners,
   ]);
components/organization/cards/ReviewSubmissionModal/SubmissionLinksTab.tsx (1)

36-62: Consider using link.url as the key instead of array index.

Using index as a React key is fragile if the list can be reordered or filtered. Since URLs should be unique within a submission's links, link.url would be a more stable key.

-            <motion.a
-              key={index}
+            <motion.a
+              key={link.url}
hooks/hackathon/use-team-invite.ts (1)

51-54: Hardcoded invitation message.

The invite message 'Join my team for the hackathon!' is hardcoded. Consider making it a parameter of createTeamAndInvite or part of the hook options for flexibility.

components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx (1)

14-14: Remove speculative comment.

// Assuming cn utility is from here should be removed — it's shipped code, not a code review note.

hooks/use-participant-submission.ts (1)

60-63: Nested property access may crash on incomplete voter/commenter profiles.

If the API returns votes or comments where user.profile is partially populated (e.g., missing firstName), the template literal on line 62 would produce "undefined undefined". Consider adding fallbacks:

-        name: `${vote.user.profile.firstName} ${vote.user.profile.lastName}`,
+        name: [vote.user?.profile?.firstName, vote.user?.profile?.lastName].filter(Boolean).join(' ') || 'Unknown',

Same applies to the comment author name on line 76.

Also applies to: 75-78

components/organization/hackathons/ParticipantsGrid.tsx (1)

72-76: Duplicate vote-count derivation — consider reusing transformParticipantToSubmission.

This same array-vs-number vote counting logic exists in hooks/use-participant-submission.ts (lines 52–54). Consider extracting a shared helper or reusing transformParticipantToSubmission here to avoid drift between the two implementations.

components/project-details/project-voters/index.tsx (1)

165-166: Prefer unknown over any for the catch clause.

Using catch (err: any) bypasses type safety. Narrowing from unknown is the idiomatic TypeScript approach.

♻️ Suggested fix
-    } catch (err: any) {
-      toast.error(err.message || 'Failed to submit vote. Please try again.');
+    } catch (err: unknown) {
+      const message = err instanceof Error ? err.message : 'Failed to submit vote. Please try again.';
+      toast.error(message);

As per coding guidelines, "Use provided Types from Trustless Work documentation when applicable" and prefer explicit type annotations.

components/ui/expandable-screen.tsx (1)

113-113: Use cn() instead of template literal string concatenation for className composition.

The file already imports cn from other components' patterns. Using cn() is more consistent with the codebase and handles falsy values gracefully.

♻️ Example
-          <motion.div className={`relative inline-block ${className}`}>
+          <motion.div className={cn('relative inline-block', className)}>
-            className={`relative flex h-full w-full transform-gpu overflow-y-auto will-change-transform ${className}`}
+            className={cn('relative flex h-full w-full transform-gpu overflow-y-auto will-change-transform', className)}

Note: cn is not currently imported in this file — you'd need to add the import. As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX."

Also applies to: 170-170

components/hackathons/submissions/SubmissionForm.tsx (1)

50-51: Unused import: useTeamInvite.

useTeamInvite is imported on Line 51 but never used in the component.

🧹 Remove unused import
-import { useTeamInvite } from '@/hooks/hackathon/use-team-invite';
components/hackathons/submissions/submissionTab.tsx (1)

39-45: mySubmission: any undermines type safety across the component.

The comment acknowledges this, but it propagates any into all usages (status checks, field accesses, etc.) and causes the as any cast at Line 417. Consider defining a minimal interface or importing the submission type from the hook.

components/organization/cards/ReviewSubmissionModal/TeamSection.tsx (1)

4-4: Unused import: Linkedin.

Linkedin is imported but never rendered.

🧹 Remove unused import
-import { Github, Twitter, Linkedin } from 'lucide-react';
+import { Github, Twitter } from 'lucide-react';
components/organization/cards/ReviewSubmissionModal.tsx (1)

173-182: No empty state when the Team tab has no team members.

When currentSubmission.teamMembers is falsy, the Team tab renders nothing. Consider showing a brief message (e.g., "No team information available") so the tab doesn't appear broken.

♻️ Suggested addition
                       <TabsContent
                         value='team'
                         className='mt-0 h-full border-none p-0 outline-none'
                       >
-                        {currentSubmission.teamMembers && (
+                        {currentSubmission.teamMembers ? (
                           <TeamSection
                             teamMembers={currentSubmission.teamMembers}
                           />
+                        ) : (
+                          <p className='py-8 text-center text-sm text-gray-500'>
+                            No team information available.
+                          </p>
                         )}
                       </TabsContent>
app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx (2)

3-3: Unused import: useRef.

useRef is imported but not used in the component.

🧹 Remove unused import
-import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
+import { useEffect, useMemo, useState, useCallback } from 'react';

183-205: "Mock" table instance is a pragmatic workaround — consider typing the pagination updater.

The empty-columns table just to drive DataTablePagination works, but onPaginationChange: (updater: any) should use the proper TanStack type (Updater<PaginationState>) to avoid the any.

…athon team invite logic with better error handling.
Copy link

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

Caution

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

⚠️ Outside diff range comments (2)
components/hackathons/submissions/SubmissionForm.tsx (2)

855-1122: ⚠️ Potential issue | 🔴 Critical

Bug: Duplicate case 1 — Media & Links step is unreachable, Review step renders wrong content.

The switch statement has two case 1: blocks (Lines 855 and 941). JavaScript executes only the first match, so step index 2 (Media & Links) falls through to case 2: which renders the Review content instead. Meanwhile, the second case 1: block (the actual Media & Links form at Line 941) is dead code.

With 4 steps in INITIAL_STEPS (indices 0–3), the cases should be 0, 1, 2, 3:

Proposed fix
-      case 1:
+      case 2:
         return (
-          <div key='step-1' className='space-y-6'>
+          <div key='step-2' className='space-y-6'>
             <FormField
               control={form.control}
               name='logo'

and

-      case 2:
+      case 3:
         return (
           <div className='space-y-6'>
             <div className='rounded-lg border border-gray-700 bg-gray-800/50 p-6'>

556-566: ⚠️ Potential issue | 🟠 Major

Validation failure redirects to wrong step.

When required field validation fails at submission time, setCurrentStep(0) sends the user back to the "Participation" step (step 0). The fields being validated (projectName, category, description) are on step 1 ("Basic Info"). This should redirect to step 1.

Proposed fix
-        setCurrentStep(0);
-        updateStepState(0, 'active');
+        setCurrentStep(1);
+        updateStepState(1, 'active');
🤖 Fix all issues with AI agents
In
`@app/`(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx:
- Around line 142-166: In handleGrade, move the early-return guard that checks
organizationId and hackathonId before calling setSelectedParticipant so
selectedParticipant isn't mutated when we bail out; specifically, in the
handleGrade function check if (!organizationId || !hackathonId) return; as the
first statement (or at least before setSelectedParticipant(participant)), then
proceed to set loading and fetch criteria (getHackathon), setCriteria, and open
the modal (setIsJudgeModalOpen) as currently implemented.
- Around line 357-358: The submission prop is being given the participant's id
(id: selectedParticipant.id) but GradeSubmissionModal expects a submission id
per its SubmissionData interface; replace the incorrect field with the actual
submission id (e.g., id: selectedParticipant.submission?.id or id:
selectedParticipant.submissionId) and add an existence check (optional chaining
or guard) before opening GradeSubmissionModal so you never pass undefined; if
your component tracks a selectedSubmission object, pass that
selectedSubmission.id instead to align with GradeSubmissionModal and
SubmissionData.

In `@components/hackathons/submissions/SubmissionForm.tsx`:
- Around line 607-611: Remove the duplicated useEffect that sets
form.setValue('participationType', 'TEAM') when myTeam exists; keep a single
instance of this effect (so it only runs once based on myTeam and submissionId)
and delete the redundant useEffect, ensuring the remaining effect still depends
on [myTeam, form, submissionId].
- Around line 701-715: The alert is only informational but you must enforce
leader-only submission: add a guard using the existing myTeam.leaderId !==
user?.id to disable the "Next" and "Submit Project" controls and prevent
submission in the submit handler. Concretely, set disabled={myTeam.leaderId !==
user?.id} on the Next button component and on the final Submit button (or
SubmitProjectButton), and add an early return/throw in the form submit handler
(onSubmit / handleFinalSubmit / handleSubmit) that prevents processing when the
same condition is true; optionally keep the current Alert for UX while ensuring
the buttons and submit logic enforce the constraint.

In
`@components/organization/cards/ReviewSubmissionModal/SubmissionDetailsTab.tsx`:
- Around line 14-18: The SubmissionDetailsTab component currently destructures
the introduction prop but never uses it; either render it inside the component
(e.g., add a styled paragraph or section for introduction above or below the
description in the JSX returned by SubmissionDetailsTab) or remove introduction
from the prop destructuring to avoid unused props; update the JSX where
projectName and description are rendered in SubmissionDetailsTab to include the
introduction element (or delete introduction from the parameter list and any
typing) so the prop is either displayed or removed.

In
`@components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx`:
- Around line 45-48: The handleShare function calls
navigator.clipboard.writeText which can reject; make handleShare async, await
navigator.clipboard.writeText(window.location.href) inside a try/catch, call
toast.success('Link copied to clipboard') on success and toast.error(...) with a
helpful message on failure, and include any caught error details in the error
toast/log; optionally provide a fallback (e.g., show the URL in a selectable
input or prompt) if clipboard access is denied.
- Line 32: The SubmissionModalHeader component currently declares a
submissionName prop that is never used; either remove submissionName from the
component's props interface and stop passing it from the parent, or implement
rendering logic to display it (e.g., add submissionName into the header JSX
inside SubmissionModalHeader) and ensure its type remains in the props type
definition; update the parent component to match the chosen approach (remove the
prop or pass it through) and run a quick compile to ensure no unused prop/type
warnings remain.

In `@components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx`:
- Around line 83-92: The ThumbsUp icon currently treats undefined voter.voteType
as negative because the ternary defaults to the negative branch; update the
logic in SubmissionVotesTab (around the ThumbsUp className/ cn call) to
explicitly handle three cases: if voter.voteType === 'positive' apply
'text-green-500' (no rotation), else if voter.voteType === 'negative' apply
'rotate-180 text-red-500', and otherwise (undefined/neutral) apply a neutral
style like 'text-gray-400' with no rotation; modify the ternary into a small
conditional or a helper (e.g., getVoteIconClasses or a switch) to make the
neutral case explicit.

In `@components/organization/cards/ReviewSubmissionModal/TeamSection.tsx`:
- Around line 67-70: The Github and Twitter icon elements (Github and Twitter in
TeamSection.tsx) show interactive styles (cursor-pointer, hover:text-gray-400)
but have no click handlers or hrefs; update the UI to either (A) wire them to
the TeamMember profile URLs by wrapping each icon in an anchor (or adding
onClick) that uses the TeamMember.githubUrl / TeamMember.twitterUrl properties
and opens in a new tab, ensuring to add rel="noopener noreferrer", or (B) remove
the interactive classes (cursor-pointer and hover:text-gray-400) from the Github
and Twitter icon elements until the links are available; locate the icons in the
TeamSection component and apply one of these fixes consistently.

In `@components/organization/hackathons/ParticipantsGrid.tsx`:
- Around line 78-94: Add an explicit branch for the disqualified status so it
doesn't fall through to "Registered": check the isDisqualified flag (the same
predicate used by ParticipantsTable) before the other conditions and set
statusColor, statusText ("Disqualified"), statusIcon (red/negative icon), and
dotColor to the red styles used by ParticipantsTable; update the status
configuration block that currently sets
statusColor/statusText/statusIcon/dotColor (and references isShortlisted and
hasSubmission) to include this isDisqualified branch first so disqualified
participants render the red badge consistently.

In `@components/organization/hackathons/ParticipantToolbar.tsx`:
- Around line 41-47: The reset handler resets searchValue but doesn't notify
upstream immediately, causing a 500ms lag; update handleReset to also call
onSearchChange('') immediately (and if you have a debounced hook with a
cancel/flush API, call its cancel/flush there) so search, status and type
filters all reset synchronously; reference handleReset, setSearchValue and
onSearchChange when making the change.
🧹 Nitpick comments (12)
components/hackathons/submissions/SubmissionForm.tsx (3)

389-433: Dual state for team members — invitees and form.teamMembers can drift.

invitees (local state) and form.getValues('teamMembers') (form state) are maintained in parallel via handleAddInvitee and handleRemoveInvitee. If either gets out of sync (e.g., form reset at Line 261 clears teamMembers but not invitees), the UI and submitted data will diverge.

Consider deriving the invitee list from form.watch('teamMembers') as the single source of truth, eliminating the separate invitees state.


161-175: Clean up uncertainty comments and use const arrow function.

Lines 169–174 contain stream-of-consciousness comments expressing uncertainty about the implementation. These should be removed before merging. Also, per coding guidelines, prefer const arrow functions over function declarations.

Proposed fix
-function SubmissionFormContent({
+const SubmissionFormContent = ({
   hackathonSlugOrId,
   organizationId,
   initialData,
   submissionId,
   onSuccess,
-}: SubmissionFormContentProps) {
+}: SubmissionFormContentProps) => {
   const { collapse, isExpanded: open } = useExpandableScreen();
-  // Use a local state for 'open' behavior if needed, generally expandable screen is always rendered when expanded
-  // But we need 'open' for some effects?
-  // The 'open' in effects was used to reset form or fetch data.
-  // We can use 'useEffect(() => ..., [])' for mount (which happens on expand usually? No, ExpandableScreen keeps content mounted?
-  // Let's check ExpandableScreenContent implementation... It uses AnimatePresence, so it mounts/unmounts!
-  // So 'open' is effectively 'true' when this component is mounted.

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".


1288-1304: Use const arrow function for SubmissionScreenWrapper.

Same guideline applies here — prefer const arrow functions.

Proposed fix
-export function SubmissionScreenWrapper({
+export const SubmissionScreenWrapper = ({
   children,
   ...props
-}: SubmissionScreenWrapperProps) {
+}: SubmissionScreenWrapperProps) => {
   return (

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

lib/api/hackathons/participants.ts (1)

161-161: not_submitted status is defined but not exposed in the toolbar UI.

The API accepts 'not_submitted' as a filter value, but ParticipantToolbar.tsx (line 78-81) only offers submitted, shortlisted, and disqualified. If filtering by "not submitted" is a desired use case, consider adding it to the toolbar's status select; otherwise this is fine as-is.

app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx (3)

196-204: Avoid any — type the pagination updater properly.

onPaginationChange: (updater: any) and the as any casts on lines 213 and 217 bypass type safety. For the pagination updater, use the type from @tanstack/react-table:

Proposed fix
-   onPaginationChange: (updater: any) => {
+   onPaginationChange: (updater: unknown) => {
      if (typeof updater === 'function') {
-       const newState = updater({
+       const newState = (updater as (old: { pageIndex: number; pageSize: number }) => { pageIndex: number; pageSize: number })({
          pageIndex: participantsPagination.currentPage - 1,
          pageSize: participantsPagination.itemsPerPage,
        });
        handlePageChange(newState.pageIndex + 1);
      }
    },

81-89: Filter changes always reset to page 1 — this is good, but page size is hardcoded.

The 12 page size is duplicated across lines 83, 124, and 173. Consider extracting it to a constant to keep it in sync with hookOptions.pageSize.

Proposed fix
+ const PAGE_SIZE = 12;
+
  const hookOptions = useMemo(
    () => ({
      organizationId,
      autoFetch: false,
-     pageSize: 12,
+     pageSize: PAGE_SIZE,
    }),
    [organizationId]
  );

  // Then replace all occurrences of `12` with `PAGE_SIZE`

25-25: Page component uses function declaration instead of const arrow function.

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations." However, for Next.js page components that require a default export, this is a common pattern. Consider switching if the team enforces it strictly.

components/organization/cards/ReviewSubmissionModal/SubmissionInfo.tsx (1)

30-53: Consider extracting the fallback image path as a constant.

'/bitmed.png' is used twice (Lines 37 and 45). A named constant would avoid duplication and make future changes easier.

♻️ Suggested change
+const FALLBACK_LOGO = '/bitmed.png';
+
 export const SubmissionInfo: React.FC<SubmissionInfoProps> = ({
   submission,
 }) => {
   return (
     <div className='bg-background-card/20 flex w-1/2 flex-col overflow-y-auto border-r border-gray-900/40'>
       <div className='relative aspect-video w-full overflow-hidden bg-black/40'>
         {submission.videoUrl ? (
           <VideoPlayer videoUrl={submission.videoUrl} />
         ) : (
           <div className='bg-background/20 flex h-full w-full items-center justify-center'>
             <Image
-              src={submission.logo || '/bitmed.png'}
+              src={submission.logo || FALLBACK_LOGO}
               alt={submission.projectName}
               width={400}
               height={225}
               className='h-full w-full object-cover px-8 py-4 opacity-30 blur-2xl'
             />
             <div className='absolute inset-0 flex items-center justify-center'>
               <Image
-                src={submission.logo || '/bitmed.png'}
+                src={submission.logo || FALLBACK_LOGO}
                 alt={submission.projectName}
components/organization/cards/ReviewSubmissionModal/SubmissionLinksTab.tsx (1)

15-26: Consider moving getIcon outside the component to avoid re-creation on every render.

Since getIcon has no dependency on component state or props, it can be extracted as a module-level function for a marginal performance improvement and better testability.

components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx (2)

103-103: Remove stale comment that looks like a pasted review suggestion.

Line 103 contains // "const activeIndex = Math.min(currentIndex, visibleCount - 1)" which appears to be a leftover from the review feedback, not an actual code comment.

🧹 Proposed fix
           const visibleCount = Math.min(totalSubmissions, 5);
-            // "const activeIndex = Math.min(currentIndex, visibleCount - 1)"
             const clampedIndex = Math.min(currentIndex, visibleCount - 1);

14-14: Remove unnecessary inline comment.

// Assuming cn utility is from here adds no value — the import path is self-evident.

🧹 Proposed fix
-import { cn } from '@/lib/utils'; // Assuming cn utility is from here
+import { cn } from '@/lib/utils';
components/organization/cards/ReviewSubmissionModal/SubmissionDetailsTab.tsx (1)

21-25: Animation fires on every scroll into view — consider adding once: true.

whileInView without viewport={{ once: true }} re-triggers the fade-in each time the element scrolls in/out of the viewport. For a details tab inside a modal this is likely fine, but if the ScrollArea can cause re-entry, the repeated animation may feel jarring.

Suggested tweak
       <motion.div
         initial={{ opacity: 0, y: 10 }}
         whileInView={{ opacity: 1, y: 0 }}
+        viewport={{ once: true }}
         className='space-y-10'
       >

Comment on lines 357 to 358
submission={{
id: selectedParticipant.id,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find where GradeSubmissionModal is defined
fd -e tsx -e ts | xargs rg -l "GradeSubmissionModal" | head -20

Repository: boundlessfi/boundless

Length of output: 1045


🏁 Script executed:

#!/bin/bash
# Search for GradeSubmissionModal interface or component definition
rg -A 10 "interface GradeSubmissionModalProps|function GradeSubmissionModal|export.*GradeSubmissionModal" --type=ts --type=tsx

Repository: boundlessfi/boundless

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check the selectedParticipant type in the participants page
rg -B 5 -A 5 "selectedParticipant.*submission" app/\(landing\)/organizations/*/hackathons/*/participants/page.tsx 2>/dev/null || rg -B 5 -A 5 "selectedParticipant.*submission" --type=ts --type=tsx

Repository: boundlessfi/boundless

Length of output: 1887


🏁 Script executed:

#!/bin/bash
# Look at lines around 357-358 in the target file to see context
fd "participants/page.tsx" --path "*hackathons*" | head -1 | xargs -I {} head -370 {} | tail -30

Repository: boundlessfi/boundless

Length of output: 280


🏁 Script executed:

#!/bin/bash
# Read the GradeSubmissionModal component to see what it expects
cat -n components/organization/cards/GradeSubmissionModal/index.tsx | head -100

Repository: boundlessfi/boundless

Length of output: 3407


🏁 Script executed:

#!/bin/bash
# Search for submission object type definitions and SubmissionData
rg -n "type.*Submission|interface.*Submission" --type=ts -A 5 | head -60

Repository: boundlessfi/boundless

Length of output: 3076


🏁 Script executed:

#!/bin/bash
# Check if selectedParticipant.submission has an id field in type definitions
rg -n "submission.*id|\.id.*submission" --type=ts -B 2 -A 2 | head -40

Repository: boundlessfi/boundless

Length of output: 2649


Bug: Participant ID passed as submission ID to GradeSubmissionModal.

id: selectedParticipant.id passes the participant ID, but GradeSubmissionModal expects a submission ID as defined in its SubmissionData interface. This will cause incorrect grading operations downstream.

              submission={{
-               id: selectedParticipant.id,
+               id: selectedParticipant.submission.id,
                projectName: selectedParticipant.submission.projectName,
📝 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
submission={{
id: selectedParticipant.id,
submission={{
id: selectedParticipant.submission.id,
projectName: selectedParticipant.submission.projectName,
🤖 Prompt for AI Agents
In
`@app/`(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
around lines 357 - 358, The submission prop is being given the participant's id
(id: selectedParticipant.id) but GradeSubmissionModal expects a submission id
per its SubmissionData interface; replace the incorrect field with the actual
submission id (e.g., id: selectedParticipant.submission?.id or id:
selectedParticipant.submissionId) and add an existence check (optional chaining
or guard) before opening GradeSubmissionModal so you never pass undefined; if
your component tracks a selectedSubmission object, pass that
selectedSubmission.id instead to align with GradeSubmissionModal and
SubmissionData.

Copy link

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

Caution

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

⚠️ Outside diff range comments (4)
.github/workflows/ci.yml (1)

354-358: ⚠️ Potential issue | 🔴 Critical

notify-failure references the commented-out build job, which will break the workflow.

Line 357: needs: [code-quality, build, security] still depends on the build job, but that job is commented out at Line 63. GitHub Actions will fail to parse this workflow because build is not a defined job ID.

Remove build from the needs list:

Proposed fix
-    needs: [code-quality, build, security]
+    needs: [code-quality, security]
components/hackathons/submissions/SubmissionForm.tsx (2)

1125-1217: ⚠️ Potential issue | 🟡 Minor

Review step doesn't show participation type or team information.

Step 3 (Review) renders project details but omits the participation type selection, team name, and team members. Users creating a new team can't verify their team details before submitting.


569-592: ⚠️ Potential issue | 🔴 Critical

safeData never includes teamName or teamMembers — new-team submissions silently lose this data.

safeData is constructed at lines 510–549 without teamName or teamMembers. Then lines 586 and 590 read safeData.teamName and safeData.teamMembers, which are always undefined. When a user creates a new team with invitees, this data is extracted into the form but never transferred to safeData, so it's silently discarded during submission.

The fix: add these fields to the safeData construction:

const safeData: SubmissionFormData = {
  projectName: (data.projectName ?? currentValues.projectName ?? '')
    .toString()
    .trim(),
  category: (data.category ?? currentValues.category ?? '')
    .toString()
    .trim(),
  description: (data.description ?? currentValues.description ?? '')
    .toString()
    .trim(),
  // ... existing optional fields ...
  links: /* ... existing logic ... */,
  participationType: data.participationType || 'INDIVIDUAL',
+ teamName: data.teamName ?? currentValues.teamName,
+ teamMembers: data.teamMembers ?? currentValues.teamMembers,
};
components/organization/cards/ReviewSubmissionModal/SubmissionInfo.tsx (1)

10-20: ⚠️ Potential issue | 🟡 Minor

Import the canonical Submission type from ./types.ts instead of re-declaring it locally.

The local Submission interface is incomplete and diverges from the canonical type in types.ts. Specifically, it's missing the required submissionDate: string field, along with optional fields like teamMembers, links, voters, and commentsList. This creates a type mismatch: the component receives submissions that conform to the canonical type, but the local interface doesn't reflect all fields.

Replace the local interface declaration (lines 10–20) with an import:

import type { Submission } from './types';
🤖 Fix all issues with AI agents
In @.github/workflows/ci.yml:
- Around line 63-98: The workflow has the entire "build" job commented out (the
job named "Build & Test" with steps like "Checkout code", "Setup Node.js",
"Install dependencies", "Set up environment variables", "Build application", and
"Upload build artifacts"); restore that job instead of removing it—either
uncomment and fix the underlying environment/config issues causing failures or
re-enable the job and set continue-on-error only temporarily on the failing
step(s) (e.g., add continue-on-error: true to specific steps) so the pipeline
still performs compilation and artifact upload to catch build and bundle
regressions.

In
`@app/`(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx:
- Around line 88-96: The effect is firing on every keystroke because it depends
on the whole filters object (including filters.search); debounce the search
before calling fetchParticipants by deriving a debouncedSearch from
filters.search (e.g., via a useDebounce hook or a short setTimeout/clearTimeout)
with ~300ms delay, then change the effect to depend on debouncedSearch (and
filters.status/type as needed) and call fetchParticipants(actualHackathonId, 1,
PAGE_SIZE, { search: debouncedSearch, status: filters.status === 'all' ?
undefined : filters.status, type: filters.type === 'all' ? undefined :
filters.type }); ensure cleanup of timers if using setTimeout.

In `@components/hackathons/submissions/SubmissionForm.tsx`:
- Around line 628-631: The RadioGroup is using defaultValue (uncontrolled) so
programmatic updates via form.setValue('participationType', 'TEAM') won't update
the UI; change RadioGroup to controlled mode by replacing
defaultValue={field.value} with value={field.value} and keep
onValueChange={field.onChange} so the component reflects form state updates from
the useEffect that auto-selects TEAM and any other programmatic changes.
🧹 Nitpick comments (14)
app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx (3)

224-230: Avoid as any — use the existing union type.

The filter state already declares the valid union types on lines 35-41. Cast to the actual type instead of any to preserve type safety.

Suggested fix
  const handleStatusFilterChange = useCallback((status: string) => {
-   setFilters(f => ({ ...f, status: status as any }));
+   setFilters(f => ({ ...f, status: status as typeof f.status }));
  }, []);

  const handleTypeFilterChange = useCallback((type: string) => {
-   setFilters(f => ({ ...f, type: type as any }));
+   setFilters(f => ({ ...f, type: type as typeof f.type }));
  }, []);

129-137: handlePageChange duplicates filter-mapping logic from the effect and handleReviewSuccess.

The filter-to-params mapping (status === 'all' ? undefined : ...) is repeated in three places (lines 90-93, 131-134, 181-184). Extract a small helper to keep it DRY and prevent future divergence.


190-217: Using useReactTable solely for pagination state management is workable but heavy.

An empty columns: [] array and unused row model make this a heavyweight shim. This is fine for now, but if DataTablePagination is under your control, consider accepting raw pagination props directly to avoid this boilerplate.

components/hackathons/submissions/SubmissionForm.tsx (4)

168-174: Remove developer thinking-out-loud comments.

Lines 169–174 are internal reasoning notes ("// Use a local state for 'open' behavior if needed…", "// Let's check ExpandableScreenContent implementation…"). These read like scratchpad notes and should be removed before merging.


201-207: Dual state for invitees is fragile — derive display from form state instead.

invitees local state and form.teamMembers are updated in tandem in handleAddInvitee / handleRemoveInvitee, but a form reset (e.g., from initialData) won't clear invitees. This can cause the badge list to show stale entries. Consider removing the invitees state and using form.watch('teamMembers') for display.

♻️ Sketch
- const [invitees, setInvitees] = useState<
-   Array<{ email?: string; userId?: string; name: string; role: string }>
- >([]);

+ const invitees = form.watch('teamMembers') || [];

Then in handleAddInvitee, only update the form value:

  const handleAddInvitee = () => {
    // ...validation...
    const currentMembers = form.getValues('teamMembers') || [];
    form.setValue('teamMembers', [
      ...currentMembers,
      { name: currentInviteeName, email: currentInviteeEmail, role: currentInviteeRole },
    ]);
-   setInvitees(prev => [...prev, { ... }]);
    // clear inputs...
  };

Similarly simplify handleRemoveInvitee.

Also applies to: 381-425


634-636: Use clsx for conditional classes instead of template literal.

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX."

♻️ Example
+ import clsx from 'clsx';

  <FormItem
-   className={`flex items-center space-y-0 space-x-3 rounded-md border border-gray-800 p-4 hover:border-gray-700 ${myTeam ? 'cursor-not-allowed opacity-50' : ''}`}
+   className={clsx(
+     'flex items-center space-y-0 space-x-3 rounded-md border border-gray-800 p-4 hover:border-gray-700',
+     myTeam && 'cursor-not-allowed opacity-50'
+   )}
  >

161-167: Prefer const arrow functions with explicit type annotations.

Both SubmissionFormContent (line 161) and SubmissionScreenWrapper (line 1303) use function declarations. As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations."

♻️ Example for SubmissionScreenWrapper
-export function SubmissionScreenWrapper({
-  children,
-  ...props
-}: SubmissionScreenWrapperProps) {
+export const SubmissionScreenWrapper: React.FC<SubmissionScreenWrapperProps> = ({
+  children,
+  ...props
+}) => {

Also applies to: 1303-1306

components/organization/cards/ReviewSubmissionModal/SubmissionLinksTab.tsx (1)

36-62: Consider using a stable key instead of array index.

Using index as the key (Line 38) is fine for a static, non-reorderable list, but if links can be added/removed/reordered, React may incorrectly reconcile items. If each link has a unique url, that would be a more stable key.

♻️ Suggested change
-          links.map((link, index) => (
-            <motion.a
-              key={index}
+          links.map((link, index) => (
+            <motion.a
+              key={link.url}
components/organization/cards/ReviewSubmissionModal/SubmissionInfo.tsx (1)

105-109: Hardcoded + suffix on comments count may be misleading.

{submission.comments.toLocaleString()}+ always appends a + regardless of whether there are actually more comments than shown. If the count is exact, this gives a false impression. Consider conditionally showing the + or removing it.

components/organization/cards/ReviewSubmissionModal/TeamSection.tsx (1)

30-73: No empty state when teamMembers is an empty array.

Other sibling tabs (Links, Voters) render a dedicated empty state when data is absent. If teamMembers is [], this component renders an empty grid with no user feedback. The parent guards with currentSubmission.teamMembers && (truthy check), but an empty array [] is truthy, so it would still render.

♻️ Suggested guard in TeamSection or in the parent
-                        {currentSubmission.teamMembers && (
+                        {currentSubmission.teamMembers && currentSubmission.teamMembers.length > 0 && (
                           <TeamSection
                             teamMembers={currentSubmission.teamMembers}
                           />
                         )}

Or add an empty state inside TeamSection similar to the other tabs.

components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx (1)

14-14: Remove speculative inline comment.

// Assuming cn utility is from here adds noise — the import path is correct and self-documenting.

-import { cn } from '@/lib/utils'; // Assuming cn utility is from here
+import { cn } from '@/lib/utils';
components/organization/cards/ReviewSubmissionModal.tsx (3)

19-30: Function declaration instead of const arrow function.

Per coding guidelines, prefer const arrow functions with explicit type annotations over function declarations for .tsx files.

♻️ Suggested change
-export default function ReviewSubmissionModal({
+const ReviewSubmissionModal: React.FC<ReviewSubmissionModalProps> = ({
   open,
   onOpenChange,
   submissions = [],
   currentIndex = 0,
   organizationId,
   hackathonId,
   participantId,
   onSuccess,
   onShortlist,
   onDisqualify,
-}: ReviewSubmissionModalProps) {
+}) => {

Then at the bottom of the file:

export default ReviewSubmissionModal;

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations."


150-201: AnimatePresence re-mounts all TabsContent nodes on every tab switch.

The motion.div keyed on activeTab wraps all four TabsContent elements. When the key changes, AnimatePresence unmounts and remounts the entire subtree (all four tabs), even though only one is active. This works correctly but is slightly wasteful.

An alternative is to move the key and animation props onto each individual TabsContent wrapper, or use a single content renderer that switches on activeTab:

♻️ Alternative approach — render only the active tab's content
                 <AnimatePresence mode='wait'>
                   <motion.div
                     key={activeTab}
                     initial={{ opacity: 0, x: 10 }}
                     animate={{ opacity: 1, x: 0 }}
                     exit={{ opacity: 0, x: -10 }}
                     transition={{ duration: 0.2 }}
                     className='h-full'
                   >
-                     <TabsContent value='details' className='mt-0 h-full border-none p-0 outline-none'>
-                       <SubmissionDetailsTab ... />
-                     </TabsContent>
-                     <TabsContent value='team' className='mt-0 h-full border-none p-0 outline-none'>
-                       ...
-                     </TabsContent>
-                     <TabsContent value='links' className='mt-0 h-full border-none p-0 outline-none'>
-                       ...
-                     </TabsContent>
-                     <TabsContent value='voters' className='mt-0 h-full border-none p-0 outline-none'>
-                       ...
-                     </TabsContent>
+                     {activeTab === 'details' && (
+                       <SubmissionDetailsTab
+                         projectName={currentSubmission.projectName}
+                         videoUrl={currentSubmission.videoUrl}
+                         introduction={currentSubmission.introduction}
+                         description={currentSubmission.description}
+                       />
+                     )}
+                     {activeTab === 'team' && currentSubmission.teamMembers?.length > 0 && (
+                       <TeamSection teamMembers={currentSubmission.teamMembers} />
+                     )}
+                     {activeTab === 'links' && (
+                       <SubmissionLinksTab links={currentSubmission.links || []} />
+                     )}
+                     {activeTab === 'voters' && (
+                       <SubmissionVotesTab voters={currentSubmission.voters || []} />
+                     )}
                   </motion.div>
                 </AnimatePresence>

This also lets you drop the Tabs/TabsContent dependency for the content area (keeping only TabsList/TabsTrigger for the header), simplifying the rendering.


63-68: Sync effect is clean but consider adding setActiveTab('details') here too.

This effect fires when the modal opens or currentIndex changes from outside, resetting the internal index. The third effect (Line 71-73) resets activeTab when currentSubmissionIndex changes — but only if setCurrentSubmissionIndex actually triggers a state change. If currentIndex equals currentSubmissionIndex, the state won't change and activeTab won't reset. This is generally fine but worth noting for edge cases where a user re-opens the modal on the same submission while on a non-details tab.

Comment on lines +63 to +98
# build:
# name: Build & Test
# runs-on: ubuntu-latest
# needs: code-quality

# steps:
# - name: Checkout code
# uses: actions/checkout@v4

# - name: Setup Node.js
# uses: actions/setup-node@v4
# with:
# node-version: ${{ env.NODE_VERSION }}
# cache: 'npm'

# - name: Install dependencies
# run: npm ci

# - name: Set up environment variables
# run: |
# echo "🔧 Setting up environment variables for build..."
# echo "NEXT_PUBLIC_API_URL=http://localhost:3000/api" >> .env.local
# echo "NEXT_PUBLIC_APP_URL=http://localhost:3000" >> .env.local
# echo "NEXTAUTH_SECRET=ci-test-secret-key" >> .env.local
# echo "NEXTAUTH_URL=http://localhost:3000" >> .env.local
# echo "DATABASE_URL=postgresql://test:test@localhost:5432/test_db" >> .env.local

# - name: Build application
# run: npm run build

# - name: Upload build artifacts
# uses: actions/upload-artifact@v4
# with:
# name: build-files
# path: .next/
# retention-days: 7
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Commenting out Build & Test, Bundle Analysis, Performance, and Deploy jobs disables critical CI safeguards.

This PR introduces significant code changes (motion migration, new components, gating patterns) yet disables the build verification step entirely. Without the build job, broken builds and bundle regressions can land on main undetected.

If these jobs are failing due to environment or config issues, consider fixing them or marking them as continue-on-error rather than commenting them out wholesale. At minimum, the build job should remain active to validate that the application compiles.

Also applies to: 183-351

🧰 Tools
🪛 Checkov (3.2.334)

[medium] 88-89: Basic Auth Credentials

(CKV_SECRET_4)

🤖 Prompt for AI Agents
In @.github/workflows/ci.yml around lines 63 - 98, The workflow has the entire
"build" job commented out (the job named "Build & Test" with steps like
"Checkout code", "Setup Node.js", "Install dependencies", "Set up environment
variables", "Build application", and "Upload build artifacts"); restore that job
instead of removing it—either uncomment and fix the underlying
environment/config issues causing failures or re-enable the job and set
continue-on-error only temporarily on the failing step(s) (e.g., add
continue-on-error: true to specific steps) so the pipeline still performs
compilation and artifact upload to catch build and bundle regressions.

Comment on lines +88 to +96
useEffect(() => {
if (actualHackathonId) {
fetchParticipants(actualHackathonId, 1, PAGE_SIZE, {
search: filters.search,
status: filters.status === 'all' ? undefined : filters.status,
type: filters.type === 'all' ? undefined : filters.type,
});
}
}, [actualHackathonId, fetchParticipants, filters]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Search filter triggers an API call on every keystroke — add debounce.

The filters object (including search) is a direct dependency of this effect, so each character typed fires fetchParticipants. This hammers the backend and degrades UX. Debounce the search value (e.g., 300ms) before it flows into the effect.

Sketch
+import { useDebouncedValue } from '@/hooks/use-debounced-value'; // or implement a simple one
+
+// near other state declarations
+const debouncedSearch = useDebouncedValue(filters.search, 300);
+
 useEffect(() => {
   if (actualHackathonId) {
     fetchParticipants(actualHackathonId, 1, PAGE_SIZE, {
-      search: filters.search,
+      search: debouncedSearch,
       status: filters.status === 'all' ? undefined : filters.status,
       type: filters.type === 'all' ? undefined : filters.type,
     });
   }
-}, [actualHackathonId, fetchParticipants, filters]);
+}, [actualHackathonId, fetchParticipants, debouncedSearch, filters.status, filters.type]);
🤖 Prompt for AI Agents
In
`@app/`(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
around lines 88 - 96, The effect is firing on every keystroke because it depends
on the whole filters object (including filters.search); debounce the search
before calling fetchParticipants by deriving a debouncedSearch from
filters.search (e.g., via a useDebounce hook or a short setTimeout/clearTimeout)
with ~300ms delay, then change the effect to depend on debouncedSearch (and
filters.status/type as needed) and call fetchParticipants(actualHackathonId, 1,
PAGE_SIZE, { search: debouncedSearch, status: filters.status === 'all' ?
undefined : filters.status, type: filters.type === 'all' ? undefined :
filters.type }); ensure cleanup of timers if using setTimeout.

Comment on lines +628 to +631
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className='flex flex-col space-y-1'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

RadioGroup uses defaultValue instead of value — programmatic updates won't reflect in the UI.

defaultValue is for uncontrolled mode and only takes effect on the initial render. The auto-select TEAM useEffect (line 608–612) calls form.setValue('participationType', 'TEAM'), but the RadioGroup won't visually update because it ignores subsequent field.value changes when using defaultValue.

🐛 Proposed fix
                    <RadioGroup
                      onValueChange={field.onChange}
-                     defaultValue={field.value}
+                     value={field.value}
                      className='flex flex-col space-y-1'
-                     disabled={false} // Always allow switching (validation handles team membership)
                    >
🤖 Prompt for AI Agents
In `@components/hackathons/submissions/SubmissionForm.tsx` around lines 628 - 631,
The RadioGroup is using defaultValue (uncontrolled) so programmatic updates via
form.setValue('participationType', 'TEAM') won't update the UI; change
RadioGroup to controlled mode by replacing defaultValue={field.value} with
value={field.value} and keep onValueChange={field.onChange} so the component
reflects form state updates from the useEffect that auto-selects TEAM and any
other programmatic changes.

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