Conversation
…sidebar, and tabbed content sections including teams, announcements, winners, participants, and resources.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR redesigns the hackathon detail page architecture by migrating from local component state to React Query-based data fetching with server-side data seeding. A new composable component hierarchy replaces HackathonPageClient with Banner, Header, ActionButtons, Sidebar, and HackathonTabs components, enabling comprehensive team/announcement/submission/participant management with interactive features across multiple tabbed sections. Changes
Sequence DiagramsequenceDiagram
participant Server as Server
participant Page as HackathonPage
participant Provider as HackathonDataProvider
participant ReactQuery as React Query
participant Components as UI Components
Server->>Page: GET /hackathons/[slug] + initialHackathon
Page->>Provider: Render with initialData={hackathon}
Provider->>ReactQuery: setQueryData(hackathonKeys, initialData)
Provider->>Components: Provide currentHackathon via context
Components->>ReactQuery: useHackathon(slug) / useHackathonAnnouncements(slug)
ReactQuery-->>Components: Return seeded/cached data immediately
Components->>Components: Render with currentHackathon
Components->>ReactQuery: User action (join/invite/follow)
ReactQuery->>Server: POST mutation
Server-->>ReactQuery: Response
ReactQuery->>ReactQuery: Invalidate related query keys
ReactQuery-->>Components: Updated data
Components->>Components: Re-render with fresh state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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 Tip You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.Change the |
There was a problem hiding this comment.
Actionable comments posted: 4
Note
Due to the large number of review comments, Critical 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 (2)
components/hackathons/team-formation/TeamDetailsSheet.tsx (1)
247-260:⚠️ Potential issue | 🟡 MinorUse
unknowninstead ofanyin catch clause.As per coding guidelines, avoid using
anytype. For error handling, useunknownand narrow the type appropriately.🔧 Proposed fix
- } catch (err: any) { + } catch (err: unknown) { // Rollback on error setHiredRoles(prev => { const next = new Set(prev); if (wasHired) { next.add(skill); } else { next.delete(skill); } return next; }); - const errorMessage = err?.message || 'Failed to update role status'; + const errorMessage = err instanceof Error ? err.message : 'Failed to update role status'; toast.error(errorMessage);As per coding guidelines: "Do not use 'any' type; always search for proper Trustless Work entity types"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/TeamDetailsSheet.tsx` around lines 247 - 260, The catch block currently types the thrown value as "any" (catch (err: any)); change this to "unknown" and narrow it before use: update the catch signature to (err: unknown), then derive a safe errorMessage by checking the type (e.g., if (err instanceof Error) use err.message, else coerce to string or use a default). Keep the rollback logic that calls setHiredRoles (referencing wasHired and skill) as-is and pass the finalized errorMessage to toast.error to replace the current err?.message usage.components/hackathons/team-formation/CreateTeamPostModal.tsx (1)
143-168:⚠️ Potential issue | 🟠 MajorDon't drop
maxSizeandskillsat submit time.The form collects both fields, but
updatePayloadomitsmaxSizeand both payloads flattenlookingFordown to role names. As written, edited team-size changes never persist, and any per-role skills a user enters are lost after save/reopen.Also applies to: 193-208
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/CreateTeamPostModal.tsx` around lines 143 - 168, The submit path currently drops maxSize and per-role skills because the payload flattens lookingFor to names; update the submit/updatePayload logic (the function that builds the payload for create/update) to include form.getValues('maxSize') and to send lookingFor as the full array of role objects (preserving each role.skills array) rather than mapping to just role names; ensure handlers that call addSkill/removeSkill (e.g., addSkill, removeSkill) continue to update form.setValue('lookingFor') and that the payload uses that full lookingFor value so edited maxSize and per-role skills persist after save/reopen.
🟠 Major comments (26)
app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx-91-95 (1)
91-95:⚠️ Potential issue | 🟠 MajorCTA label and behavior are inconsistent for non-participants.
When
!isParticipant, the button text says “Register to Submit” but still routes directly to/submit. Route to registration (or rename the CTA to match actual behavior).Also applies to: 133-137
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx around lines 91 - 95, The CTA for non-participants is routing to submit while the label reads “Register to Submit”; update the onClick/route logic in the PoolAndAction component so when !isParticipant it navigates to the registration page (e.g., router.push(`/hackathons/${slug}/register`)) instead of `/submit`, and ensure the button label remains “Register to Submit”; apply the same fix to the other occurrence referenced (the similar branch around the other button handling) so both label and navigation are consistent.app/(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsx-38-46 (1)
38-46:⚠️ Potential issue | 🟠 Major
statusFilteris not connected to fetching, so the status dropdown is non-functional.The selected status never affects
useExploreSubmissions(...), so users see no change when picking status values.Also applies to: 132-136
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsx around lines 38 - 46, The status dropdown selection (statusFilter) is not passed into the data fetch, so update the useExploreSubmissions call in Submissions.tsx to include the selected status in the query params (e.g., add status: statusFilter === 'All Statuses' ? undefined : statusFilter) so the hook receives and reacts to status changes; also apply the same change to the other useExploreSubmissions invocation referenced in this file so both fetches honor statusFilter.app/(landing)/hackathons/[slug]/components/tabs/contents/participants/ParticipantCard.tsx-71-78 (1)
71-78:⚠️ Potential issue | 🟠 MajorAdd an accessible name to the icon-only message button.
The icon action has no accessible label, so assistive tech users won’t get a meaningful control name. Add
aria-label(for example,"Message participant").🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/participants/ParticipantCard.tsx around lines 71 - 78, The icon-only message button (BoundlessButton wrapping IconMessage, with onClick={onMessage}) lacks an accessible name; update the BoundlessButton element to include an aria-label (e.g., aria-label="Message participant") so screen readers get a meaningful control name—add the aria-label prop to the BoundlessButton that renders IconMessage and ensure the label text is descriptive for the participant action.components/hackathons/team-formation/ContactTeamModal.tsx-134-137 (1)
134-137:⚠️ Potential issue | 🟠 MajorHarden
window.openagainst reverse-tabnabbing.Opening links with
_blankshould includenoopener,noreferreras the third parameter to prevent the opened page from accessingwindow.opener. Update line 135 to:window.open(contactInfo, '_blank', 'noopener,noreferrer');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/ContactTeamModal.tsx` around lines 134 - 137, In the onClick handler inside ContactTeamModal (the arrow function that calls window.open and onTrackContact), harden the window.open call against reverse-tabnabbing by passing the third parameter 'noopener,noreferrer' so replace the current window.open(contactInfo, '_blank') invocation with window.open(contactInfo, '_blank', 'noopener,noreferrer'); keep the subsequent onTrackContact?.(id) call unchanged and ensure you're updating the onClick block where contactInfo, onTrackContact and id are referenced.app/(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsx-157-162 (1)
157-162:⚠️ Potential issue | 🟠 MajorReplace
anyand implement theonViewClickhandler before merge.Line 157 uses
anyinstead of the properExploreSubmissionsResponsetype. Line 161 has a TODO placeholder with an empty function instead of handling the click action—it should callonViewClick(sub.id)to navigate to the submission detail.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsx around lines 157 - 162, The map callback is typed as any and the SubmissionCard click handler is a noop; change the parameter type in the submissions.map callback to ExploreSubmissionsResponse (or the correct exported type used for API responses) and wire the SubmissionCard prop to call onViewClick(sub.id) instead of the empty function; update imports if necessary and ensure the component receiving onViewClick (SubmissionCard) expects a (id: string) => void signature so clicking navigates to the submission detail.app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx-82-84 (1)
82-84:⚠️ Potential issue | 🟠 MajorUse the
Participanttype instead ofanyin participant membership check.Line 83 uses
(p: any)which violates the coding guideline. Replace with the properParticipanttype fromtypes/hackathon/participant.ts. TheParticipantinterface has the requireduserIdfield for the comparison.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx around lines 82 - 84, Replace the usage of the any type in the membership check by importing and using the Participant interface from types/hackathon/participant.ts; update the participants array typing (or the callback) so isParticipant uses participants.some((p: Participant) => p.userId === user.id) and add the corresponding import for Participant at the top of the file, ensuring the code references the Participant type instead of any.components/ui/popover-cult.tsx-208-244 (1)
208-244:⚠️ Potential issue | 🟠 MajorGive
PopoverTextareaa real accessible label.
PopoverLabelis hidden from assistive tech and isn't associated with the textarea, so screen readers get an unnamed field. Please make the label programmatic or acceptaria-label/aria-labelledbyon the textarea.One way to wire the existing label to the textarea
export function PopoverLabel({ children, className }: PopoverLabelProps) { const { uniqueId, note } = usePopover(); return ( - <motion.span + <motion.label layoutId={`popover-label-${uniqueId}`} - aria-hidden='true' + htmlFor={`${uniqueId}-textarea`} style={{ opacity: note ? 0 : 1, }} className={cn( 'absolute top-3 left-4 text-sm text-zinc-500 select-none dark:text-zinc-400', className )} > {children} - </motion.span> + </motion.label> ); } @@ export function PopoverTextarea({ className }: PopoverTextareaProps) { - const { note, setNote } = usePopover(); + const { note, setNote, uniqueId } = usePopover(); return ( <textarea + id={`${uniqueId}-textarea`} className={cn( 'h-full w-full resize-none rounded-md bg-transparent px-4 py-3 text-sm outline-none', className )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/popover-cult.tsx` around lines 208 - 244, PopoverTextarea is currently unlabeled for assistive tech because PopoverLabel is aria-hidden and not associated with the textarea; fix by making the label programmatically associated using the existing uniqueId from usePopover: update PopoverLabel to render a real label element (or a span with an id) using id={`popover-label-${uniqueId}`} and remove aria-hidden, then update PopoverTextarea to accept an optional ariaLabel/ariaLabelledby prop and default aria-labelledby to `popover-label-${uniqueId}` (or use aria-label if provided); use the same uniqueId and the usePopover hook (uniqueId, note, setNote) to wire the association so screen readers see the label.lib/api/hackathons/teams.ts-122-123 (1)
122-123:⚠️ Potential issue | 🟠 MajorReplace
anytypes in legacy compatibility aliases with proper type definitions.The
getTeamPosts()andupdateTeamPost()compatibility aliases useanytypes, bypassing the typed request handling introduced in this file. Replace them with their corresponding types:
options: GetTeamOptions(already defined in this file)data: UpdateTeamRequest(already defined in this file)Proposed fix
-export const getTeamPosts = async (hackathonId: string, options?: any) => +export const getTeamPosts = async ( + hackathonId: string, + options?: GetTeamOptions +) => getTeams(hackathonId, options); @@ -export const updateTeamPost = async (id: string, teamId: string, data: any) => +export const updateTeamPost = async ( + id: string, + teamId: string, + data: UpdateTeamRequest +) => updateTeam(id, teamId, data);Also applies to: 166-167
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/hackathons/teams.ts` around lines 122 - 123, Change the legacy compatibility aliases to use the proper typed request types instead of any: update the signature of getTeamPosts to use options: GetTeamOptions (replace options?: any) and update updateTeamPost to use data: UpdateTeamRequest (replace data: any); ensure the functions still delegate to getTeams and updateTeam respectively and import/keep the existing GetTeamOptions and UpdateTeamRequest types referenced in the file (symbols: getTeamPosts, getTeams, updateTeamPost, updateTeam, GetTeamOptions, UpdateTeamRequest).components/common/SharePopover.tsx-43-58 (1)
43-58:⚠️ Potential issue | 🟠 MajorProtect the opener when launching third-party share windows.
Both
handleTwitterShareandhandleLinkedinShareusewindow.open(..., '_blank')withoutnoopener,noreferrer, leaving the current tab exposed to reverse-tabnabbing attacks. Third-party pages opened via_blankcan accesswindow.openerto navigate the original tab.Add a third parameter with the security specs:
Proposed fix
const handleTwitterShare = () => { window.open( `https://twitter.com/intent/tweet?text=${encodeURIComponent( shareTitle )}&url=${encodeURIComponent(shareUrl)}`, '_blank', + 'noopener,noreferrer' ); }; @@ const handleLinkedinShare = () => { window.open( `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent( shareUrl )}`, '_blank', + 'noopener,noreferrer' ); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/common/SharePopover.tsx` around lines 43 - 58, Both handleTwitterShare and handleLinkedinShare open new tabs with '_blank' which exposes window.opener; update both window.open calls to pass the third parameter with security specs "noopener,noreferrer" (e.g., window.open(url, '_blank', 'noopener,noreferrer')) and, for extra safety, null out newWindow.opener if a reference is returned. Modify the window.open usage inside the functions handleTwitterShare and handleLinkedinShare accordingly.lib/api/hackathons/teams.ts-51-56 (1)
51-56:⚠️ Potential issue | 🟠 MajorDon't ship the new team API surface with exported
anytypes.The
contactInfo,hackathon,invitee, andinviterfields are part of the public API contract. Replace them with proper types:
- Line 55:
contactInfo: any→contactInfo: string- Lines 86-88: Use
Hackathon(already imported),Userfor invitee/inviterAdditionally, line 122's
options?: anyparameter should use the existingGetTeamOptionstype instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/hackathons/teams.ts` around lines 51 - 56, Replace exported any types in the teams API interfaces: change CreateTeamRequest.contactInfo from any to string, update any occurrences of the types for hackathon/invitee/inviter to use the existing Hackathon and User types (they are already imported) where those fields are exported, and replace the options?: any parameter with the GetTeamOptions type (use GetTeamOptions instead of any) so the public API surface uses concrete types (refer to CreateTeamRequest.contactInfo, the hackathon/invitee/inviter fields, and the options parameter).hooks/hackathon/use-team-posts.ts-149-154 (1)
149-154:⚠️ Potential issue | 🟠 MajorKeep
myTeamin sync when these mutations succeed.After switching this hook over to
Team, these responses represent the same entity returned bygetMyTeam, but onlypostsandmyPostsare updated. Any consumer readingmyTeamwill stay stale until a refetch.Suggested fix
try { const response = await createTeamPost(hackathonSlugOrId, data); if (response.success && response.data) { - setPosts(prev => [response.data!, ...prev]); - setMyPosts(prev => [response.data!, ...prev]); + setPosts(prev => [response.data, ...prev]); + setMyPosts(prev => [response.data, ...prev]); + setMyTeam(response.data); toast.success('Team post created successfully'); return response.data; } else { throw new Error(response.message || 'Failed to create team post'); } @@ try { const response = await updateTeamPost(hackathonSlugOrId, postId, data); if (response.success && response.data) { setPosts(prev => - prev.map(post => (post.id === postId ? response.data! : post)) + prev.map(post => (post.id === postId ? response.data : post)) ); setMyPosts(prev => - prev.map(post => (post.id === postId ? response.data! : post)) + prev.map(post => (post.id === postId ? response.data : post)) ); + setMyTeam(prev => (prev?.id === postId ? response.data : prev)); toast.success('Team post updated successfully'); return response.data; } else { throw new Error(response.message || 'Failed to update team post'); }Also applies to: 185-193
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/hackathon/use-team-posts.ts` around lines 149 - 154, The createTeamPost response updates setPosts and setMyPosts but leaves myTeam stale; after a successful response in the createTeamPost success branch (and the analogous branch at lines 185-193), also update the myTeam state to include the new post in its posts array (merge by spreading the previous myTeam and prepending response.data to myTeam.posts) so consumers of getMyTeam see the new post without a refetch; reference the createTeamPost success handling and the myTeam state updater (e.g., setMyTeam) when making this change.app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx-28-28 (1)
28-28:⚠️ Potential issue | 🟠 MajorThe skill filter is currently a non-functional placeholder.
Changing
skillFilteronly updates the button label; it never affects the request or the rendered list, and the options are hard-coded mock data. Either wire this to real participant data/API support or remove the control until it works.As per coding guidelines,
Always ensure final code is fully functional with no placeholders, TODOs, or missing parts.Also applies to: 48-57, 82-111, 146-173
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx at line 28, The skill filter control in the Participants component is only updating UI text (skillFilter/setSkillFilter) but not affecting data; update Participants.tsx so the skillFilter state is used to request and render filtered participants: either add skillFilter as a dependency to the participants loader (e.g., include it in the fetchParticipants or loadParticipants call used in the component) and pass it as a query param to the API, or if the backend lacks support, derive filtered list client-side by computing participants.filter(p => matchesSkill(skillFilter)); replace hard-coded options with a dynamic list (e.g., derive unique skills from fetched participants into skills state) or remove the entire control if you cannot implement filtering now; ensure the effect that fetches participants (or the render logic that maps participants) references the updated symbols skillFilter, setSkillFilter, and the fetch function so changing the filter updates the displayed list.lib/providers/hackathonProvider.tsx-77-114 (1)
77-114:⚠️ Potential issue | 🟠 MajorThe provider drops loading/error from two of the queries it exposes.
exploreSubmissionsandwinnerscan still be fetching or fail while context reportsloading = falseanderror = null, which makes those sections indistinguishable from genuine empty states for consumers. Fold theirisLoading/errorvalues into the aggregated context state.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/providers/hackathonProvider.tsx` around lines 77 - 114, The context currently ignores loading/error from useExploreSubmissions and useHackathonWinners so consumers see false/no-error when those queries are still pending or failed; update the aggregation to pull isLoading and error from useExploreSubmissions and useHackathonWinners (alongside hackathonLoading and submissionsLoading and hackathonError) and compute loading = hackathonLoading || submissionsLoading || exploreLoading || winnersLoading, and error = first non-null of hackathonError, submissionsError, exploreError, winnersError (or combine messages) so exploreSubmissions, winners and the context consumers reflect their real fetch state; modify the existing variables around exploreSubmissionsData, winners and the exported loading/error values to include these new flags.app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx-29-40 (1)
29-40:⚠️ Potential issue | 🟠 Major“Load More” is replacing the grid instead of appending.
Incrementing
pageswaps the query to page N, but the component only rendersparticipantsData.participantsfrom that single page. This also needs to resetpageback to 1 when either filter changes, otherwise a new filter can start on an arbitrary later page.Also applies to: 59-63, 146-197
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx around lines 29 - 40, The component currently only renders participantsData.participants for the current page (from useHackathonParticipants) causing “Load More” to replace the grid and filters to start on the wrong page; fix this by keeping an accumulatedParticipants state and updating it whenever participantsData changes: when page === 1 replace accumulatedParticipants with participantsData.participants, otherwise append participantsData.participants to accumulatedParticipants; ensure the render uses accumulatedParticipants instead of participantsData.participants. Also reset page to 1 and clear accumulatedParticipants whenever any filter (e.g. statusFilter or search/filter props used in useHackathonParticipants) changes by calling setPage(1) and setAccumulatedParticipants([]) in the filter-change handlers or in a useEffect watching those filters so new filters start at the first page.hooks/hackathon/use-hackathon-queries.ts-143-158 (1)
143-158:⚠️ Potential issue | 🟠 MajorInclude
paramsin the submission query cache key to prevent stale data when filters change.The
useHackathonSubmissionshook acceptspage,limit,status, andsortparameters but the cache key only includes the slug. Changing filters while keeping the same slug will incorrectly reuse the cached data from the previous filter state.This pattern is already used correctly in similar hooks like
useHackathonParticipants(line 115) anduseHackathonTeams(line 244), which include their params in the query key.Suggested fix
Update
hackathonKeys.submissionsto accept params and include them in the cache key:- submissions: (slug: string) => ['hackathon', 'submissions', slug] as const, + submissions: (slug: string, params?: any) => ['hackathon', 'submissions', slug, params] as const,Then update the hook to pass params:
- queryKey: hackathonKeys.submissions(slug), + queryKey: hackathonKeys.submissions(slug, params),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/hackathon/use-hackathon-queries.ts` around lines 143 - 158, The submissions cache key must include the filter params to avoid stale results: update hackathonKeys.submissions to accept the params object (page, limit, status, sort) and include it in the returned key tuple, then change useHackathonSubmissions to pass the params into hackathonKeys.submissions(slug, params) so the queryKey reflects filter changes; keep the enabled and queryFn logic the same and ensure the params object used is the same shape as the hook signature so equality checks work correctly.lib/providers/hackathonProvider.tsx-106-111 (1)
106-111:⚠️ Potential issue | 🟠 MajorUse one identifier for the winners cache key.
The
useHackathonWinnersquery is keyed bycurrentHackathon.id, butuseRefreshHackathoninvalidates viahackathonKeys.winners(hackathonSlug). These are different cache entries, so manual refresh operations will not update the winners data unless both identifiers happen to be identical strings.Use
hackathonSlugconsistently for both the query and refresh operation:Suggested fix
- const { data: winners = [] } = useHackathonWinners( - currentHackathon?.id ?? '', - !!currentHackathon?.id - ); + const { data: winners = [] } = useHackathonWinners( + hackathonSlug, + !!hackathonSlug + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/providers/hackathonProvider.tsx` around lines 106 - 111, The winners query and refresh use different cache keys; update the call to useHackathonWinners to use hackathonSlug (not currentHackathon?.id) so it shares the same cache key as hackathonKeys.winners(hackathonSlug) and useRefreshHackathon; e.g., call useHackathonWinners(hackathonSlug, !!hackathonSlug) instead of useHackathonWinners(currentHackathon?.id ?? '', !!currentHackathon?.id) so useHackathonWinners, hackathonKeys.winners, and useRefreshHackathon all operate on the same identifier.hooks/hackathon/use-hackathon-queries.ts-394-400 (1)
394-400:⚠️ Potential issue | 🟠 MajorFix cache invalidation for team invitation mutations to use actual teamId.
Both
useInviteToTeamanduseInvitationActions.cancelActioninvalidate the team invitations cache with an emptyteamId(hackathonKeys.teamInvitations(slug, '')), which fails to match queries created byuseTeamInvitations(slug, teamId, ...). This leaves sent/cancelled invitations stale in the cache.
useInviteToTeamreceivesteamIdin params but doesn't use it in invalidationuseInvitationActions.cancelActionneeds its mutation signature updated to acceptteamIdso the caller can provide it for proper cache invalidationUpdate both mutations to pass the actual
teamIdtohackathonKeys.teamInvitations():Suggested fix
const cancelAction = useMutation({ - mutationFn: (inviteId: string) => cancelInvitation(slug, inviteId), - onSuccess: (_, inviteId) => { + mutationFn: ({ teamId, inviteId }: { teamId: string; inviteId: string }) => + cancelInvitation(slug, inviteId), + onSuccess: (_, { teamId }) => { queryClient.invalidateQueries({ - queryKey: hackathonKeys.teamInvitations(slug, ''), - }); // Simplified, usually we'd pass teamId if we had it + queryKey: hackathonKeys.teamInvitations(slug, teamId), + }); }, }); @@ return useMutation({ mutationFn: (params: { teamId: string; inviteeIdentifier: string; message?: string; @@ - onSuccess: () => { + onSuccess: (_, { teamId }) => { queryClient.invalidateQueries({ - queryKey: hackathonKeys.teamInvitations(slug, ''), + queryKey: hackathonKeys.teamInvitations(slug, teamId), }); }, }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/hackathon/use-hackathon-queries.ts` around lines 394 - 400, The cache invalidation is using an empty teamId (hackathonKeys.teamInvitations(slug, '')) so invitations stay stale; update useInviteToTeam to call queryClient.invalidateQueries(hackathonKeys.teamInvitations(slug, teamId)) using the teamId parameter it already receives, and change useInvitationActions.cancelAction mutation signature to accept the teamId along with inviteId (e.g., mutationFn: ({ teamId, inviteId }) => cancelInvitation(slug, inviteId)) and then invalidate with hackathonKeys.teamInvitations(slug, teamId); ensure these keys match useTeamInvitations(slug, teamId, ...) so the correct cache entry is invalidated.app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx-13-18 (1)
13-18:⚠️ Potential issue | 🟠 MajorUse
hackathon?.isParticipantinstead of checking pagination-limited participant data.
useHackathonParticipants(slug)only fetches page 1 (12 users by default), so users registered outside that slice incorrectly see the JOIN CTA. TheuseHackathonhook already provides the viewer-scopedisParticipantflag—use it directly. Remove theuseHackathonParticipantsimport and theanycast on line 43, which masks the properly-typeduserIdfield on the Participant interface.import { useHackathon, useMyTeam, - useHackathonParticipants, useJoinHackathon, useLeaveHackathon, } from '@/hooks/hackathon/use-hackathon-queries'; @@ const { data: hackathon } = useHackathon(slug); - const { data: myTeam } = useMyTeam(slug); - const { data: participantsData } = useHackathonParticipants(slug); - const participants = participantsData?.participants || []; + const { data: myTeam } = useMyTeam(slug, !!user); @@ - const isParticipant = user - ? participants.some((p: any) => p.userId === user.id) - : false; + const isParticipant = !!user && !!hackathon?.isParticipant;Also applies to: 34–44
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/header/ActionButtons.tsx around lines 13 - 18, Replace the pagination-limited participant check with the viewer-scoped flag from the hackathon object: remove the useHackathonParticipants import and any references to it, and in ActionButtons use useHackathon(... )'s hackathon?.isParticipant to determine participation instead of scanning participants (remove the any cast that masked Participant.userId). Keep useJoinHackathon and useLeaveHackathon usage but wire their enabled/disabled logic to hackathon?.isParticipant (and hackathon?.id if needed) so the JOIN/LEAVE CTAs reflect the true viewer state.hooks/hackathon/use-hackathon-queries.ts-45-63 (1)
45-63:⚠️ Potential issue | 🟠 MajorReplace
anytypes with proper typed parameters in cache keys.The
participantsandteamscache key builders useanytype for optional params, violating the coding guidelines (do not useany; always search for proper Trustless Work entity types). Additionally,useHackathonSubmissionsacceptsparamsbut the queryKey only includesslug, so different page/filter/sort combinations share the same cache entry, causing stale data issues.Use the already-imported
GetTeamOptionstype for teams, define a proper type for participants params, and include params in the submissions cache key:Suggested fix
+type ParticipantParams = { + page?: number; + limit?: number; + status?: string; +}; export const hackathonKeys = { all: ['hackathon'] as const, detail: (slug: string) => ['hackathon', 'detail', slug] as const, - participants: (slug: string, params?: any) => - ['hackathon', 'participants', slug, params] as const, + participants: (slug: string, params?: ParticipantParams) => + ['hackathon', 'participants', slug, params] as const, submissions: (slug: string) => ['hackathon', 'submissions', slug] as const, + submissionsList: (slug: string, params?: { page?: number; limit?: number; status?: string; sort?: string }) => + ['hackathon', 'submissions', slug, params] as const, ... - teams: (idOrSlug: string, params?: any) => - ['hackathon', 'teams', idOrSlug, params] as const, + teams: (idOrSlug: string, params?: GetTeamOptions) => + ['hackathon', 'teams', idOrSlug, params] as const,Then update
useHackathonSubmissionsto usehackathonKeys.submissionsList(slug, params)instead ofhackathonKeys.submissions(slug).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hooks/hackathon/use-hackathon-queries.ts` around lines 45 - 63, Update the hackathonKeys to remove any: replace teams' params?: any with the existing GetTeamOptions type (teams: (idOrSlug: string, params?: GetTeamOptions) => ...) and create/use a proper typed alias for participants params (e.g., ParticipantsQueryParams) instead of any in participants: (slug: string, params?: ParticipantsQueryParams) => ...; add a submissionsList key that includes params (submissionsList: (slug: string, params?: SubmissionsQueryParams) => ['hackathon','submissions',slug,params] as const) and keep the existing submissions slug-only key if needed, then update useHackathonSubmissions to use hackathonKeys.submissionsList(slug, params) so different filters/pages produce distinct cache keys.app/(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsx-31-40 (1)
31-40:⚠️ Potential issue | 🟠 MajorToast message shows stale state.
isFollowingreflects the state beforetoggleFollow()completes. The toast will show the opposite of what actually happened (e.g., shows "Unfollowed" when user just followed).Also, avoid using
anytype per coding guidelines.🐛 Suggested fix
const handleToggleFollow = async () => { + const wasFollowing = isFollowing; try { await toggleFollow(); toast.success( - isFollowing ? `Unfollowed ${orgName}` : `Following ${orgName}` + wasFollowing ? `Unfollowed ${orgName}` : `Now following ${orgName}` ); - } catch (error: any) { - toast.error(error.message || 'Failed to update follow status'); + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to update follow status'; + toast.error(message); } };As per coding guidelines: "Do not use 'any' type".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsx around lines 31 - 40, handleToggleFollow shows a stale toast because it uses isFollowing (pre-toggle) and declares error as any; change handleToggleFollow to derive the post-toggle state from the toggleFollow result (or invert isFollowing only after toggle resolves) and use that derived boolean to build the toast message (e.g., newIsFollowing ? `Following ${orgName}` : `Unfollowed ${orgName}`); also replace catch(error: any) with catch(error: unknown) and extract a safe message (e.g., via String(error) or (error as Error).message) before calling toast.error so you don't use the any type.app/(landing)/hackathons/[slug]/components/tabs/index.tsx-32-33 (1)
32-33:⚠️ Potential issue | 🟠 MajorDon't invalidate
?tab=announcementsbefore the announcements query finishes.On a direct visit to
?tab=announcements, the first render seesannouncements = [], drops that tab fromhackathonTabs, and this effect immediately rewrites the URL tooverview. By the time the query resolves, the original deep link is already lost.Also applies to: 76-85, 144-160
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/index.tsx around lines 32 - 33, The announcements query defaulting to an empty array causes the announcements tab to be considered empty before data loads and triggers URL rewrites; change the usage of useHackathonAnnouncements in this component (and the related logic that builds hackathonTabs and the effect that syncs the URL) to treat announcements as undefined until the query finishes (or use the query's isLoading/isFetching flag) instead of defaulting to [] so the announcements tab is preserved on initial render and the effect does not prematurely push the user to "overview"; update references around useHackathonAnnouncements, hackathonTabs construction, and the URL-sync effect to check for undefined or loading state rather than an empty array (also apply the same change to the other occurrences noted).app/(landing)/hackathons/[slug]/HackathonPageClient.tsx-85-89 (1)
85-89:⚠️ Potential issue | 🟠 MajorHold the tab redirect until announcements have resolved.
This version has the same race:
announcementsstarts as[], so?tab=announcementsis treated as invalid and replaced withoverviewbefore the query can populate the tab list.Also applies to: 123-132, 309-331
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/HackathonPageClient.tsx around lines 85 - 89, The tab-redirection races because announcements is defaulted to [] so the code treats ?tab=announcements as invalid before the query resolves; update the logic that reads useHackathonAnnouncements (the hook call where you currently do const { data: announcements = [] } = useHackathonAnnouncements(...)) to either (a) stop defaulting data to []—i.e., destructure const { data: announcements, isLoading: isLoadingAnnouncements } = useHackathonAnnouncements(...) and treat announcements as undefined while loading—or (b) keep the default but consult the hook's loading flag (isLoading/isFetching) before performing the tab redirect so the redirect waits until isLoadingAnnouncements is false; apply the same change to the other occurrences that use useHackathonAnnouncements and the tab-redirect logic.app/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx-77-99 (1)
77-99:⚠️ Potential issue | 🟠 MajorSplit lookup failures from invitation failures.
getUserProfileByUsername(...)andinviteMutation.mutateAsync(...)share the same catch block, so a real invite rejection is currently shown as “Failed to verify user”. That is misleading and hides the server's actual invite error.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx around lines 77 - 99, The current single try/catch mixes failures from getUserProfileByUsername and inviteMutation.mutateAsync so invite rejections are misreported as verification failures; split the logic into two distinct steps: first call getUserProfileByUsername and handle its 404/user-not-found case (using setVerificationError and setIsVerifying) in its own try/catch, then call inviteMutation.mutateAsync in a separate try/catch that sets a different, more specific invite error (use err.response?.status and err.response?.data?.message when available) and only clears inputs (setInviteIdentifier, setInviteMessage, setVerificationError) on successful invite; ensure setIsVerifying is toggled appropriately around each operation.app/(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsx-38-54 (1)
38-54:⚠️ Potential issue | 🟠 MajorMake the category and role controls actually filter the list.
categoryFilterandroleFilteronly change the dropdown labels.useHackathonTeams(...)still fetches bypage/search/openOnly, so both controls are currently no-ops for users.As per coding guidelines, "Always ensure final code is fully functional with no placeholders, TODOs, or missing parts".
Also applies to: 132-189
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsx around lines 38 - 54, categoryFilter and roleFilter only change the UI labels but are not passed to useHackathonTeams, so the list isn't actually filtered; update the hook call to include category and role filter parameters derived from categoryFilter and roleFilter (e.g., pass category: categoryFilter !== 'All Categories' ? categoryFilter : undefined and role: roleFilter !== 'Role' ? roleFilter : undefined) and ensure the dropdown change handlers (setCategoryFilter, setRoleFilter) reset page to 1 (setPage(1)) so new filters start at the first page; apply the same fix to the other filtering block in this file that mirrors this logic.components/hackathons/team-formation/CreateTeamPostModal.tsx-215-217 (1)
215-217:⚠️ Potential issue | 🟠 MajorSurface save failures in the modal.
The catch block only logs to the console. If
createPostorupdatePostrejects, the user gets no toast or inline error and has no clue why nothing happened.Minimal fix
- } catch (err) { - console.error('Failed to save team post:', err); + } catch (err) { + toast.error( + err instanceof Error ? err.message : 'Failed to save team post' + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/CreateTeamPostModal.tsx` around lines 215 - 217, The catch block in CreateTeamPostModal.tsx currently only console.errors failures from createPost/updatePost so users get no feedback; update the catch in the save handler (the function calling createPost and updatePost) to set any existing "isSaving"/loading flag to false and surface the failure to the user by calling the app's toast/error notifier or by setting a local error state (e.g., setSaveError) that the modal renders inline, and include the caught error message in the notification; ensure the handler still closes or keeps the modal open appropriately only after a successful save.app/(landing)/hackathons/[slug]/components/tabs/contents/resources/index.tsx-25-26 (1)
25-26:⚠️ Potential issue | 🟠 MajorReplace
anyand@ts-ignorewith proper Trustless Work entity types.The
HackathonResourceItemandResourceCardPropstypes are available and should be used throughout this file. Usinganyand@ts-ignorebypasses type checking at the API-to-UI boundary, where schema or props drift can only be caught at runtime.Update the function signatures:
- Line 25:
mapApiResource(resource: HackathonResourceItem): ResourceCardProps- Line 154:
filter((r: ResourceCardProps) => ...)- Line 163: Remove
anyparameter typeAlso remove the
@ts-ignorecomment andas anyassertion on lines 148–150; theisComingSoonproperty should extendResourceCardPropsthrough proper typing.Also applies to: 140–150, 153–164
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/resources/index.tsx around lines 25 - 26, Change the looser types and assertions to the concrete Trustless Work types: update the mapApiResource signature to mapApiResource(resource: HackathonResourceItem): ResourceCardProps and return a properly typed ResourceCardProps instead of any; update the array filter to filter((r: ResourceCardProps) => ...) and remove the any parameter type from the subsequent map/handlers that consume these items; remove the `@ts-ignore` and the as any assertion around the isComingSoon construction and instead ensure the object you create extends ResourceCardProps (add isComingSoon as an optional prop on ResourceCardProps or compose the object so TypeScript infers ResourceCardProps). Locate usages by the function name mapApiResource and by the array filter/map that currently use any/@ts-ignore and replace them with the concrete types HackathonResourceItem and ResourceCardProps.
🟡 Minor comments (9)
app/(landing)/hackathons/[slug]/components/tabs/contents/winners/MainStageHeader.tsx-6-6 (1)
6-6:⚠️ Potential issue | 🟡 MinorRemove stray
sfrom className.The className contains
swhich appears to be a typo or leftover from editing.🐛 Proposed fix
- <div className='s flex h-8 w-8 items-center justify-center'> + <div className='flex h-8 w-8 items-center justify-center'>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/winners/MainStageHeader.tsx at line 6, The className in the MainStageHeader component contains a stray "s " token ("className='s flex h-8 w-8 items-center justify-center'"); remove the stray "s " so the className reads "flex h-8 w-8 items-center justify-center" to avoid unintended styling/invalid class names and restore the intended layout.components/hackathons/team-formation/TeamRecruitmentPostCard.tsx-284-284 (1)
284-284:⚠️ Potential issue | 🟡 MinorAvoid using
anytype.As per coding guidelines, avoid using
anytype. Theleadervariable already has a type from the resolution logic. Consider defining a proper type or using type narrowing instead.🔧 Proposed fix
- const displayLeader = leader as any; + const displayLeader = leader as { id?: string; name?: string; username?: string; image?: string } | undefined;Or define a
DisplayLeaderinterface that covers both member object shapes and thepost.leadershape.As per coding guidelines: "Do not use 'any' type; always search for proper Trustless Work entity types"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/TeamRecruitmentPostCard.tsx` at line 284, Replace the unsafe cast "const displayLeader = leader as any" with a proper typed value: identify the actual shapes involved (e.g., the member shape and the post.leader shape) and create a union/interface (e.g., DisplayLeader) that covers both, or use type narrowing/type guards to discriminate the variants before assigning to displayLeader; update usages of displayLeader to rely on the new DisplayLeader type (or narrowed type) rather than any so the compiler enforces correct properties for leader/post.leader handling.components/ui/popover-cult.tsx-106-121 (1)
106-121:⚠️ Potential issue | 🟡 MinorAdd
type="button"to prevent accidental form submission on reusable button primitives.Buttons without an explicit
typeattribute default totype="submit". IfPopoverTriggerorPopoverButtonare used within or near a form, clicking them will submit the form instead of triggering their onClick handlers.Proposed fix
export function PopoverTrigger({ children, className }: PopoverTriggerProps) { const { openPopover, uniqueId } = usePopover(); return ( <motion.button + type='button' key='button' layoutId={`popover-${uniqueId}`} @@ export function PopoverButton({ children, onClick, className, }: { @@ return ( <button + type='button' className={cn( 'flex w-full items-center gap-2 rounded-md px-4 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-700', className )}Also applies to: 342-350
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/popover-cult.tsx` around lines 106 - 121, The motion.button elements (the Popover trigger buttons using layoutId `popover-${uniqueId}` and span `popover-label-${uniqueId}`) lack an explicit type and will default to submit inside forms; update those reusable button primitives (the motion.button that calls openPopover and the other similar motion.button at the later block) to include type="button" so clicks don't accidentally submit surrounding forms—add the type attribute to the motion.button JSX where openPopover is used and to the matching button instance around lines referenced.lib/providers/hackathonProvider.tsx-86-104 (1)
86-104:⚠️ Potential issue | 🟡 MinorRemove the
as anyescape hatch from status mapping.
SubmissionCardProps['status']has a finite type contract ('Pending' | 'Approved' | 'Rejected'), and the mapping already returns only valid members of that union. Theas anycoercion hides type mismatches instead of surfacing them. Create a typed mapper function that explicitly returns the correct type without type erasure.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/providers/hackathonProvider.tsx` around lines 86 - 104, The status mapping uses an "as any" escape hatch; replace it with a small typed mapper function (e.g., function mapSubmissionStatus(raw?: string): SubmissionCardProps['status']) that explicitly returns only 'Pending' | 'Approved' | 'Rejected' based on raw?.toUpperCase() === 'SHORTLISTED' / 'DISQUALIFIED' and defaults to 'Pending', then call mapSubmissionStatus(s.status) in the exploreSubmissions mapping and remove the as any cast so the compiler enforces the union type.app/(landing)/hackathons/[slug]/components/header/index.tsx-20-20 (1)
20-20:⚠️ Potential issue | 🟡 MinorAdd null safety for
_countaccess.
hackathon._count.participantsmay throw if_countis undefined. Consider adding optional chaining with a fallback.🛡️ Suggested fix
- participantCount={hackathon._count.participants} + participantCount={hackathon._count?.participants ?? 0}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/header/index.tsx at line 20, The participant count access can throw if hackathon._count is undefined; update the participantCount prop assignment for the header component to safely read hackathon._count.participants using optional chaining with a fallback (e.g., treat missing _count as 0) so the component uses a safe default when _count is absent.app/(landing)/hackathons/[slug]/components/tabs/Lists.tsx-41-45 (1)
41-45:⚠️ Potential issue | 🟡 MinorBadge active-state styling won't apply.
The badge uses
group-data-[state=active]:classes, but the parentTabsTriggerdoesn't have thegroupclass. The active state styles won't be applied to the badge.🐛 Suggested fix
Add
groupclass to the TabsTrigger:<TabsTrigger key={id} value={id} className={[ + 'group', 'relative shrink-0 rounded-none border-0 bg-transparent px-0! pt-0! pb-0!',🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/Lists.tsx around lines 41 - 45, The badge's active-state styles (group-data-[state=active]:*) won't work because the parent TabsTrigger isn't a group; update the TabsTrigger component (where TabsTrigger is rendered around the badge in Lists.tsx) to include the "group" class on its className so the badge's group-data-[state=active]:... utilities can apply, and ensure the badge span remains a descendant of that TabsTrigger so the active-state styling takes effect.app/(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsx-70-72 (1)
70-72:⚠️ Potential issue | 🟡 MinorAvoid
href="#"for better accessibility.Using
href="#"causes the page to scroll to top and isn't ideal for accessibility. IfactionHrefis not provided for a non-coming-soon card, consider either makingactionHrefrequired or using abuttonelement instead.🛡️ Suggested approaches
Option 1: Make
actionHrefrequired whenisComingSoonis false:export interface ResourceCardProps { // ... actionHref: string; // Remove optional // ... }Option 2: Use
javascript:void(0)as fallback (less ideal but prevents scroll):- href={actionHref || '#'} + href={actionHref || 'javascript:void(0)'}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsx around lines 70 - 72, The current ResourceCard uses an anchor with href={actionHref || '#'} which causes scrolling and hurts accessibility; update ResourceCard to avoid using '#' by either (A) making actionHref required when isComingSoon is false (change ResourceCardProps so actionHref is non-optional and validate/guard rendering) and keep the <a> only when actionHref exists, or (B) when actionHref is absent and the card is interactive, render a <button> (or an <a> with role="button" and javascript:void(0) only as a last resort) instead of an anchor; adjust the conditional around the <a> (the element with href={actionHref || '#'} and target logic) to only render anchors when a real actionHref exists and add appropriate aria-disabled/aria-label attributes for non-clickable coming-soon cards.app/(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsx-25-29 (1)
25-29:⚠️ Potential issue | 🟡 MinorAdd guard to prevent API calls when
org.idis missing.The component's early return only checks
!org, not!org?.id. If org exists but lacks an id, an empty string is passed touseFollow. The hook doesn't validate empty entityId before calling followApi methods (follow, unfollow), which build malformed API URLs like/follows/ORGANIZATION//check, causing API errors.Either add
!org?.idto the guard at line 61, or add validation in the follow/unfollow button handler to checkorg.idbefore allowing the action.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsx around lines 25 - 29, The component currently calls useFollow('ORGANIZATION', org?.id || '', false) even when org exists without an id, causing malformed API calls; add a guard to prevent that by ensuring the early-return checks !org?.id (not just !org) before rendering or invoking useFollow, or alternatively update the follow/unfollow click handler (the toggleFollow invocation) to first validate org?.id and bail out if missing—this prevents passing an empty entityId into useFollow and stops followApi calls that build URLs like /follows/ORGANIZATION//check.app/(landing)/hackathons/[slug]/components/tabs/index.tsx-46-46 (1)
46-46:⚠️ Potential issue | 🟡 MinorRemove the debug logging.
These
console.logcalls will keep the pre-commit hook failing and will also spam client logs on every tab recomputation.Also applies to: 130-130
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/`(landing)/hackathons/[slug]/components/tabs/index.tsx at line 46, Remove the stray debug console.log statements that print currentHackathon in the tabs component (index.tsx); locate the console.log({ currentHackathon }) calls (one near the top and one around the later recomputation) and delete them so client logs and pre-commit hooks are not spammed—do not replace with other console.* calls (use proper logging only if necessary via a guarded logger or NODE_ENV checks elsewhere).
Summary by CodeRabbit
Release Notes
New Features
Improvements