-
Notifications
You must be signed in to change notification settings - Fork 75
Feat/minor ux fixes #380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/minor ux fixes #380
Conversation
…s, dedicated UI components like Family Wallet, and integration across the application.
…ation, and improved button state handling for connected/disconnected wallets.
…reen, and introduce a new timer component.
…atus check from 'Funding' to 'CAMPAIGNING'.
… the `WalletTrigger`, alongside minor landing page styling adjustments and code cleanup.
… is now handled by the backend.
…hing and add draft ID validation.
…n projects, refactor vote submission logic, and improve funding modal error handling.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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.
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:projectStatusprop is declared but unused.
projectStatusis defined inEmptyProps(line 7) and passed from the parent (line 363 ofindex.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 jsxblock uses plain CSS instead of TailwindCSS.This is pre-existing, but per coding guidelines, TailwindCSS classes should be used for styling. The
fadeInUpanimation could be replaced with Tailwind'sanimate-utilities or a custom animation intailwind.config. Low priority since it's not part of this PR's changes.
…able/grid views, filtering, pagination, and an enhanced submission review experience.
There was a problem hiding this 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 | 🟡 MinorBadge 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 atotalCountfield 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 />whenactiveTab === 'resources'and resources exist. The two conditions (resources?.length > 0vsresources?.[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 | 🟡 MinorRemove
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
organizationIdis in the dependency array but not passed togetMyTeam.Line 126 calls
getMyTeam(hackathonSlugOrId)withoutorganizationId, yetorganizationIdremains in theuseCallbackdependency array (line 139). This causes unnecessary callback re-creation without changing behavior.If dropping
organizationIdfrom 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
organizationIdto the call.package.json (1)
109-117:⚠️ Potential issue | 🟡 MinorRemove unused
motiondependency or complete migration to it.The codebase imports from
framer-motionin 31 files but doesn't import frommotionanywhere, even though both are listed in package.json.motion(v12+) is the successor toframer-motionand appears unused. Either remove themotiondependency to eliminate bloat, or migrate all imports fromframer-motiontomotion/reactand removeframer-motion.components/hackathons/submissions/SubmissionForm.tsx (2)
855-942:⚠️ Potential issue | 🔴 CriticalCritical bug: duplicate
case 1:— Media & Links step never renders.The
renderStepContentswitch has twocase 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 becase 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 | 🟠 MajorValidation 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 besetCurrentStep(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
introductionis 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 | 🟡 MinorConflicting Tailwind color classes:
text-cardandtext-white.Both
text-cardandtext-whiteset the CSScolorproperty. 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, removetext-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
submissionNameprop 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 | 🟡 MinorPotentially misleading
+suffix on exact comment count.
{submission.comments.toLocaleString()}+always appends a+, even when the count is exact (e.g.,0+or3+). 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
handleSharedoesn't handle clipboard API failure.
navigator.clipboard.writeTextreturns 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 | 🟡 MinorUndefined
voteTyperenders as a negative (red, rotated) vote.
voteTypeis optional in theVoterinterface (line 16), but the styling defaults to the negative branch (red +rotate-180) when it'sundefined. This means voters without avoteTypelook 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 | 🟡 MinorEmpty 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
CardFooterwith 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 | 🟡 MinorDuplicate
useEffectfor auto-selecting TEAM participation.Lines 227–232 and 607–611 contain identical effects that set
participationTypeto'TEAM'whenmyTeamexists. 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 | 🟡 MinorSocial icons are non-functional — misleading clickable appearance.
The
Githubandcursor-pointerand hover effects but noonClickhandler or navigation target. TheTeamMemberinterface 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
registeredAtisnull,undefined, or an invalid date string,new Date(date)producesInvalid Dateanddate-fnsformatwill throw aRangeError.🛡️ 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
.tsxfiles.♻️ 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 addingviewport={{ once: true }}to avoid repeated fade-ins on scroll.Inside a
ScrollArea,whileInViewre-triggers every time the element scrolls back into view, causing a repeated fade-in flicker. Addingviewport={{ 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?.organizationIdis no longer used inside the memo.Now that
organizationIdhas been removed from theuseTeamPostscall, 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 usinglink.urlas the key instead of array index.Using
indexas a React key is fragile if the list can be reordered or filtered. Since URLs should be unique within a submission's links,link.urlwould 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 ofcreateTeamAndInviteor part of the hook options for flexibility.components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx (1)
14-14: Remove speculative comment.
// Assuming cn utility is from hereshould 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.profileis partially populated (e.g., missingfirstName), 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 reusingtransformParticipantToSubmission.This same array-vs-number vote counting logic exists in
hooks/use-participant-submission.ts(lines 52–54). Consider extracting a shared helper or reusingtransformParticipantToSubmissionhere to avoid drift between the two implementations.components/project-details/project-voters/index.tsx (1)
165-166: Preferunknownoveranyfor the catch clause.Using
catch (err: any)bypasses type safety. Narrowing fromunknownis 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: Usecn()instead of template literal string concatenation for className composition.The file already imports
cnfrom other components' patterns. Usingcn()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:
cnis 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.
useTeamInviteis 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: anyundermines type safety across the component.The comment acknowledges this, but it propagates
anyinto all usages (status checks, field accesses, etc.) and causes theas anycast 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:
🧹 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.teamMembersis 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.
useRefis 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
DataTablePaginationworks, butonPaginationChange: (updater: any)should use the proper TanStack type (Updater<PaginationState>) to avoid theany.
app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
Outdated
Show resolved
Hide resolved
components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx
Show resolved
Hide resolved
components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx
Show resolved
Hide resolved
…athon team invite logic with better error handling.
…yment build steps in the CI workflow.
There was a problem hiding this 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 | 🔴 CriticalBug: Duplicate
case 1— Media & Links step is unreachable, Review step renders wrong content.The
switchstatement has twocase 1:blocks (Lines 855 and 941). JavaScript executes only the first match, so step index 2 (Media & Links) falls through tocase 2:which renders the Review content instead. Meanwhile, the secondcase 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 be0, 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 | 🟠 MajorValidation 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 —inviteesandform.teamMemberscan drift.
invitees(local state) andform.getValues('teamMembers')(form state) are maintained in parallel viahandleAddInviteeandhandleRemoveInvitee. If either gets out of sync (e.g., form reset at Line 261 clearsteamMembersbut notinvitees), the UI and submitted data will diverge.Consider deriving the invitee list from
form.watch('teamMembers')as the single source of truth, eliminating the separateinviteesstate.
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
constarrow functions overfunctiondeclarations.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 forSubmissionScreenWrapper.Same guideline applies here — prefer
constarrow 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_submittedstatus is defined but not exposed in the toolbar UI.The API accepts
'not_submitted'as a filter value, butParticipantToolbar.tsx(line 78-81) only offerssubmitted,shortlisted, anddisqualified. 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: Avoidany— type the pagination updater properly.
onPaginationChange: (updater: any)and theas anycasts 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
12page size is duplicated across lines 83, 124, and 173. Consider extracting it to a constant to keep it in sync withhookOptions.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 movinggetIconoutside the component to avoid re-creation on every render.Since
getIconhas 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 hereadds 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 addingonce: true.
whileInViewwithoutviewport={{ 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 theScrollAreacan 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' >
app/(landing)/organizations/[id]/hackathons/[hackathonId]/participants/page.tsx
Show resolved
Hide resolved
| submission={{ | ||
| id: selectedParticipant.id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find where GradeSubmissionModal is defined
fd -e tsx -e ts | xargs rg -l "GradeSubmissionModal" | head -20Repository: 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=tsxRepository: 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=tsxRepository: 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 -30Repository: 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 -100Repository: 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 -60Repository: 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 -40Repository: 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.
| 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.
components/organization/cards/ReviewSubmissionModal/SubmissionDetailsTab.tsx
Show resolved
Hide resolved
components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx
Outdated
Show resolved
Hide resolved
components/organization/cards/ReviewSubmissionModal/SubmissionVotesTab.tsx
Show resolved
Hide resolved
…ance participant filtering and review UI.
There was a problem hiding this 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-failurereferences the commented-outbuildjob, which will break the workflow.Line 357:
needs: [code-quality, build, security]still depends on thebuildjob, but that job is commented out at Line 63. GitHub Actions will fail to parse this workflow becausebuildis not a defined job ID.Remove
buildfrom theneedslist:Proposed fix
- needs: [code-quality, build, security] + needs: [code-quality, security]components/hackathons/submissions/SubmissionForm.tsx (2)
1125-1217:⚠️ Potential issue | 🟡 MinorReview 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
safeDatanever includesteamNameorteamMembers— new-team submissions silently lose this data.
safeDatais constructed at lines 510–549 withoutteamNameorteamMembers. Then lines 586 and 590 readsafeData.teamNameandsafeData.teamMembers, which are alwaysundefined. When a user creates a new team with invitees, this data is extracted into the form but never transferred tosafeData, so it's silently discarded during submission.The fix: add these fields to the
safeDataconstruction: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 | 🟡 MinorImport the canonical
Submissiontype from./types.tsinstead of re-declaring it locally.The local
Submissioninterface is incomplete and diverges from the canonical type intypes.ts. Specifically, it's missing the requiredsubmissionDate: stringfield, along with optional fields liketeamMembers,links,voters, andcommentsList. 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: Avoidas 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
anyto 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:handlePageChangeduplicates filter-mapping logic from the effect andhandleReviewSuccess.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: UsinguseReactTablesolely 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 ifDataTablePaginationis 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.
inviteeslocal state andform.teamMembersare updated in tandem inhandleAddInvitee/handleRemoveInvitee, but a form reset (e.g., frominitialData) won't clearinvitees. This can cause the badge list to show stale entries. Consider removing theinviteesstate and usingform.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: Useclsxfor 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: Preferconstarrow functions with explicit type annotations.Both
SubmissionFormContent(line 161) andSubmissionScreenWrapper(line 1303) usefunctiondeclarations. 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
indexas thekey(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 uniqueurl, 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 whenteamMembersis an empty array.Other sibling tabs (Links, Voters) render a dedicated empty state when data is absent. If
teamMembersis[], this component renders an empty grid with no user feedback. The parent guards withcurrentSubmission.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
TeamSectionsimilar to the other tabs.components/organization/cards/ReviewSubmissionModal/SubmissionModalHeader.tsx (1)
14-14: Remove speculative inline comment.
// Assuming cn utility is from hereadds 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
constarrow functions with explicit type annotations over function declarations for.tsxfiles.♻️ 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 allTabsContentnodes on every tab switch.The
motion.divkeyed onactiveTabwraps all fourTabsContentelements. 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
keyand animation props onto each individualTabsContentwrapper, or use a single content renderer that switches onactiveTab:♻️ 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/TabsContentdependency for the content area (keeping onlyTabsList/TabsTriggerfor the header), simplifying the rendering.
63-68: Sync effect is clean but consider addingsetActiveTab('details')here too.This effect fires when the modal opens or
currentIndexchanges from outside, resetting the internal index. The third effect (Line 71-73) resetsactiveTabwhencurrentSubmissionIndexchanges — but only ifsetCurrentSubmissionIndexactually triggers a state change. IfcurrentIndexequalscurrentSubmissionIndex, the state won't change andactiveTabwon'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.
| # 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <RadioGroup | ||
| onValueChange={field.onChange} | ||
| defaultValue={field.value} | ||
| className='flex flex-col space-y-1' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor