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 introduces a comprehensive redesign of the hackathon detail page and platform architecture, migrating from centralized provider-based state management to server-seeded data combined with React Query hooks for per-component data fetching. It adds new UI components (banner, header, sidebar, tabs), introduces React Query integration across the stack, refactors team APIs, consolidates color theming from hard-coded hex values to primary design tokens, and implements new auth and messaging systems. Changes
Sequence DiagramsequenceDiagram
participant Browser
participant Server
participant HackathonPage as Hackathon Page<br/>(SSR)
participant ReactQuery as React Query<br/>Client Cache
participant API as Hackathon API
Browser->>Server: GET /hackathons/[slug]
Server->>API: Fetch hackathon data
API-->>Server: hackathon object
Server->>HackathonPage: Render with initialHackathon prop
HackathonPage->>ReactQuery: Seed cache with initialHackathon
HackathonPage-->>Browser: HTML + Page components
Note over Browser,ReactQuery: Component-Level Data Fetching
rect rgba(100, 150, 200, 0.5)
HackathonPage->>ReactQuery: useHackathonAnnouncements(slug)
ReactQuery->>API: GET /announcements?slug=
API-->>ReactQuery: announcements[]
ReactQuery-->>HackathonPage: render AnnouncementsTab
end
rect rgba(150, 100, 200, 0.5)
HackathonPage->>ReactQuery: useHackathonWinners(slug)
ReactQuery->>API: GET /winners?slug=
API-->>ReactQuery: winners[]
ReactQuery-->>HackathonPage: render Winners tab
end
rect rgba(200, 150, 100, 0.5)
HackathonPage->>ReactQuery: useHackathonParticipants(slug)
ReactQuery->>API: GET /participants?slug=
API-->>ReactQuery: participants[]
ReactQuery-->>HackathonPage: render Participants tab
end
rect rgba(100, 200, 150, 0.5)
HackathonPage->>ReactQuery: useJoinHackathon(slug)
User->>HackathonPage: Click join
HackathonPage->>ReactQuery: Join mutation
ReactQuery->>API: POST /join
API-->>ReactQuery: success
ReactQuery->>ReactQuery: Invalidate hackathon query
ReactQuery-->>HackathonPage: Refresh + toast
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests (beta)
|
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).
…ment, and dynamic tab visibility.
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/api/hackathons.ts (1)
2825-2835:⚠️ Potential issue | 🟠 MajorCorrect the double-wrapping in
GetHackathonBySlug.The API response is already enveloped by the backend. Since
api.get('/hackathons/${slug}')returns{ data: envelopedResponse, ... }andres.datais already{ success, data: Hackathon, message, meta }, wrapping it again creates{ success, data: { success, data, message }, ... }, requiring callers to accessresponse.data.datainstead ofresponse.data.Align with the pattern used in
getHackathon(lib/api/hackathon.ts, line 54–60), which correctly returnsres.datadirectly without re-wrapping.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/api/hackathons.ts` around lines 2825 - 2835, The current GetHackathonBySlug implementation double-wraps the backend envelope by returning a new object around res.data; instead, return the backend envelope directly: replace the custom return block that constructs { success: true, data: res.data, message: ..., meta: ... } with a direct return of res.data (the value from api.get(`/hackathons/${slug}`)). Locate the call to api.get(`/hackathons/${slug}`) and the variable res, remove the manual envelope and any duplicate meta/timestamp construction so the function returns res.data just like getHackathon does.
♻️ Duplicate comments (1)
lib/api/hackathons/teams.ts (1)
342-356:⚠️ Potential issue | 🔴 CriticalDo not return fabricated success from legacy shims.
trackContactClickanddeleteTeamPostcurrently return success without performing backend writes, which can leave UI/client state inconsistent with server state. Please wire real endpoints or fail fast explicitly.As per coding guidelines `Always ensure final code is fully functional with no placeholders, TODOs, or missing parts`.💡 Minimal safe fix (fail fast until endpoints are wired)
export const trackContactClick = async ( hackathonSlugOrId: string, postId: string, organizationId?: string ): Promise<ApiResponse<null>> => { - return { success: true, data: null, message: 'Tracking click' }; + throw new Error( + 'trackContactClick is not implemented against backend teams endpoints yet.' + ); }; export const deleteTeamPost = async ( hackathonSlugOrId: string, postId: string, organizationId?: string ): Promise<ApiResponse<null>> => { - return { success: true, data: null, message: 'Deleted' }; + throw new Error( + 'deleteTeamPost is not implemented against backend teams endpoints yet.' + ); };🤖 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 342 - 356, Both trackContactClick and deleteTeamPost currently return fabricated success responses without performing backend writes; replace these placeholders by either wiring them to the real backend endpoints (use the existing HTTP/api client used elsewhere in this module to call the write endpoints and return the actual ApiResponse) or, until endpoints are available, fail fast by returning a non-success ApiResponse or throwing an Error indicating "unimplemented" so callers won't assume success. Update the implementations of trackContactClick and deleteTeamPost to call the proper write API (or explicitly return success: false with a clear message) and propagate/handle any errors from the network call.
🧹 Nitpick comments (5)
app/(landing)/hackathons/[slug]/components/tabs/contents/winners/MainStageHeader.tsx (1)
3-3: Add an explicit return type toMainStageHeader.Line 3 defines a const arrow component without an explicit type annotation, which conflicts with the TS/TSX project convention.
♻️ Suggested update
-export const MainStageHeader = () => { +export const MainStageHeader = (): React.JSX.Element => {As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".
🤖 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 3, The MainStageHeader arrow component lacks an explicit type annotation; update the declaration of MainStageHeader to include a React component return type (e.g. const MainStageHeader: React.FC = () => { ... } or import type { FC } and use const MainStageHeader: FC = () => { ... }), and add the necessary React/type import if missing so the component signature follows the project's TS/TSX convention.components/ui/popover-cult.tsx (2)
22-38: Register document listeners only while the popover is open
mousedownandkeydownlisteners are attached even when closed. With multiple popovers, this adds unnecessary global handlers and redundant state updates.Refactor sketch
-function useClickOutside( +function useClickOutside( ref: React.RefObject<HTMLElement | null>, - handler: () => void + handler: () => void, + enabled: boolean ) { useEffect(() => { + if (!enabled) return; const handleClickOutside = (event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) { handler(); } }; @@ - }, [ref, handler]); + }, [ref, handler, enabled]); } @@ - useClickOutside(formContainerRef, closePopover); + useClickOutside(formContainerRef, closePopover, isOpen); @@ useEffect(() => { + if (!isOpen) return; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { closePopover(); } }; @@ - }, [closePopover]); + }, [isOpen, closePopover]);Also applies to: 135-149
🤖 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 22 - 38, The document listeners are always attached inside useClickOutside (and the similar block at 135-149); update useClickOutside to accept an "isOpen" boolean (or a predicate) and only add the 'mousedown' (and any 'keydown' handlers you have elsewhere) when isOpen is true, and remove them when isOpen becomes false or on cleanup; specifically change the effect in useClickOutside (and the other listener block) to depend on isOpen and handler, attach handleClickOutside only if isOpen, and ensure document.removeEventListener is called in the cleanup so global listeners are not present while the popover is closed.
22-25: Convert function declarations to const arrow functions with explicit type annotationsThe file uses function declarations throughout. Project guidelines prefer const arrow functions with explicit type annotations. This applies to:
useClickOutside(lines 22-25)usePopover(lines 51-57)usePopoverLogic(lines 59-71)- All exported components (lines 78-364): PopoverRoot, PopoverTrigger, PopoverContent, PopoverForm, PopoverLabel, PopoverTextarea, PopoverFooter, PopoverCloseButton, PopoverSubmitButton, PopoverHeader, PopoverBody, and PopoverButton
🤖 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 22 - 25, Replace all plain function declarations with const arrow function expressions and add explicit TypeScript types: convert useClickOutside, usePopover, and usePopoverLogic to consts with proper types for parameters and return values (e.g., React.RefObject<HTMLElement | null> for refs, handlers as () => void, and explicit hook return types). Update all exported components (PopoverRoot, PopoverTrigger, PopoverContent, PopoverForm, PopoverLabel, PopoverTextarea, PopoverFooter, PopoverCloseButton, PopoverSubmitButton, PopoverHeader, PopoverBody, PopoverButton) to const ComponentName: React.FC<PropsType> = (props: PropsType) => { ... } or the appropriate function component signature with explicit PropsType and return type JSX.Element; ensure any forwarded refs use React.forwardRef with correct generics and prop types. Keep behavior unchanged while only adjusting declaration style and adding explicit types.app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx (1)
34-49: Stop the countdown interval when time reaches zero.After
diff <= 0,tick()keeps pushing a fresh{ d: 0, h: 0, m: 0, s: 0 }every second, so ended pages rerender forever for no user-visible change.🤖 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 34 - 49, The tick function never clears the interval when deadline is reached; update the code so the interval id is stored in an outer-scope variable (e.g., let id = null), start the interval with id = setInterval(tick, 1000), and inside tick when diff <= 0 call clearInterval(id) (and set id = null) after calling setTimeLeft({ d:0,h:0,m:0,s:0}); this ensures the interval created by setInterval is stopped when time runs out (referencing tick, setTimeLeft, id and clearInterval).app/(landing)/hackathons/[slug]/HackathonPageClient.tsx (1)
33-40:initialHackathonis required but unusedThis prop currently has no effect in this component. Either wire it into the data path at this boundary or remove it from the public props to avoid dead API surface.
As per coding guidelines, "Always ensure final code is fully functional with no placeholders, TODOs, or missing parts."
🤖 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 33 - 40, The prop initialHackathon is declared on HackathonPageClient but never used; either remove it from the public props and function signature to eliminate dead API surface, or wire it into the data path by seeding client-side cache / initial data: inside HackathonPageClient call React Query's useQueryClient and setQueryData (or provide initialData to the useQuery that reads the hackathon) for the same query key your component uses (e.g., the hackathon slug key) so server-fetched initialHackathon is consumed on first render; update the interface and call sites accordingly to keep types consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(landing)/hackathons/[slug]/components/header/ActionButtons.tsx:
- Around line 43-49: The leave flow in handleLeave calls
leaveMutation.mutateAsync but does not refresh the seeded hackathon state so
isParticipant (derived from currentHackathon) remains stale; after a successful
leave, trigger a refresh of the currentHackathon data (e.g., call your
data-layer refetch/invalidate for the current hackathon query or call
router.refresh()) so UI updates immediately; locate handleLeave/leaveMutation
and add a post-success refetch/invalidate for currentHackathon (or use
router.refresh()) and keep the toast.success call after the refresh.
- Around line 28-30: Replace the unsafe any by importing and using the
Participant type from "@/types/hackathon" and update the isParticipant check to
use that typed array (participants: Participant[]), and after the handleLeave
mutation ensure you refresh/refetch the current hackathon state the same way
handleJoin does (e.g., call the same refetch/mutate method used in handleJoin)
so the header reflects the backend change; update imports to include Participant
and adjust the participants.some callback to use typed Participant objects and
user.id.
In `@app/`(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsx:
- Around line 151-158: The Message button (BoundlessButton with icon
MessageSquare) is a live CTA with no click handler; either implement its action
or remove/hide it. Add a handler (e.g., create handleMessageClick) and pass it
as onClick to BoundlessButton to open the messaging flow or composer (use your
router/navigation or call an existing openComposeMessage/openMessageModal
function and supply the current hackathon slug from props), or conditionally
render the button only when messaging is enabled (wrap it in a feature flag
check or isMessagingAvailable prop). Ensure the change touches
FollowAndMessage.tsx and wires the onClick to the new handleMessageClick or
removes the button if not supported.
In `@app/`(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx:
- Around line 77-80: The submit CTA in PoolAndAction is only gated by
isLive/isEnded and thus still enables navigation when status is
ARCHIVED/CANCELLED or when the submission deadline has passed; update the gating
logic in PoolAndAction (where isLive, isEnded and the submit button/link to
"/submit" are used) to also check hackathon?.status !== 'ARCHIVED' &&
hackathon?.status !== 'CANCELLED' and validate the actual submission window
timestamps (e.g., hackathon?.submissionStart and hackathon?.submissionEnd or
equivalent fields) against Date.now() so the button is disabled/hidden unless
now is within the submission window; apply the same fix to the other submit CTA
occurrences referenced in the diff.
In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx:
- Around line 67-79: The component currently returns early when `!hackathon`
before calling hooks, causing a Hooks order violation; move the `if (!hackathon)
return null;` so that all hooks (including the `useMemo` that computes
`availableSkills`) are called unconditionally first, or alternatively compute
`availableSkills` inside a hook-safe conditional (e.g., set it to a default
empty set within the `useMemo` when `accumulatedParticipants` is empty) so
`useMemo` always runs; also change the nested `forEach` callback over
`accumulatedParticipants`/`p.user.profile.skills` to a block-bodied callback
(referencing `accumulatedParticipants`, `Participant`, and the `useMemo` that
defines `availableSkills`) and add `skillsSet.add(s)` inside that block for
clarity.
In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsx:
- Around line 135-139: When changing the status via the DropdownMenuItem map you
need to reset pagination to page 1; update the onClick handler used in
Submissions.tsx so that when a new status is selected it calls both
setStatusFilter(status) and setPage(1) (or ensures whatever setter for the page
state is invoked) so status changes behave the same as search/track changes and
avoid landing on empty pages; locate the DropdownMenuItem onClick where
setStatusFilter is called and add the page reset there.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx:
- Around line 51-58: The second badge in the SubmissionCard component is
hard-coded to "Infrastructure" which mislabels items and duplicates the
category; replace that fixed span with a dynamic value from the submission data
(e.g., a second tag/field such as submission.subcategory, submission.type, or an
array item from submission.tags) and render it conditionally only when that
value exists; update the JSX in SubmissionCard (where {category} is rendered and
the hard-coded "Infrastructure" span appears) to map/render the real secondary
badge from the submission prop or tags array, and remove the static string so
only valid badges from the data are shown.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx:
- Around line 283-289: The filter/map pipeline uses a loose any cast for
members; replace it by adding a type predicate function (e.g., isTeamMember(m):
m is TeamMember) and use that in the filter so TypeScript knows team.members
(checked with typeof m !== 'string') is narrowed to TeamMember[] before mapping;
update the filter call on team.members and remove the any in the map callback
(references: team.members, the filter callback, map callback, and TeamMember
type).
- Around line 85-113: Replace the untyped catch handlers using "err: any" with a
safe Axios-aware pattern: import AxiosError and/or axios (e.g., import axios, {
AxiosError } from 'axios') and change both catch blocks to catch (err: unknown)
then narrow with either "const axiosError = err as AxiosError" or preferably "if
(axios.isAxiosError(err)) { ... }" before accessing err.response?.status or
err.response?.data?.message; apply the same change around the verification step
(where setVerificationError is used) and the invitation step (around
inviteMutation.mutateAsync and setting setInviteIdentifier/setInviteMessage) so
error properties are accessed only after the Axios type guard.
In `@app/`(landing)/hackathons/[slug]/components/tabs/index.tsx:
- Around line 140-147: The useMemo that computes tab badges uses
announcements.length but announcements is missing from the dependency array, so
add announcements (or announcements.length) to the dependencies for the useMemo
call to ensure badge counts update when announcements refetch; update the
dependency array referenced in the useMemo invocation that currently lists
currentHackathon, winners, submissions,
discussionComments.pagination.totalItems, announcementsLoading, generalLoading
to include announcements.
- Around line 130-136: The filter uses an unnecessary any cast in
enabledSet.has(key as any); change the Set creation to be type-safe (e.g.,
construct enabledSet as new Set<string>(enabledTabs) or otherwise type
enabledTabs/Set as string) and remove the cast so the tabs.filter callback uses
enabledSet.has(key) directly; update references to enabledTabs, enabledSet,
tabIdToEnabledKey and the tabs.filter logic accordingly to preserve the
special-case for tab.id === 'overview'.
In `@components/hackathons/team-formation/CreateTeamPostModal.tsx`:
- Around line 219-225: Replace the catch clause in CreateTeamPostModal (and the
similar handlers in useTeamPosts) to avoid using `any`: change `catch (err:
any)` to `catch (err: unknown)` and narrow the type before accessing properties
(e.g., use `if (err instanceof Error) { const message = err.message; }` and/or
check for axios-like shapes with a type guard before reading
`err.response?.data?.message`) then use the narrowed message for `toast.error`
and `console.error`; update the same pattern in the `useTeamPosts` hook
functions so all error handlers use `unknown` + type narrowing.
In `@components/hackathons/team-formation/TeamRecruitmentPostCard.tsx`:
- Around line 283-294: leaderObject can be undefined but is assigned to the
non-nullable DisplayLeader type; change the initialization to build a normalized
DisplayLeader with fallbacks: inspect leaderObject (from post.members.find) and
post.leader, then create a concrete DisplayLeader object (copy expected fields
like id, name, avatarUrl, role, etc.) providing safe defaults (empty
strings/nullables as appropriate) when values are missing; replace the direct
assignment to displayLeader with this constructed object so displayLeader is
always a valid DisplayLeader and no longer nullable.
In `@components/ui/popover-cult.tsx`:
- Around line 106-123: The popover trigger (the motion.button using layoutId
`popover-${uniqueId}` and handler openPopover) needs ARIA attributes: add
aria-expanded={isOpen} (or equivalent state),
aria-controls={`popover-panel-${uniqueId}`} and role="button" plus an id like
`popover-trigger-${uniqueId}`; then ensure the popover panel component uses
id={`popover-panel-${uniqueId}`} and
aria-labelledby={`popover-trigger-${uniqueId}`} so the trigger and panel are
explicitly linked for screen readers (apply the same pattern to the other
trigger/panel pair around lines 154-167).
- Line 220: The absolutely positioned label string 'absolute top-3 left-4
text-sm text-zinc-500 select-none dark:text-zinc-400' in the popover-cult.tsx
label is intercepting clicks; update that label's class list (the label element
rendering the floating text inside the component) to include pointer-events-none
so clicks pass through to the textarea/input (keep the rest of the classes
unchanged).
- Around line 158-159: The Tailwind class string in the Popover component
contains an invalid z-index utility 'z-90'; update the class list (the string
containing 'absolute z-90 h-[200px] w-[364px] ...') to remove 'z-90' and replace
it with the valid utility 'z-50' so the JSX/TSX className uses 'absolute z-50
h-[200px] w-[364px] ...' (preserve the rest of the classes and the surrounding
className variable).
In `@hooks/hackathon/use-hackathon-queries.ts`:
- Around line 182-202: The hook accepts params.sort and includes it in the
queryKey but never forwards it to the API; update the call in the useQuery
queryFn to pass params.sort into getExploreSubmissions (and if necessary, extend
getExploreSubmissions’ signature to accept a sort parameter and propagate it to
the backend request) so that use-hackathon-queries.ts’s useQuery call and the
getExploreSubmissions function both accept and use the sort argument.
In `@hooks/hackathon/use-team-posts.ts`:
- Around line 154-158: The updater passed to setMyTeam leaves myTeam null when
prev is null, so after createPost the UI stays stale; change the fallback to
initialize myTeam with a minimal team object that includes the new post (use
response.data) instead of null. Locate the updater used with setMyTeam (the
block referencing prev and response.data) and replace the null fallback with an
initialized team shape (e.g., at least a posts array containing response.data,
matching the Team type used elsewhere) so the UI immediately reflects the new
post.
In `@lib/api/hackathons.ts`:
- Around line 1735-1742: Change getExploreSubmissions to accept an options
object for the optional string filters instead of positional args so filters
can't be mis-assigned: update the signature of getExploreSubmissions (returning
ExploreSubmissionsApiResponse) to (hackathonId: string, page?: number, limit?:
number, options?: { search?: string; category?: string; status?: string }) and
then read search/category/status from that options object inside the function;
update any internal references that used the old positional parameters and
adjust callers to pass an options object (or add a thin wrapper overload for
backward compatibility if needed).
In `@lib/providers/hackathonProvider.tsx`:
- Line 108: The fallback label for submitterName contains a typo: replace the
string 'Anonym' with the correct user-visible label 'Anonymous' wherever
submitterName is constructed (see the expression submitterName:
s.participant?.name || s.teamName || 'Anonym') so that s.participant?.name and
s.teamName fallbacks show 'Anonymous' instead of 'Anonym'.
---
Outside diff comments:
In `@lib/api/hackathons.ts`:
- Around line 2825-2835: The current GetHackathonBySlug implementation
double-wraps the backend envelope by returning a new object around res.data;
instead, return the backend envelope directly: replace the custom return block
that constructs { success: true, data: res.data, message: ..., meta: ... } with
a direct return of res.data (the value from api.get(`/hackathons/${slug}`)).
Locate the call to api.get(`/hackathons/${slug}`) and the variable res, remove
the manual envelope and any duplicate meta/timestamp construction so the
function returns res.data just like getHackathon does.
---
Duplicate comments:
In `@lib/api/hackathons/teams.ts`:
- Around line 342-356: Both trackContactClick and deleteTeamPost currently
return fabricated success responses without performing backend writes; replace
these placeholders by either wiring them to the real backend endpoints (use the
existing HTTP/api client used elsewhere in this module to call the write
endpoints and return the actual ApiResponse) or, until endpoints are available,
fail fast by returning a non-success ApiResponse or throwing an Error indicating
"unimplemented" so callers won't assume success. Update the implementations of
trackContactClick and deleteTeamPost to call the proper write API (or explicitly
return success: false with a clear message) and propagate/handle any errors from
the network call.
---
Nitpick comments:
In `@app/`(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx:
- Around line 34-49: The tick function never clears the interval when deadline
is reached; update the code so the interval id is stored in an outer-scope
variable (e.g., let id = null), start the interval with id = setInterval(tick,
1000), and inside tick when diff <= 0 call clearInterval(id) (and set id = null)
after calling setTimeLeft({ d:0,h:0,m:0,s:0}); this ensures the interval created
by setInterval is stopped when time runs out (referencing tick, setTimeLeft, id
and clearInterval).
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/winners/MainStageHeader.tsx:
- Line 3: The MainStageHeader arrow component lacks an explicit type annotation;
update the declaration of MainStageHeader to include a React component return
type (e.g. const MainStageHeader: React.FC = () => { ... } or import type { FC }
and use const MainStageHeader: FC = () => { ... }), and add the necessary
React/type import if missing so the component signature follows the project's
TS/TSX convention.
In `@app/`(landing)/hackathons/[slug]/HackathonPageClient.tsx:
- Around line 33-40: The prop initialHackathon is declared on
HackathonPageClient but never used; either remove it from the public props and
function signature to eliminate dead API surface, or wire it into the data path
by seeding client-side cache / initial data: inside HackathonPageClient call
React Query's useQueryClient and setQueryData (or provide initialData to the
useQuery that reads the hackathon) for the same query key your component uses
(e.g., the hackathon slug key) so server-fetched initialHackathon is consumed on
first render; update the interface and call sites accordingly to keep types
consistent.
In `@components/ui/popover-cult.tsx`:
- Around line 22-38: The document listeners are always attached inside
useClickOutside (and the similar block at 135-149); update useClickOutside to
accept an "isOpen" boolean (or a predicate) and only add the 'mousedown' (and
any 'keydown' handlers you have elsewhere) when isOpen is true, and remove them
when isOpen becomes false or on cleanup; specifically change the effect in
useClickOutside (and the other listener block) to depend on isOpen and handler,
attach handleClickOutside only if isOpen, and ensure
document.removeEventListener is called in the cleanup so global listeners are
not present while the popover is closed.
- Around line 22-25: Replace all plain function declarations with const arrow
function expressions and add explicit TypeScript types: convert useClickOutside,
usePopover, and usePopoverLogic to consts with proper types for parameters and
return values (e.g., React.RefObject<HTMLElement | null> for refs, handlers as
() => void, and explicit hook return types). Update all exported components
(PopoverRoot, PopoverTrigger, PopoverContent, PopoverForm, PopoverLabel,
PopoverTextarea, PopoverFooter, PopoverCloseButton, PopoverSubmitButton,
PopoverHeader, PopoverBody, PopoverButton) to const ComponentName:
React.FC<PropsType> = (props: PropsType) => { ... } or the appropriate function
component signature with explicit PropsType and return type JSX.Element; ensure
any forwarded refs use React.forwardRef with correct generics and prop types.
Keep behavior unchanged while only adjusting declaration style and adding
explicit types.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 461799c6-42ba-4ee1-8e0f-b475078c7c89
📒 Files selected for processing (32)
app/(landing)/hackathons/[slug]/HackathonPageClient.tsxapp/(landing)/hackathons/[slug]/components/header/ActionButtons.tsxapp/(landing)/hackathons/[slug]/components/header/index.tsxapp/(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsxapp/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsxapp/(landing)/hackathons/[slug]/components/tabs/Lists.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/participants/ParticipantCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/resources/index.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/teams/TeamCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/winners/MainStageHeader.tsxapp/(landing)/hackathons/[slug]/components/tabs/index.tsxcomponents/common/SharePopover.tsxcomponents/hackathons/submissions/SubmissionForm.tsxcomponents/hackathons/team-formation/ContactTeamModal.tsxcomponents/hackathons/team-formation/CreateTeamPostModal.tsxcomponents/hackathons/team-formation/TeamDetailsSheet.tsxcomponents/hackathons/team-formation/TeamFormationTab.tsxcomponents/hackathons/team-formation/TeamRecruitmentPostCard.tsxcomponents/ui/popover-cult.tsxhooks/hackathon/use-hackathon-queries.tshooks/hackathon/use-hackathon-submissions.tshooks/hackathon/use-team-posts.tslib/api/hackathon.tslib/api/hackathons.tslib/api/hackathons/teams.tslib/providers/hackathonProvider.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- app/(landing)/hackathons/[slug]/components/tabs/Lists.tsx
- components/hackathons/submissions/SubmissionForm.tsx
- app/(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsx
- app/(landing)/hackathons/[slug]/components/header/index.tsx
- components/common/SharePopover.tsx
- app/(landing)/hackathons/[slug]/components/tabs/contents/teams/TeamCard.tsx
- app/(landing)/hackathons/[slug]/components/tabs/contents/participants/ParticipantCard.tsx
- components/hackathons/team-formation/ContactTeamModal.tsx
- hooks/hackathon/use-hackathon-submissions.ts
app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx
Outdated
Show resolved
Hide resolved
app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx
Outdated
Show resolved
Hide resolved
app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx
Outdated
Show resolved
Hide resolved
app/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsx
Outdated
Show resolved
Hide resolved
…ance messaging features across hackathon components
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
types/messages.ts (1)
6-11:⚠️ Potential issue | 🟡 MinorMake
usernameoptional inOtherUserto align with codebase conventions.The
username: stringfield is inconsistent with howusernameis handled throughout the codebase, where it is defined as optional (username?: string) in all other user/profile types (user.ts, auth.ts, comment.ts, follow.ts, hackathon/participant.ts, etc.). Defensive patterns throughout the codebase check for username existence before use ({member.username && ...}), indicating it is not always available. Makingusername?: stringmaintains consistency and prevents runtime issues if the backend doesn't provide it in all contexts.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/messages.ts` around lines 6 - 11, The OtherUser interface currently declares username as a required string; change it to an optional property (username?: string) to match other user/profile types and existing defensive checks across the codebase—update the OtherUser type definition so any code referencing OtherUser will treat username as possibly undefined.components/hackathons/team-formation/CreateTeamPostModal.tsx (1)
110-127:⚠️ Potential issue | 🟠 MajorReset create-mode defaults explicitly to avoid stale edit data.
Line 111 only resets with
initialData, so reopening in create mode can retain previous form state. Add an explicitopen && !initialDatareset branch with clean defaults.💡 Suggested fix
useEffect(() => { - if (open && initialData) { + if (open && initialData) { form.reset({ teamName: initialData.teamName, description: initialData.description, lookingFor: initialData.lookingFor.map(roleObj => ({ role: typeof roleObj === 'string' ? roleObj : roleObj.role, skills: typeof roleObj === 'string' ? [] : roleObj.skills || [], })), maxSize: initialData.maxSize, contactMethod: initialData.contactMethod || 'email', contactInfo: initialData.contactInfo, }); + setStep('IDENTITY'); + setSkillInputs({}); + } else if (open && !initialData) { + form.reset({ + teamName: '', + description: '', + lookingFor: [{ role: '', skills: [] }], + maxSize: 5, + contactMethod: 'email', + contactInfo: '', + }); + setStep('IDENTITY'); + setSkillInputs({}); } else if (!open) { form.reset(); setStep('IDENTITY'); setSkillInputs({}); } }, [open, initialData, form]);🤖 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 110 - 127, The useEffect handling form.reset only covers edit mode (open && initialData) and the close case (!open), so reopening the modal in create mode (open && !initialData) can keep stale values; add an explicit branch for open && !initialData that calls form.reset with clean defaults (e.g., teamName: '', description: '', lookingFor: [], maxSize: undefined or default, contactMethod: 'email', contactInfo: ''), and also call setStep('IDENTITY') and setSkillInputs({}) there to ensure identity and skill inputs are reset; update the useEffect around the existing form.reset, setStep, and setSkillInputs logic to include this new create-mode reset branch.
♻️ Duplicate comments (3)
components/hackathons/team-formation/CreateTeamPostModal.tsx (1)
219-225:⚠️ Potential issue | 🟠 MajorRemove
as anyfrom error extraction.Line 223 and Line 224 reintroduce
anyvia cast, which bypasses type safety.💡 Suggested fix
+type ApiErrorWithMessage = { + response?: { + data?: { + message?: string; + }; + }; +}; } catch (err: unknown) { console.error('Failed to save team post:', err); let errorMessage = 'Failed to save team post. Please try again.'; if (err && typeof err === 'object') { - if ('response' in err && (err as any).response?.data?.message) { - errorMessage = (err as any).response.data.message; + const apiErr = err as ApiErrorWithMessage; + if (apiErr.response?.data?.message) { + errorMessage = apiErr.response.data.message; } else if (err instanceof Error) { errorMessage = err.message; } } toast.error(errorMessage);#!/bin/bash # Verify there are no explicit any usages in team-post flows rg -nP --type=ts --type=tsx '\bas\s+any\b|:\s*any\b' \ components/hackathons/team-formation/CreateTeamPostModal.tsx \ hooks/hackathon/use-team-posts.tsBased on learnings,
Applies to **/*.{ts,tsx} : 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/CreateTeamPostModal.tsx` around lines 219 - 225, In the catch block inside CreateTeamPostModal (the catch(err: unknown) handler), remove the "as any" casts and replace them with a small user-defined type guard (e.g., isResponseError(obj: unknown): obj is { response: { data?: { message?: string } } }) so you can safely access response.data.message; fall back to err instanceof Error for Error.message and otherwise use the generic fallback string. Update the extraction logic to call that type guard instead of casting to any.app/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx (1)
52-62:⚠️ Potential issue | 🟡 MinorDuplicate category badge rendered.
The category is rendered twice: once at line 55 (
{category}) and again at lines 57-61 ({submission.category}). Sincecategoryis destructured fromsubmission, these display the same value, resulting in duplicate badges.🐛 Proposed fix to remove duplicate badge
{/* Tags/Categories */} <div className='flex flex-wrap gap-2 pt-2'> <span className='text-primary rounded-md bg-[`#232B20`]/50 px-2.5 py-1 text-[10px] font-bold tracking-wider uppercase'> {category} </span> - {submission.category && ( - <span className='rounded-md bg-white/5 px-2.5 py-1 text-[10px] font-bold tracking-wider text-gray-400 uppercase'> - {submission.category} - </span> - )} </div>🤖 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/SubmissionCard.tsx around lines 52 - 62, SubmissionCard renders the same category twice because category is destructured from submission and also accessed as submission.category; remove the duplicate by keeping a single badge render (either the unconditional {category} span or the conditional {submission.category} span) and ensure the remaining span uses the canonical source (prefer using submission.category or the destructured category consistently) and remains conditionally rendered if category can be undefined; update the JSX in SubmissionCard.tsx to eliminate the redundant span.app/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx (1)
87-117:⚠️ Potential issue | 🟠 MajorReplace
anywith proper error typing and fix error property access.The catch blocks use
err: anywhich violates the coding guideline to avoidanytypes. Additionally, based on the API client inlib/api/auth.ts, errors havestatusat the top level, not nested underresponse.🔧 Proposed fix using axios type guard
+import axios from 'axios'; import { getUserProfileByUsername } from '@/lib/api/auth';- } catch (err: any) { + } catch (err: unknown) { setVerificationError( - err.response?.status === 404 + axios.isAxiosError(err) && err.response?.status === 404 ? 'User not found. Please check the username.' : 'Failed to verify user. Please try again.' );- } catch (err: any) { + } catch (err: unknown) { const errorMessage = - err.response?.data?.message || - err.message || + (axios.isAxiosError(err) && err.response?.data?.message) || + (err instanceof Error && err.message) || 'Failed to send invitation. Please try again.'; setVerificationError(errorMessage);🤖 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 87 - 117, Replace the untyped catches using "err: any" with "err: unknown" and narrow the error before accessing properties: in the verification catch (around setVerificationError) read status from err.status (not err.response.status) after a type-guard (e.g., typeof err === 'object' && err !== null && 'status' in err) and map 404 to 'User not found...' otherwise a generic message; in the invite catch (around inviteMutation.mutateAsync and setVerificationError) similarly narrow err and read err.message or err.status (and err.data?.message if your API error shape includes data) instead of err.response?.data?.message; you can add a small helper/isApiError type-guard used by both handlers to keep code DRY while keeping setIsVerifying, setInviteIdentifier, setInviteMessage, inviteIdentifier, inviteMessage and inviteMutation.mutateAsync logic unchanged.
🧹 Nitpick comments (14)
components/hackathons/HackathonComments.tsx (1)
12-12: Consider using const arrow function per coding guidelines.The guidelines prefer const arrow functions with explicit type annotations over function declarations.
✨ Suggested refactor
-export function HackathonComments({ hackathonId }: HackathonCommentsProps) { +export const HackathonComments = ({ hackathonId }: HackathonCommentsProps): JSX.Element => {As per coding guidelines: "Prefer const arrow functions with explicit type annotations over function declarations".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/HackathonComments.tsx` at line 12, Replace the function declaration for HackathonComments with a const arrow function that has an explicit React.FC (or appropriate) type annotation using HackathonCommentsProps; specifically change the export function HackathonComments({ hackathonId }: HackathonCommentsProps) declaration to a const HackathonComments: React.FC<HackathonCommentsProps> = ({ hackathonId }) => { ... } (ensure export is preserved and update any default/ named export usage accordingly).components/messages/MessagesTrigger.tsx (1)
22-24: Consider simplifying theopenMessagescall.Since
conversationIdis optional and defaults toundefinedwhen not provided, explicitly passingconversationId: undefinedis unnecessary.♻️ Optional simplification
- onClick={() => - openMessages({ conversationId: undefined, trigger: buttonRef.current }) - } + onClick={() => openMessages({ trigger: buttonRef.current })}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/messages/MessagesTrigger.tsx` around lines 22 - 24, In MessagesTrigger.tsx simplify the onClick call to openMessages by removing the redundant explicit conversationId: undefined; update the onClick handler that currently calls openMessages({ conversationId: undefined, trigger: buttonRef.current }) to pass only the trigger property (openMessages({ trigger: buttonRef.current })), keeping the same behavior while relying on the optional conversationId default; locate this change in the MessagesTrigger component’s onClick handler where openMessages is invoked.components/messages/TetherMessage.tsx (1)
372-408: AnimatePresence exit animations won't trigger.The component returns
nullwhen!isOpen(line 370), so the AnimatePresence never sees its children unmount - they're simply not rendered. To enable exit animations, the AnimatePresence should wrap the conditional render, not be inside it.♻️ Refactor for proper exit animations
Move the
isOpencheck inside AnimatePresence or restructure:// In the parent or wrapping the return: return ( <AnimatePresence> {isOpen && isMobile && ( <div aria-label='Messages' ...> <motion.div ... exit={{ opacity: 0 }}> ... </motion.div> </div> )} </AnimatePresence> );Currently, the early
if (!isOpen) return null;at line 370 prevents AnimatePresence from animating the exit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/messages/TetherMessage.tsx` around lines 372 - 408, The exit animations aren't firing because the component returns null when !isOpen so AnimatePresence is never present to observe unmounts; instead, always render AnimatePresence and move the isOpen/isMobile conditional inside it (i.e., replace the early `if (!isOpen) return null;` with rendering <AnimatePresence> and conditionally render the mobile dialog when `isOpen && isMobile`), keeping the existing motion elements (the overlay motion.div and panel motion.div that use overlayRef and panelRef) and their exit props intact so AnimatePresence can play the exit animations.components/hackathons/team-formation/CreateTeamPostModal.tsx (2)
262-269: Avoid nested ternaries in JSX class composition for step states.Line 262-269 and Line 274-277 use nested ternaries; prefer declarative
cn/clsxobject conditions for readability and guideline compliance.As per coding guidelines,
**/*.{jsx,tsx}: For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX.Also applies to: 274-277
🤖 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 262 - 269, The JSX uses nested ternary expressions inside the className in CreateTeamPostModal (the className that references step, s.id, steps, and idx) which hurts readability; replace the nested ternaries with cn/clsx object or array-based conditional entries (e.g., provide keys like 'bg-primary text-black', 'bg-primary/20 text-primary', 'bg-white/5 text-gray-500' mapped to boolean conditions such as step === s.id, steps.findIndex(x => x.id === step) > idx, else fallback) so the logic is declarative and duplicated pattern at lines referencing step/s.id (including the other occurrence at the 274-277 block) is unified, using the existing cn helper to compute the final class string.
372-377: Usehandle*naming for UI event callbacks.Callbacks wired to events (e.g.,
addRole,removeRole,removeSkill) should follow thehandle*convention for consistency.As per coding guidelines,
**/*.{jsx,tsx}: Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit).Also applies to: 393-394, 430-431, 536-548
🤖 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 372 - 377, Rename the UI event callbacks to use the handle* convention and update all references: change addRole -> handleAddRole, removeRole -> handleRemoveRole, removeSkill -> handleRemoveSkill (and any similarly named callbacks noted at the other locations), then update every JSX prop and internal call that uses these functions (e.g., the BoundlessButton onClick, any other onClick/onChange handlers at the other ranges) and adjust any TypeScript types/exports/imports or tests that reference the old names so the component compiles and all usages are consistent.components/avatars/BasicAvatar.tsx (1)
23-33: Inconsistent truncation behavior for username.The
truncateprop controls truncation for thenamefield but theusernamefield always hasmax-w-[100px] truncateapplied regardless of the prop value. If this is intentional, consider documenting it; otherwise, apply the same conditional logic.♻️ Suggested fix for consistent truncation
- <p className='w-full max-w-[100px] truncate text-[10px] text-gray-500'> + <p + className={cn( + 'w-full text-[10px] text-gray-500', + truncate && 'max-w-[100px] truncate' + )} + > @{username} </p>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/avatars/BasicAvatar.tsx` around lines 23 - 33, The username paragraph in BasicAvatar always applies "max-w-[100px] truncate" regardless of the truncate prop, causing inconsistent behavior with the name field; update the username <p> className to use the same conditional logic as the name (use cn and apply 'max-w-[100px] truncate' only when the truncate prop is truthy) or, if intentional, add a comment/docstring to the BasicAvatar component explaining why username truncation is always enforced; target the className on the username <p> and the truncate prop usage in the BasicAvatar component to make the change.components/hackathons/hackathonBanner.tsx (1)
117-128: Consider removing commented-out code.This commented-out
formatDateWithFallbackfunction appears to be dead code. If it's no longer needed, consider removing it to keep the codebase clean.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/hackathonBanner.tsx` around lines 117 - 128, Remove the dead commented-out helper function formatDateWithFallback (the commented block containing formatDateWithFallback), or if it is still needed restore and use it where dates are formatted; ensure no leftover commented code remains—either delete the commented block entirely or reinstate the function and replace inline date formatting with calls to formatDateWithFallback to keep the code clean and consistent.app/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx (1)
20-32: Participant data reliability concern for individual submissions.The
participantfield is indeed optional onExploreSubmissionsResponse(marked with?in the interface). While the code correctly uses optional chaining (participant?.name,participant?.image) and provides reasonable fallbacks ('Anonymous'and empty string), relying on these fallbacks for individual submissions suggests the API contract may not guarantee participant data is populated. Consider either:
- Ensuring the API always populates the
participantfield for INDIVIDUAL submissions- Fetching participant data from
participantIdif the field is absent- Documenting why the API omits participant data for certain submissions
🤖 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/SubmissionCard.tsx around lines 20 - 32, The SubmissionCard currently falls back to 'Anonymous' and empty avatar when participant is missing for INDIVIDUAL submissions; update the SubmissionCard component to, when isTeam is false and participant is undefined but participantId exists, asynchronously fetch the participant data (e.g., via an existing user/participant service like fetchParticipantById or getUserById inside a useEffect) and store it in local state so submitterName and submitterAvatar use the fetched data instead of the fallback; keep the existing team logic (isTeam, teamName, teamMembers) and retain the final fallbacks ('Anonymous' and '') only if the fetch returns nothing.app/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx (2)
50-50: Unusedrefreshfunction.
useRefreshHackathonis initialized but never called. Consider removing it if not needed, or invoking it after successful mutations if cache invalidation beyond the mutation'sonSuccessis required.🤖 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 at line 50, The declared but unused refresh variable from useRefreshHackathon in MyTeamView should be either removed or actually invoked to invalidate the hackathon cache after team mutations; locate the const refresh = useRefreshHackathon(hackathonSlug) line and either delete it if you don't need extra invalidation, or call refresh() inside the onSuccess callbacks of your team mutation handlers (e.g., handleCreateTeam, handleJoinTeam, handleLeaveTeam or whatever mutation functions are used in this component) so the hackathon data is refreshed after successful operations.
14-20: Remove unused importuseInvitationActions.
useInvitationActionsis imported but never used in this component.♻️ Proposed fix
import { useLeaveTeam, useInviteToTeam, - useInvitationActions, useTransferLeadership, useRefreshHackathon, } from '@/hooks/hackathon/use-hackathon-queries';🤖 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 14 - 20, Remove the unused hook import useInvitationActions from the import list at the top of MyTeamView.tsx so only the actually used hooks remain (useLeaveTeam, useInviteToTeam, useTransferLeadership, useRefreshHackathon); update the import statement that currently includes useInvitationActions to exclude it to eliminate the unused-symbol lint error.hooks/hackathon/use-hackathon-queries.ts (1)
496-515: Consider parallelizing cache invalidations.The sequential
awaitcalls could be parallelized for slightly faster refresh. This is a minor optimization.♻️ Optional parallel invalidation
return async () => { - await queryClient.invalidateQueries({ - queryKey: hackathonKeys.detail(slug), - }); - await queryClient.invalidateQueries({ - queryKey: hackathonKeys.participants(slug), - }); - await queryClient.invalidateQueries({ - queryKey: hackathonKeys.submissions(slug), - }); - await queryClient.invalidateQueries({ - queryKey: hackathonKeys.winners(slug), - }); - await queryClient.invalidateQueries({ - queryKey: hackathonKeys.myTeam(slug), - }); + await Promise.all([ + queryClient.invalidateQueries({ queryKey: hackathonKeys.detail(slug) }), + queryClient.invalidateQueries({ queryKey: hackathonKeys.participants(slug) }), + queryClient.invalidateQueries({ queryKey: hackathonKeys.submissions(slug) }), + queryClient.invalidateQueries({ queryKey: hackathonKeys.winners(slug) }), + queryClient.invalidateQueries({ queryKey: hackathonKeys.myTeam(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 496 - 515, The current useRefreshHackathon function performs cache invalidations sequentially which is slower; change it to run the invalidateQueries calls in parallel by calling all hackathonKeys invalidations (hackathonKeys.detail, participants, submissions, winners, myTeam) and awaiting Promise.all on the resulting promises instead of awaiting each one individually so the invalidations execute concurrently.app/(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsx (1)
216-232: Consider extracting hardcoded roles to a shared constant.The roles list is hardcoded here. If these roles are used elsewhere or need to match backend expectations, consider extracting to a shared constant for consistency and easier maintenance.
🤖 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 216 - 232, Extract the hardcoded roles array into a single exported constant (e.g., ROLES or HACKATHON_ROLES) and import it into this component, replacing the inline array used in the map; update the map to iterate over that constant and keep the existing handlers (DropdownMenuItem onClick that calls setRoleFilter and setPage). Ensure the constant name is used consistently wherever roles are needed (including any backend type or enum adapters) so the DropdownMenuItem key and displayed label come from the shared constant and remain in sync with backend expectations.lib/providers/hackathonProvider.tsx (1)
142-144: No-op setters documented appropriately.The
setLoadingandsetErrorno-ops are clearly documented as kept for interface compatibility during migration. Consider adding a TODO or deprecation notice if these should be removed in a future cleanup.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/providers/hackathonProvider.tsx` around lines 142 - 144, Add explicit TODO/deprecation comments to the no-op setters to indicate they are temporary for interface compatibility and should be removed later: annotate the setLoading and setError no-op implementations (in the hackathon provider implementation where setLoading: () => {} and setError: () => {} are defined) with a clear TODO or `@deprecated` note explaining why they are kept, the intended removal timeline or condition, and optionally a reference to the migration issue/PR so future maintainers know when it can be cleaned up.components/ui/side-panel.tsx (1)
66-69: Replace JSX class ternaries withcn/precomputed class names.These
classNamebranches add noise in the markup and leave an empty-string branch behind. A smallheaderClassName/panelRadiusClassNamevariable keeps the JSX cleaner and matches the repo convention.As per coding guidelines,
**/*.{jsx,tsx}: For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX.Also applies to: 114-117
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/ui/side-panel.tsx` around lines 66 - 69, Replace inline JSX ternary class fragments with precomputed variables using the cn helper: create a headerClassName (or panelClassName) and a panelRadiusClassName computed from panelOpen and other state, then use those variables in the className props instead of the ternary expressions found in the className on the element that uses panelOpen (around the className with 'flex w-full items-center...' and the other occurrence at lines 114-117). Ensure you reference and reuse the existing cn utility, compute the conditional segments once above the return (e.g., headerClassName = cn('base-classes', panelOpen && 'pr-3')), and replace the JSX ternaries with the new variables to clean up the markup.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx:
- Around line 63-67: The file currently uses placeholder flags (hackathonError =
false and participantsLoading = false) which disables real error/loading
handling; update PoolAndAction to derive these states from the hook returned
values instead of hardcoding: use the actual properties from useHackathonData
(e.g., replace hackathonError and participantsLoading with the hook's error and
loading fields or boolean checks such as !hackathon and hookError) and update
any conditional rendering that checks hackathonError or participantsLoading
(including the other block referenced around lines 121-133) so the component
shows the error UI and skeletons correctly when the provider reports an error or
loading state, and surface the provider error message where previously a
placeholder was used.
In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsx:
- Line 246: In the FindTeam component update the grid classes: remove the
redundant md:grid-cols-1 or replace it with the intended breakpoint value (e.g.,
md:grid-cols-2 or md:grid-cols-3) so the container div with className 'grid
grid-cols-1 gap-6 md:grid-cols-1' either correctly collapses to a single column
at all sizes or switches to the intended multi-column layout at the md
breakpoint.
In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/teams/TeamCard.tsx:
- Around line 118-120: TeamCard currently force-casts Team.members to
TeamMember[] when building GroupAvatar, which breaks if Team.members is
string[]; add a type guard (e.g., isTeamMemberArray) to detect whether members
are TeamMember[] vs string[] and normalize to an array of avatar URLs before
passing to GroupAvatar: if TeamMember[] map each member => member.image ?? '',
if string[] map each id => '' (or use an existing member lookup to resolve
images if available). Update the mapping in the TeamCard component where
GroupAvatar is called so it safely handles both union cases without
force-casting.
In `@app/`(landing)/hackathons/[slug]/components/tabs/index.tsx:
- Around line 21-23: The prop type declaration for HackathonTabsProps references
React.ReactNode but the file lacks the React/ReactNode import; add an explicit
type import (e.g., import type { ReactNode } from 'react') at the top and update
HackathonTabsProps to use ReactNode (or keep React.ReactNode) so the sidebar
prop is correctly typed; ensure the import is a type-only import to avoid
runtime impact and that the symbol HackathonTabsProps/sidebar is the one using
the type.
In `@components/auth/AuthModalProvider.tsx`:
- Around line 44-47: openAuthModal sets redirectTo via setRedirectTo but that
value is never passed into the sign-in flow; update the component that renders
LoginWrapper (in AuthModalProvider) to forward the stored redirect state
(redirectTo) into LoginWrapper (e.g., as a
callbackUrl/redirectTo/onSuccessRedirect prop) and ensure LoginWrapper or the
sign-in handler uses that prop to navigate after successful login so the stored
redirect actually controls post-login navigation.
In `@components/messages/TetherMessage.tsx`:
- Around line 68-83: The hook useIsMobile currently initializes isMobile to
false which causes the server to always render the desktop variant and leads to
hydration mismatches on mobile; change the initial state to an "unknown" value
(e.g., null/undefined) and have the component handle that sentinel (render a
neutral placeholder or desktop via CSS until the client effect runs), or
alternatively use window.matchMedia in the effect to set the state only on the
client—update the useIsMobile implementation (references: useIsMobile,
setIsMobile, update) to start with an unknown initial value and only call
setIsMobile inside useEffect so server and first client render match.
- Around line 323-345: The focus trap in the keydown handler currently builds
focusable from closeButtonRef and initialFocusRef but omits the send button; add
a new sendButtonRef (useRef<HTMLButtonElement | null>) and include
sendButtonRef.current in the same focusable array alongside
closeButtonRef.current and initialFocusRef.current, then attach
ref={sendButtonRef} to the send <Button> JSX (the send button component in this
file) so keyboard Tab/Shift+Tab cycles through close, textarea, and send
correctly.
- Around line 545-556: The loadOlder function swallows errors from getMessages
which gives no user feedback; wrap the await getMessages(...) call in a
try/catch (or add a catch branch) inside loadOlder, log the error (e.g.,
processLogger.error or console.error) and surface a user-visible error state
similar to fetchThread (e.g., set an error state or call the same toast/error
handler used elsewhere), while keeping the existing finally block that calls
setLoadingOlder(false); refer to loadOlder, getMessages, setLoadingOlder and
fetchThread when applying this change.
In `@components/ui/side-panel.tsx`:
- Around line 35-40: SidePanel is wrapped with forwardRef but never forwards
that ref into the ResizablePanel, so consumers get null; update the SidePanel
component to pass the incoming ref down by adding ref={ref} (or
ref={forwardedRef} if you rename) to the ResizablePanel element so the forwarded
ref from forwardRef(SidePanel) points to the ResizablePanel instance (ensure any
existing ref variables like measureRef remain unchanged).
- Around line 56-81: The AnimatePresence wrapper is around a motion.div that
never unmounts so close animations on the nested content (the panel guarded by
panelOpen) never run; move the AnimatePresence so it directly wraps the
conditional panelOpen content instead of the static motion.div. Concretely, keep
the static header/button block (renderButton(handlePanelOpen)) as a plain
element, then wrap the conditional block that renders children (the motion.div
currently inside {panelOpen && ...}) with AnimatePresence so AnimatePresence can
track its mount/unmount; ensure you preserve the existing exit/transition props
on that motion.div and keep keys consistent for correct animations.
In `@hooks/hackathon/use-hackathon-queries.ts`:
- Around line 192-193: The query key mismatch: useQuery is passing params but
hackathonKeys.exploreSubmissions(id) does not accept params; update the
centralized key factory hackathonKeys.exploreSubmissions to accept an optional
params argument (e.g., exploreSubmissions(id, params)) and include params in the
returned array, then switch the hook use-hackathon-queries.ts to call
hackathonKeys.exploreSubmissions(hackathonId, params) instead of manually
building ['hackathon','exploreSubmissions',hackathonId,params] so cache keys are
consistent across usages.
In `@hooks/hackathon/use-team-posts.ts`:
- Around line 167-175: Create a typed helper (e.g., getErrorMessage(err:
unknown): string) that imports AxiosError from 'axios' and implements the logic
currently duplicated in the catches: if err is an AxiosError and
err.response?.data?.message exists return it, else if err instanceof Error
return err.message, otherwise return a default message like 'Failed to create
team post'; remove all uses of 'as any' and call this helper from each catch
block in use-team-posts.ts (the three identical catch sites around the team-post
creation/updates) so the duplicated logic is consolidated and no any casts are
used.
In `@hooks/use-require-auth-for-action.ts`:
- Around line 21-33: The handler currently only blocks when the user is
unauthenticated and not loading, allowing action(...args) to run while isLoading
is true; update the guard so the protected action never runs during auth
loading—either add an early check "if (isLoading) return" before the existing
unauthenticated check, or combine conditions to handle loading separately (e.g.,
if (isLoading) return; if (!isAuthenticated) { const redirectTo = ...;
openAuthModal({ redirectTo }); return; }) so use of isLoading, isAuthenticated,
openAuthModal and action(...args) prevents execution while auth is still
loading.
In `@lib/api/organization.ts`:
- Line 280: Remove the noisy debug console.trace call from getOrganization:
delete the console.trace('org') invocation in the getOrganization function (or
replace it with a conditional structured logger call that only runs when debug
is enabled via an env flag), ensuring no leftover debug statements remain and
behavior/functionality of getOrganization is unchanged.
---
Outside diff comments:
In `@components/hackathons/team-formation/CreateTeamPostModal.tsx`:
- Around line 110-127: The useEffect handling form.reset only covers edit mode
(open && initialData) and the close case (!open), so reopening the modal in
create mode (open && !initialData) can keep stale values; add an explicit branch
for open && !initialData that calls form.reset with clean defaults (e.g.,
teamName: '', description: '', lookingFor: [], maxSize: undefined or default,
contactMethod: 'email', contactInfo: ''), and also call setStep('IDENTITY') and
setSkillInputs({}) there to ensure identity and skill inputs are reset; update
the useEffect around the existing form.reset, setStep, and setSkillInputs logic
to include this new create-mode reset branch.
In `@types/messages.ts`:
- Around line 6-11: The OtherUser interface currently declares username as a
required string; change it to an optional property (username?: string) to match
other user/profile types and existing defensive checks across the
codebase—update the OtherUser type definition so any code referencing OtherUser
will treat username as possibly undefined.
---
Duplicate comments:
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx:
- Around line 52-62: SubmissionCard renders the same category twice because
category is destructured from submission and also accessed as
submission.category; remove the duplicate by keeping a single badge render
(either the unconditional {category} span or the conditional
{submission.category} span) and ensure the remaining span uses the canonical
source (prefer using submission.category or the destructured category
consistently) and remains conditionally rendered if category can be undefined;
update the JSX in SubmissionCard.tsx to eliminate the redundant span.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx:
- Around line 87-117: Replace the untyped catches using "err: any" with "err:
unknown" and narrow the error before accessing properties: in the verification
catch (around setVerificationError) read status from err.status (not
err.response.status) after a type-guard (e.g., typeof err === 'object' && err
!== null && 'status' in err) and map 404 to 'User not found...' otherwise a
generic message; in the invite catch (around inviteMutation.mutateAsync and
setVerificationError) similarly narrow err and read err.message or err.status
(and err.data?.message if your API error shape includes data) instead of
err.response?.data?.message; you can add a small helper/isApiError type-guard
used by both handlers to keep code DRY while keeping setIsVerifying,
setInviteIdentifier, setInviteMessage, inviteIdentifier, inviteMessage and
inviteMutation.mutateAsync logic unchanged.
In `@components/hackathons/team-formation/CreateTeamPostModal.tsx`:
- Around line 219-225: In the catch block inside CreateTeamPostModal (the
catch(err: unknown) handler), remove the "as any" casts and replace them with a
small user-defined type guard (e.g., isResponseError(obj: unknown): obj is {
response: { data?: { message?: string } } }) so you can safely access
response.data.message; fall back to err instanceof Error for Error.message and
otherwise use the generic fallback string. Update the extraction logic to call
that type guard instead of casting to any.
---
Nitpick comments:
In `@app/`(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsx:
- Around line 216-232: Extract the hardcoded roles array into a single exported
constant (e.g., ROLES or HACKATHON_ROLES) and import it into this component,
replacing the inline array used in the map; update the map to iterate over that
constant and keep the existing handlers (DropdownMenuItem onClick that calls
setRoleFilter and setPage). Ensure the constant name is used consistently
wherever roles are needed (including any backend type or enum adapters) so the
DropdownMenuItem key and displayed label come from the shared constant and
remain in sync with backend expectations.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsx:
- Around line 20-32: The SubmissionCard currently falls back to 'Anonymous' and
empty avatar when participant is missing for INDIVIDUAL submissions; update the
SubmissionCard component to, when isTeam is false and participant is undefined
but participantId exists, asynchronously fetch the participant data (e.g., via
an existing user/participant service like fetchParticipantById or getUserById
inside a useEffect) and store it in local state so submitterName and
submitterAvatar use the fetched data instead of the fallback; keep the existing
team logic (isTeam, teamName, teamMembers) and retain the final fallbacks
('Anonymous' and '') only if the fetch returns nothing.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx:
- Line 50: The declared but unused refresh variable from useRefreshHackathon in
MyTeamView should be either removed or actually invoked to invalidate the
hackathon cache after team mutations; locate the const refresh =
useRefreshHackathon(hackathonSlug) line and either delete it if you don't need
extra invalidation, or call refresh() inside the onSuccess callbacks of your
team mutation handlers (e.g., handleCreateTeam, handleJoinTeam, handleLeaveTeam
or whatever mutation functions are used in this component) so the hackathon data
is refreshed after successful operations.
- Around line 14-20: Remove the unused hook import useInvitationActions from the
import list at the top of MyTeamView.tsx so only the actually used hooks remain
(useLeaveTeam, useInviteToTeam, useTransferLeadership, useRefreshHackathon);
update the import statement that currently includes useInvitationActions to
exclude it to eliminate the unused-symbol lint error.
In `@components/avatars/BasicAvatar.tsx`:
- Around line 23-33: The username paragraph in BasicAvatar always applies
"max-w-[100px] truncate" regardless of the truncate prop, causing inconsistent
behavior with the name field; update the username <p> className to use the same
conditional logic as the name (use cn and apply 'max-w-[100px] truncate' only
when the truncate prop is truthy) or, if intentional, add a comment/docstring to
the BasicAvatar component explaining why username truncation is always enforced;
target the className on the username <p> and the truncate prop usage in the
BasicAvatar component to make the change.
In `@components/hackathons/hackathonBanner.tsx`:
- Around line 117-128: Remove the dead commented-out helper function
formatDateWithFallback (the commented block containing formatDateWithFallback),
or if it is still needed restore and use it where dates are formatted; ensure no
leftover commented code remains—either delete the commented block entirely or
reinstate the function and replace inline date formatting with calls to
formatDateWithFallback to keep the code clean and consistent.
In `@components/hackathons/HackathonComments.tsx`:
- Line 12: Replace the function declaration for HackathonComments with a const
arrow function that has an explicit React.FC (or appropriate) type annotation
using HackathonCommentsProps; specifically change the export function
HackathonComments({ hackathonId }: HackathonCommentsProps) declaration to a
const HackathonComments: React.FC<HackathonCommentsProps> = ({ hackathonId }) =>
{ ... } (ensure export is preserved and update any default/ named export usage
accordingly).
In `@components/hackathons/team-formation/CreateTeamPostModal.tsx`:
- Around line 262-269: The JSX uses nested ternary expressions inside the
className in CreateTeamPostModal (the className that references step, s.id,
steps, and idx) which hurts readability; replace the nested ternaries with
cn/clsx object or array-based conditional entries (e.g., provide keys like
'bg-primary text-black', 'bg-primary/20 text-primary', 'bg-white/5
text-gray-500' mapped to boolean conditions such as step === s.id,
steps.findIndex(x => x.id === step) > idx, else fallback) so the logic is
declarative and duplicated pattern at lines referencing step/s.id (including the
other occurrence at the 274-277 block) is unified, using the existing cn helper
to compute the final class string.
- Around line 372-377: Rename the UI event callbacks to use the handle*
convention and update all references: change addRole -> handleAddRole,
removeRole -> handleRemoveRole, removeSkill -> handleRemoveSkill (and any
similarly named callbacks noted at the other locations), then update every JSX
prop and internal call that uses these functions (e.g., the BoundlessButton
onClick, any other onClick/onChange handlers at the other ranges) and adjust any
TypeScript types/exports/imports or tests that reference the old names so the
component compiles and all usages are consistent.
In `@components/messages/MessagesTrigger.tsx`:
- Around line 22-24: In MessagesTrigger.tsx simplify the onClick call to
openMessages by removing the redundant explicit conversationId: undefined;
update the onClick handler that currently calls openMessages({ conversationId:
undefined, trigger: buttonRef.current }) to pass only the trigger property
(openMessages({ trigger: buttonRef.current })), keeping the same behavior while
relying on the optional conversationId default; locate this change in the
MessagesTrigger component’s onClick handler where openMessages is invoked.
In `@components/messages/TetherMessage.tsx`:
- Around line 372-408: The exit animations aren't firing because the component
returns null when !isOpen so AnimatePresence is never present to observe
unmounts; instead, always render AnimatePresence and move the isOpen/isMobile
conditional inside it (i.e., replace the early `if (!isOpen) return null;` with
rendering <AnimatePresence> and conditionally render the mobile dialog when
`isOpen && isMobile`), keeping the existing motion elements (the overlay
motion.div and panel motion.div that use overlayRef and panelRef) and their exit
props intact so AnimatePresence can play the exit animations.
In `@components/ui/side-panel.tsx`:
- Around line 66-69: Replace inline JSX ternary class fragments with precomputed
variables using the cn helper: create a headerClassName (or panelClassName) and
a panelRadiusClassName computed from panelOpen and other state, then use those
variables in the className props instead of the ternary expressions found in the
className on the element that uses panelOpen (around the className with 'flex
w-full items-center...' and the other occurrence at lines 114-117). Ensure you
reference and reuse the existing cn utility, compute the conditional segments
once above the return (e.g., headerClassName = cn('base-classes', panelOpen &&
'pr-3')), and replace the JSX ternaries with the new variables to clean up the
markup.
In `@hooks/hackathon/use-hackathon-queries.ts`:
- Around line 496-515: The current useRefreshHackathon function performs cache
invalidations sequentially which is slower; change it to run the
invalidateQueries calls in parallel by calling all hackathonKeys invalidations
(hackathonKeys.detail, participants, submissions, winners, myTeam) and awaiting
Promise.all on the resulting promises instead of awaiting each one individually
so the invalidations execute concurrently.
In `@lib/providers/hackathonProvider.tsx`:
- Around line 142-144: Add explicit TODO/deprecation comments to the no-op
setters to indicate they are temporary for interface compatibility and should be
removed later: annotate the setLoading and setError no-op implementations (in
the hackathon provider implementation where setLoading: () => {} and setError:
() => {} are defined) with a clear TODO or `@deprecated` note explaining why they
are kept, the intended removal timeline or condition, and optionally a reference
to the migration issue/PR so future maintainers know when it can be cleaned up.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7b35cc43-cf72-44c4-b518-46e6559ab9a8
📒 Files selected for processing (39)
app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsxapp/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsxapp/(landing)/hackathons/[slug]/components/sidebar/index.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/FindTeam.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/Participants.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/Submissions.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/participants/ParticipantCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/submissions/SubmissionCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/teams/TeamCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/index.tsxapp/me/notifications/page.tsxapp/providers.tsxcomponents/auth/AuthModalProvider.tsxcomponents/avatars/BasicAvatar.tsxcomponents/bounties/BountyComments.tsxcomponents/crowdfunding/campaign-comments-tab.tsxcomponents/hackathons/HackathonComments.tsxcomponents/hackathons/hackathonBanner.tsxcomponents/hackathons/hackathonStickyCard.tsxcomponents/hackathons/participants/profileCard.tsxcomponents/hackathons/team-formation/CreateTeamPostModal.tsxcomponents/layout/sidebar.tsxcomponents/messages/MessagesProvider.tsxcomponents/messages/MessagesTrigger.tsxcomponents/messages/TetherMessage.tsxcomponents/project-details/comment-section/project-comments.tsxcomponents/ui/side-panel.tsxhooks/hackathon/use-hackathon-queries.tshooks/hackathon/use-hackathon-status.tshooks/hackathon/use-hackathon-submissions.tshooks/hackathon/use-team-posts.tshooks/use-follow.tshooks/use-require-auth-for-action.tslib/api/hackathons.tslib/api/organization.tslib/providers/hackathonProvider.tsxtypes/hackathon/participant.tstypes/messages.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- app/(landing)/hackathons/[slug]/components/tabs/contents/participants/ParticipantCard.tsx
- app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx
- app/(landing)/hackathons/[slug]/components/sidebar/index.tsx
- app/providers.tsx
- hooks/use-follow.ts
app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx
Outdated
Show resolved
Hide resolved
app/(landing)/hackathons/[slug]/components/tabs/contents/teams/TeamCard.tsx
Show resolved
Hide resolved
…tency across landing pages and hackathon sections
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
components/hackathons/participants/hackathonParticipant.tsx (1)
36-43:⚠️ Potential issue | 🟡 MinorUpdate copy to avoid hardcoded “green” after switching to
primary.Line 37 still says “green indicator dot,” but the UI now uses semantic color tokens. This can become inaccurate if
primaryis not green in future themes.✏️ Suggested copy tweak
- <span className='text-primary font-semibold'> - green indicator dot - </span>{' '} + <span className='text-primary font-semibold'> + status indicator dot + </span>{' '}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/participants/hackathonParticipant.tsx` around lines 36 - 43, Replace the hardcoded phrase "green indicator dot" with a theme-agnostic description to match the semantic token usage; update the copy near the span with className 'text-primary font-semibold' (inside the HackathonParticipant component in hackathonParticipant.tsx) to say something like "primary-colored indicator dot" or "primary indicator dot" so it no longer assumes the primary color is green.features/projects/components/MilestoneSubmissionModal.tsx (1)
176-176:⚠️ Potential issue | 🟡 MinorTypo: erroneous "1 " prefix before the date.
There appears to be a stray
1before{formatDate(milestone.deliveryDate)}which will render as "1 January 15, 2026" instead of just the formatted date.🐛 Suggested fix
- <span>1 {formatDate(milestone.deliveryDate)}</span> + <span>{formatDate(milestone.deliveryDate)}</span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@features/projects/components/MilestoneSubmissionModal.tsx` at line 176, Remove the stray literal "1 " preceding the date render in the MilestoneSubmissionModal component: locate the JSX where it currently reads something like <span>1 {formatDate(milestone.deliveryDate)}</span> and update it to render only the formatted date (e.g., <span>{formatDate(milestone.deliveryDate)}</span>), ensuring you reference the formatDate(milestone.deliveryDate) expression inside the MilestoneSubmissionModal component.features/projects/components/CreateProjectModal/SuccessScreen.tsx (1)
72-117:⚠️ Potential issue | 🟡 MinorUse camelCase for SVG attributes in JSX.
Several SVG attributes use kebab-case (
stroke-opacity,stroke-width,color-interpolation-filters,flood-opacity,stop-color,stop-opacity) which will cause React warnings. JSX requires camelCase attribute names.🔧 Proposed fix for SVG attributes
stroke='url(`#paint0_radial_5702_15569`)' - stroke-opacity='0.98' - stroke-width='0.909091' + strokeOpacity='0.98' + strokeWidth='0.909091' /> </g> <defs> <filter id='filter0_f_5702_15569' x='0.149948' y='0.150436' width='215.7' height='215.7' filterUnits='userSpaceOnUse' - color-interpolation-filters='sRGB' + colorInterpolationFilters='sRGB' > - <feFlood flood-opacity='0' result='BackgroundImageFix' /> + <feFlood floodOpacity='0' result='BackgroundImageFix' /><radialGradient ... > - <stop stop-color='white' stop-opacity='0.48' /> - <stop offset='1' stop-color='white' stop-opacity='0.08' /> + <stop stopColor='white' stopOpacity='0.48' /> + <stop offset='1' stopColor='white' stopOpacity='0.08' /> </radialGradient>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@features/projects/components/CreateProjectModal/SuccessScreen.tsx` around lines 72 - 117, In SuccessScreen.tsx inside the motion.svg block, several SVG attributes use kebab-case (e.g., stroke-opacity, stroke-width, color-interpolation-filters, flood-opacity, stop-color, stop-opacity) which causes React JSX warnings; update those attributes to their camelCase equivalents (strokeOpacity, strokeWidth, colorInterpolationFilters, floodOpacity, stopColor, stopOpacity) throughout the <motion.svg> / <defs> / filter elements so JSX uses camelCase props and preserves original values and casing only for IDs like filter0_f_5702_15569 and paint0_radial_5702_15569.components/hackathons/team-formation/MyInvitationsList.tsx (1)
306-314:⚠️ Potential issue | 🟡 MinorInconsistent hover color: uses hardcoded hex instead of design token.
The button uses
bg-primaryfor the base color buthover:bg-[#8ae63a]is still a hardcoded hex value. This is inconsistent with the PR's goal of migrating to design tokens.🔧 Suggested fix
<Button size='sm' - className='bg-primary text-black transition-all hover:bg-[`#8ae63a`]' + className='bg-primary text-black transition-all hover:bg-primary/80' onClick={() => acceptInvite(invite.id)} disabled={isProcessingReceived} >Alternatively, if a specific hover shade is needed, consider defining it as a design token (e.g.,
hover:bg-primary-dark).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/MyInvitationsList.tsx` around lines 306 - 314, The Accept button in MyInvitationsList uses a hardcoded hover color (hover:bg-[`#8ae63a`]) which breaks the design-token migration; update the Button's className to use the design token (for example replace hover:bg-[`#8ae63a`] with an existing token like hover:bg-primary-dark or hover:bg-primary/hover:opacity-90 per your design system) so the base className remains 'bg-primary text-black transition-all' and the hover state uses the token instead of the hex literal; ensure the change is applied to the Button in MyInvitationsList where onClick calls acceptInvite(invite.id) and disabled uses isProcessingReceived.components/hackathons/hackathonBanner.tsx (1)
175-183:⚠️ Potential issue | 🟡 MinorInconsistent hover color: uses hardcoded hex instead of design token.
The button uses
bg-primarybut the hover statehover:bg-[#8fd93f]is a hardcoded hex value. This pattern appears again on line 316. Consider using a token-based hover approach for consistency with the PR's design token migration.🔧 Suggested fix
<Button onClick={!isAuthenticated ? handleRedirectToAuthScreen : onJoinClick} - className='bg-primary w-full py-5 text-base font-bold text-black hover:bg-[`#8fd93f`]' + className='bg-primary w-full py-5 text-base font-bold text-black hover:bg-primary/90' >Apply the same fix to line 316:
<Button onClick={onSubmitClick} - className='bg-primary w-full py-5 font-bold text-black hover:bg-[`#8fd93f`]' + className='bg-primary w-full py-5 font-bold text-black hover:bg-primary/90' >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/hackathonBanner.tsx` around lines 175 - 183, The Button component's className uses a hardcoded hover color (hover:bg-[`#8fd93f`]) instead of a design token; update the className on the Button (the element using props like onClick, buttonText, Calendar, ArrowRight) to use the project's primary hover token class (for example hover:bg-primary-hover or the established token like hover:bg-primary-600) and replace the identical hardcoded hover class at the other occurrence as well (the second Button around line 316) so both buttons consistently use the design token.components/hackathons/submissions/submissionTab.tsx (2)
51-58:⚠️ Potential issue | 🟡 MinorAvoid using
anytype formySubmission.The
mySubmission: anyviolates the coding guideline prohibitinganytypes. Define a proper type for the submission entity to ensure type safety.🔧 Suggested fix
+import type { Submission } from '@/types/submission'; // adjust path as needed interface SubmissionTabContentProps extends SubmissionTabProps { - mySubmission: any; // Type properly if possible + mySubmission: Submission | null; isLoadingMySubmission: boolean; - fetchMySubmission: () => Promise<any>; + fetchMySubmission: () => Promise<Submission | null>; removeSubmission: (id: string) => Promise<void>; hackathonId: string; hackathonSlug: string; }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/submissions/submissionTab.tsx` around lines 51 - 58, The prop mySubmission in SubmissionTabContentProps currently uses any; replace it with the concrete submission type used across the codebase (e.g., Submission, SubmissionEntity, or the hackathon submission interface) by importing that type and updating SubmissionTabContentProps to use it, and also adjust related signatures like fetchMySubmission's Promise<any> to Promise<ThatSubmissionType> and ensure removeSubmission, SubmissionTabProps consumers, and any state/hooks that reference mySubmission are updated to the new type to preserve type-safety.
358-429:⚠️ Potential issue | 🟡 MinorUse a stable identifier for React keys instead of array index fallback.
The
SubmissionCardPropstype defines_idas optional, meaningsubmissionIdcan be undefined. When it is, the code falls back to using the arrayindexas the React key (key={submissionId || index}). This causes rendering issues if the submission list reorders. Ensure all submissions always have a stable_idvalue, or add validation to guarantee it exists before rendering.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/submissions/submissionTab.tsx` around lines 358 - 429, The map uses submissionId (computed from submission._id or submission.id) with a fallback to index for React keys (key={submissionId || index}), which is unstable; ensure a stable key by validating/filtering submissions before rendering so every item has a persistent id: update the code that prepares the submissions array (or add a pre-map filter) to remove or fix entries missing _id/id, or compute a deterministic fallback key (e.g., combine projectName+submitterName+submittedDate) and use that instead of the numeric index; change usages in the rendering block (SubmissionCard and SubmissionCard2) to rely only on the validated submissionId.
♻️ Duplicate comments (2)
components/hackathons/team-formation/TeamRecruitmentPostCard.tsx (1)
283-294:⚠️ Potential issue | 🟠 MajorNormalize
displayLeaderinstead of assigning a possibly undefined source.At Line 293,
leaderObjectcan be undefined, butDisplayLeaderrequiresnameandusername. This is still the same unresolved type-safety issue previously flagged.Suggested fix
- const displayLeader: DisplayLeader = leaderObject; + const displayLeader: DisplayLeader = { + name: leaderObject?.name ?? 'Unknown User', + username: leaderObject?.username ?? 'user', + image: leaderObject?.image, + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/hackathons/team-formation/TeamRecruitmentPostCard.tsx` around lines 283 - 294, leaderObject can be undefined but displayLeader is typed as DisplayLeader (requires name and username); change the assignment in TeamRecruitmentPostCard (symbols: leaderObject, displayLeader, DisplayLeader, TeamMember, post.leader) to normalize into a fully populated DisplayLeader by using leaderObject when present otherwise constructing a fallback DisplayLeader that pulls name/username from post.leader if available or uses empty-string defaults, so displayLeader always satisfies DisplayLeader without unsafe assertions.app/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx (1)
87-93:⚠️ Potential issue | 🟠 MajorReplace
err: anywithunknown+ Axios-safe narrowing in invite error paths.
catch (err: any)appears in both invite verification and invite send flows. This is a repeated unresolved issue and bypasses type safety.Suggested fix
+import axios from 'axios'; ... - } catch (err: any) { + } catch (err: unknown) { + const is404 = axios.isAxiosError(err) && err.response?.status === 404; setVerificationError( - err.response?.status === 404 - ? 'User not found. Please check the username.' - : 'Failed to verify user. Please try again.' + is404 + ? 'User not found. Please check the username.' + : 'Failed to verify user. Please try again.' ); setIsVerifying(false); return; } ... - } catch (err: any) { - const errorMessage = - err.response?.data?.message || - err.message || - 'Failed to send invitation. Please try again.'; + } catch (err: unknown) { + const errorMessage = axios.isAxiosError<{ message?: string }>(err) + ? (err.response?.data?.message ?? + err.message ?? + 'Failed to send invitation. Please try again.') + : err instanceof Error + ? err.message + : 'Failed to send invitation. Please try again.'; setVerificationError(errorMessage); } finally {As per coding guidelines: "
**/*.{ts,tsx}: Do not use 'any' type; always search for proper Trustless Work entity types".Also applies to: 109-114
🤖 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 87 - 93, Replace the non-typed catch clauses in the MyTeamView invite verification and invite send flows by changing catch (err: any) to catch (err: unknown) and then narrow safely for Axios errors before accessing response/status; use axios.isAxiosError(err) (import from 'axios') or an instanceof/ type-guard to access err.response?.status, and for non-Axios cases extract a safe message via err instanceof Error ? err.message : String(err) when calling setVerificationError / setInviteError and setIsVerifying; update both occurrences (the verification block using setVerificationError and the send-invite block using setInviteError) to follow this pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/`(landing)/hackathons/[slug]/components/header/TitleAndInfo.tsx:
- Around line 110-139: When participantCount > 0 but displayParticipants is
empty the UI still renders the separator and an empty AvatarGroup; change the
conditional rendering so that when displayParticipants.length === 0 you render a
simple numeric fallback (e.g., a text node "{participantCount.toLocaleString()}
participants" styled similarly) instead of the AvatarGroup. Update the block
that currently renders AvatarGroup / AvatarGroupCount to check
displayParticipants.length and render either the AvatarGroup with
Avatar/AvatarImage/AvatarFallback and AvatarGroupCount or a single fallback
element (span/div) showing the localized participantCount and accessible label.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsx:
- Around line 96-100: The anchor using actionHref with target determined by the
type prop (in ResourceCard) needs rel="noopener noreferrer" whenever target is
"_blank" to prevent reverse tabnabbing; update the anchor element (the <a> that
uses actionHref, target and containerClassName) to conditionally include
rel="noopener noreferrer" when type !== 'read' && type !== 'download' (i.e.,
whenever target="_blank").
In `@components/hackathons/submissions/submissionCard.tsx`:
- Line 143: Refactor the conditional className strings in the SubmissionCard
component to use the cn helper from "@/lib/utils" instead of inline ternaries
(replace occurrences around the className on the root div and at the class
usages referenced in the review: lines near the root className, the ones around
lines 157, 167-173, and 265-269), and make interactive affordances conditional
on the presence of the onViewClick prop (remove unconditional "cursor-pointer"
and hover:border classes and only include them via cn when onViewClick is
defined). Also remove the commented-out score block at the noted location
(around line 257). Ensure you import cn from "@/lib/utils" and update the
className expressions to pass static and conditional classes to cn for clarity.
In `@components/hackathons/team-formation/TeamDetailsSheet.tsx`:
- Line 141: isLeader currently only checks post.leader?.id and therefore hides
leader controls when leader is missing; update the isLeader calculation to
resolve the leader id using a resilient fallback chain (use post.leader?.id
first, then derive from the membership array such as post.members?.find(m =>
m.role === 'leader')?.id, and finally any other canonical owner id present like
post.createdById or post.userId) and compare that resolved leader id to
user?.id; change the isLeader assignment (the variable named isLeader) to use
this resolvedLeaderId lookup so leader controls appear when membership data
provides the leader even if post.leader is absent.
In `@components/messages/TetherMessage.tsx`:
- Around line 552-568: The loadOlder handler currently calls setError which
triggers the global error guard and hides the thread; instead create a dedicated
local state (e.g., loadOlderError) and use that for pagination failures: replace
setError(...) in catch with setLoadOlderError(...), clear
setLoadOlderError(null) at start of loadOlder and on successful fetch, and leave
the existing global setError usage for real thread-level errors; update the
loadOlder useCallback dependencies to include the new setter and ensure UI
elements that show pagination errors/toasts read from loadOlderError rather than
the global error state.
- Around line 503-526: The fetchThread useCallback couples markConversationRead
into the critical path and is vulnerable to stale async responses overwriting
newer threads; change fetchThread so it (1) performs getConversation/getMessages
together, sets setConversation/setMessages/setHasMore/setNextCursor only if the
conversationId matches a locally captured id (or use an incrementing
requestToken/abort controller) to guard against older responses, and (2) calls
markConversationRead in a separate try/catch so any failure does not call
setError or fail the main fetch (log or ignore mark-read errors). Keep
references to fetchThread, getConversation, getMessages, markConversationRead,
setConversation, setMessages, setError and conversationId when implementing
these guards.
In `@components/ProjectCard.tsx`:
- Around line 186-191: Remove the duplicate category Badge so the category is
rendered only once: locate the two Badge elements in ProjectCard (the ones that
render {project.category}) and delete or comment out the redundant Badge (the
second Badge with className='bg-active-bg border-primary ...' that duplicates
the first Badge with className='flex-shrink-0 rounded-[4px] border ...'); ensure
only the intended Badge component remains and the UI/styling matches design by
keeping the correct className on the single Badge that stays.
In `@features/projects/components/CreateProjectModal/SuccessScreen.tsx`:
- Around line 139-144: The underlined "Projects Page" span in SuccessScreen (the
element with className 'text-primary font-medium underline') should be a real
navigable link; replace the <span> with your app's link component (e.g.,
react-router-dom's Link or next/link's Link) pointing to the projects route
(e.g., "/projects"), add an accessible label/role if needed, and import the
appropriate Link at the top of SuccessScreen.tsx so the text both looks and
behaves like a clickable navigation element.
In `@hooks/hackathon/use-hackathon-queries.ts`:
- Around line 291-299: The hooks useMyTeam, useMyInvitations, and
useTeamInvitations currently return response.data unconditionally which can
cache failed API responses; update each queryFn (in useMyTeam, useMyInvitations,
useTeamInvitations) to check response.success after calling
getMyTeam/getMyInvitations/getTeamInvitations and if success is false throw a
new Error(using response.error or a descriptive message) so react-query treats
it as an error instead of caching a failed payload; ensure the thrown error
includes any server message to aid debugging.
---
Outside diff comments:
In `@components/hackathons/hackathonBanner.tsx`:
- Around line 175-183: The Button component's className uses a hardcoded hover
color (hover:bg-[`#8fd93f`]) instead of a design token; update the className on
the Button (the element using props like onClick, buttonText, Calendar,
ArrowRight) to use the project's primary hover token class (for example
hover:bg-primary-hover or the established token like hover:bg-primary-600) and
replace the identical hardcoded hover class at the other occurrence as well (the
second Button around line 316) so both buttons consistently use the design
token.
In `@components/hackathons/participants/hackathonParticipant.tsx`:
- Around line 36-43: Replace the hardcoded phrase "green indicator dot" with a
theme-agnostic description to match the semantic token usage; update the copy
near the span with className 'text-primary font-semibold' (inside the
HackathonParticipant component in hackathonParticipant.tsx) to say something
like "primary-colored indicator dot" or "primary indicator dot" so it no longer
assumes the primary color is green.
In `@components/hackathons/submissions/submissionTab.tsx`:
- Around line 51-58: The prop mySubmission in SubmissionTabContentProps
currently uses any; replace it with the concrete submission type used across the
codebase (e.g., Submission, SubmissionEntity, or the hackathon submission
interface) by importing that type and updating SubmissionTabContentProps to use
it, and also adjust related signatures like fetchMySubmission's Promise<any> to
Promise<ThatSubmissionType> and ensure removeSubmission, SubmissionTabProps
consumers, and any state/hooks that reference mySubmission are updated to the
new type to preserve type-safety.
- Around line 358-429: The map uses submissionId (computed from submission._id
or submission.id) with a fallback to index for React keys (key={submissionId ||
index}), which is unstable; ensure a stable key by validating/filtering
submissions before rendering so every item has a persistent id: update the code
that prepares the submissions array (or add a pre-map filter) to remove or fix
entries missing _id/id, or compute a deterministic fallback key (e.g., combine
projectName+submitterName+submittedDate) and use that instead of the numeric
index; change usages in the rendering block (SubmissionCard and SubmissionCard2)
to rely only on the validated submissionId.
In `@components/hackathons/team-formation/MyInvitationsList.tsx`:
- Around line 306-314: The Accept button in MyInvitationsList uses a hardcoded
hover color (hover:bg-[`#8ae63a`]) which breaks the design-token migration; update
the Button's className to use the design token (for example replace
hover:bg-[`#8ae63a`] with an existing token like hover:bg-primary-dark or
hover:bg-primary/hover:opacity-90 per your design system) so the base className
remains 'bg-primary text-black transition-all' and the hover state uses the
token instead of the hex literal; ensure the change is applied to the Button in
MyInvitationsList where onClick calls acceptInvite(invite.id) and disabled uses
isProcessingReceived.
In `@features/projects/components/CreateProjectModal/SuccessScreen.tsx`:
- Around line 72-117: In SuccessScreen.tsx inside the motion.svg block, several
SVG attributes use kebab-case (e.g., stroke-opacity, stroke-width,
color-interpolation-filters, flood-opacity, stop-color, stop-opacity) which
causes React JSX warnings; update those attributes to their camelCase
equivalents (strokeOpacity, strokeWidth, colorInterpolationFilters,
floodOpacity, stopColor, stopOpacity) throughout the <motion.svg> / <defs> /
filter elements so JSX uses camelCase props and preserves original values and
casing only for IDs like filter0_f_5702_15569 and paint0_radial_5702_15569.
In `@features/projects/components/MilestoneSubmissionModal.tsx`:
- Line 176: Remove the stray literal "1 " preceding the date render in the
MilestoneSubmissionModal component: locate the JSX where it currently reads
something like <span>1 {formatDate(milestone.deliveryDate)}</span> and update it
to render only the formatted date (e.g.,
<span>{formatDate(milestone.deliveryDate)}</span>), ensuring you reference the
formatDate(milestone.deliveryDate) expression inside the
MilestoneSubmissionModal component.
---
Duplicate comments:
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsx:
- Around line 87-93: Replace the non-typed catch clauses in the MyTeamView
invite verification and invite send flows by changing catch (err: any) to catch
(err: unknown) and then narrow safely for Axios errors before accessing
response/status; use axios.isAxiosError(err) (import from 'axios') or an
instanceof/ type-guard to access err.response?.status, and for non-Axios cases
extract a safe message via err instanceof Error ? err.message : String(err) when
calling setVerificationError / setInviteError and setIsVerifying; update both
occurrences (the verification block using setVerificationError and the
send-invite block using setInviteError) to follow this pattern.
In `@components/hackathons/team-formation/TeamRecruitmentPostCard.tsx`:
- Around line 283-294: leaderObject can be undefined but displayLeader is typed
as DisplayLeader (requires name and username); change the assignment in
TeamRecruitmentPostCard (symbols: leaderObject, displayLeader, DisplayLeader,
TeamMember, post.leader) to normalize into a fully populated DisplayLeader by
using leaderObject when present otherwise constructing a fallback DisplayLeader
that pulls name/username from post.leader if available or uses empty-string
defaults, so displayLeader always satisfies DisplayLeader without unsafe
assertions.
---
Nitpick comments:
In `@app/`(landing)/code-of-conduct/CodeOfConductContent.tsx:
- Around line 210-214: The JSX uses a ternary inside className to toggle TOC
item styles; replace this inline conditional with the project's class helper (cn
or clsx) to compose classes more clearly — update the element that renders the
TOC item (referencing activeSection and item.id) to call cn/clsx with base
classes plus a conditional object or array entry for the active state so the
active classes ('text-primary bg-[`#1a1a1a`]') are applied when activeSection ===
item.id and fallback classes ('text-gray-300') otherwise.
In `@app/`(landing)/disclaimer/DisclaimerContent.tsx:
- Around line 213-217: Replace the ternary-based className string for the
element that uses activeSection and item.id with a clsx-based composition to
improve readability: import clsx and build the classes around the static base
("block w-full rounded px-3 py-2 text-left text-sm transition-colors
hover:bg-[`#1a1a1a`]") and conditional entries for activeSection === item.id (add
"text-primary bg-[`#1a1a1a`]") vs the else case ("text-gray-300"); update the JSX
where className is set so it calls clsx(...) instead of the current template
literal tied to activeSection and item.id.
In `@app/`(landing)/hackathons/[slug]/components/header/TitleAndInfo.tsx:
- Line 93: TitleAndInfo.tsx uses literal hex colors in Separator/className
(e.g., 'bg-[`#334155`]' and 'bg-[`#111`]') which breaks tokenized theming; replace
these hard-coded hex values with the project's semantic theme utility classes
(e.g., the appropriate bg-... token from your design system or tailwind theme
tokens used elsewhere in this component) for each Separator/className occurrence
(lines around the Separator usages and other className instances at the
mentioned locations) so the header follows dark/light theme tokens consistently.
- Around line 15-36: Replace the function declarations getStatusLabel and
isActiveStatus with const arrow function expressions and add explicit return
type annotations: convert getStatusLabel(status: HackathonStatus) to a const
getStatusLabel: (status: HackathonStatus) => string = (status) => { ... }
(returning the same labels) and convert isActiveStatus(status: HackathonStatus)
to a const isActiveStatus: (status: HackathonStatus) => boolean = (status) =>
status === 'ACTIVE'; keep the same switch/body logic and exported visibility so
behavior is unchanged while matching the project's const-arrow + explicit type
convention.
- Around line 79-83: Replace the template-literal ternary classNames in the
TitleAndInfo component with a class helper (cn or clsx): import the helper at
the top, then change the container's className to use cn('flex items-center
gap-1.5', { 'text-primary': isActive, 'text-gray-400': !isActive }) and the
span's className to cn('h-2 w-2 rounded-full', { 'bg-primary animate-pulse':
isActive, 'bg-gray-500': !isActive }); reference the isActive prop/state and the
TitleAndInfo component to locate where to update the two className attributes.
In
`@app/`(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsx:
- Around line 73-93: Replace hard-coded hex color classes in ResourceCard with
semantic design tokens: update the icon wrapper div (currently using
bg-[`#232B20`]) and the containerClassName (bg-[`#0D0E10`] and hover:bg-[`#141517`])
to use the project's theme tokens (e.g., bg-surface, bg-surface-weak,
bg-surface-hover or equivalent) so theming is consistent; modify the class
strings in the Icon wrapper and the containerClassName constant inside
ResourceCard.tsx to use those tokens and adjust any accompanying text color
tokens (text-primary/group-hover:text-black) to match token names if needed.
- Around line 23-31: ResourceCard and its helper getActionIcon are arrow
functions missing explicit return types; update ResourceCard to annotate its
return as JSX.Element (or React.ReactElement) and annotate getActionIcon to
return JSX.Element | null (or React.ReactElement | null) to match repo
standards; locate the ResourceCard declaration (export const ResourceCard =
(...)) and the getActionIcon arrow function and add these return type
annotations to their signatures without changing implementation.
In `@app/`(landing)/privacy/TermsContent.tsx:
- Around line 210-214: Replace the template-literal ternary in the className
prop with the project's cn() utility from lib/utils: import and use cn(...) for
the className on the button/link that currently references activeSection and
item.id (the element where className={`block w-full ... ${ activeSection ===
item.id ? 'text-primary bg-[`#1a1a1a`]' : 'text-gray-300' }`}). Build a single
cn(...) call that always includes the base classes ("block w-full rounded px-3
py-2 text-left text-sm transition-colors hover:bg-[`#1a1a1a`]") and conditionally
adds 'text-primary bg-[`#1a1a1a`]' when activeSection === item.id or
'text-gray-300' otherwise, preserving existing behavior and using cn to merge
classes.
In `@app/me/crowdfunding/`[slug]/edit/components/TeamSection.tsx:
- Line 36: Change the loose any usage to the proper TeamMember indexed type:
update the onUpdate prop signature from "value: any" to "value: TeamMember[keyof
TeamMember]" and likewise update the updateMember function signature and its
parameter typing (the updateMember implementation that currently accepts value:
any at the updateMember function around line 160) to accept value:
TeamMember[keyof TeamMember]; ensure any internal uses respect the narrower type
and update related calls to pass that typed value.
- Around line 229-236: Replace the unstable array index key used in the team.map
render with a stable unique identifier: update the map to use a member-specific
key (e.g., member.id or member.email) instead of index; ensure the Team member
type (used by CollapsibleTeamMember) exposes that id/email, and keep passing the
same props (member, index, onUpdate, onRemove) to CollapsibleTeamMember so
behavior is unchanged while React can track items reliably.
- Around line 123-124: Replace the hardcoded background hex in the selected
state within the TeamSection component with the primary token to keep styling
consistent; specifically, update the ternary branch that currently uses
bg-[`#A7F95014`] to use bg-primary/8 so it matches the existing pattern (e.g.,
alongside border-primary and text-primary) in the TeamSection JSX.
In `@components/auth/LoginWrapper.tsx`:
- Around line 43-47: The code calls searchParams.get('callbackUrl') twice when
computing rawCallbackUrl; extract that result into a local const (e.g., const
callbackParam = searchParams.get('callbackUrl')) and then use callbackParam (and
decodeURIComponent(callbackParam) when present) in the rawCallbackUrl
expression, keeping the existing fallback to callbackUrlProp and
process.env.NEXT_PUBLIC_APP_URL || '/' and preserving behavior in
LoginWrapper.tsx.
In `@components/auth/TwoFactorVerify.tsx`:
- Line 150: In TwoFactorVerify (the element with className string containing
"bg-primary h-12 w-full rounded-lg font-bold text-black transition-colors
hover:bg-[`#96e048`] disabled:opacity-50"), remove the hard-coded hover color and
replace it with the corresponding design token variant (e.g., a hover variant of
the primary token used for "bg-primary") so the hover style uses the token
system instead of "#96e048"; update the className to use that token-based hover
utility (the same token family as bg-primary) so hover styles remain consistent
with the design tokens.
In `@components/hackathons/hackathonNavTabs.tsx`:
- Line 35: Replace the inline ternary template string used in the tab element's
className with the project's class-composition helper (cn or clsx) to improve
readability and consistency: locate the className on the tab element in the
HackathonNavTabs component where isActive is used and call cn/clsx to always
include the base classes ("relative px-4 py-4 text-sm font-medium
whitespace-nowrap transition-colors duration-200") plus a conditional entry for
'text-primary' when isActive and 'text-gray-400 hover:text-white' when not;
ensure you import the helper (cn or clsx) at the top of hackathonNavTabs.tsx and
remove the ternary/template literal.
In `@components/hackathons/hackathonStickyCard.tsx`:
- Around line 38-63: The component currently types status and backendStatus as
string; update HackathonStickyCardProps to use the concrete hackathon status
type (e.g., import and use HackathonStatus or the shared BackendHackathonStatus
union) instead of string, update the destructured prop name backendStatus:
backendStatus to be typed as that status type, and ensure useHackathonStatus
invocation and the local status constant use the same imported type; add the
import for the shared status type and replace any "status?: string" and "status:
backendStatus" occurrences with the precise status type so compile-time checks
cover UI branches.
- Around line 214-243: Two Button instances in hackathonStickyCard.tsx (the one
rendering the register/join button near getRegisterButtonText and the Submit
Project Button rendered when status === 'ongoing' and isRegistered &&
!hasSubmitted with onSubmitClick) use a hard-coded hover color
hover:bg-[`#8fd93f`]; replace those hard-coded hex classes with your theme token
hover class (e.g., hover:bg-primary-600 or hover:bg-primary-hover depending on
your design system) so the buttons use the project's primary token variant for
hover styling.
In `@components/hackathons/overview/hackathonPrizes.tsx`:
- Line 101: The class list on the prize title div (the element in
components/hackathons/overview/hackathonPrizes.tsx rendering the prize title)
contains a redundant utility "group-hover:text-primary" which duplicates the
base "text-primary"; remove "group-hover:text-primary" from the className on
that div (the element with 'text-lg font-bold transition-colors duration-300')
so only "text-primary" remains.
In `@components/hackathons/overview/hackathonTimeline.tsx`:
- Around line 32-109: The color variants are identical so getEventColor, the
local color variable in the events.map callback, and the duplicate entries in
colorClasses are dead; either remove getEventColor and the color lookup and
replace usage of colorClasses[color] with a single shared class object, or
restore semantic differentiation by updating colorClasses (keys: emerald, amber,
blue, purple, slate) to contain distinct CSS tokens (e.g., different text:, bg:,
border: and iconBg: classes) so getEventColor(event.event) actually maps to
visible variants; update the events.map usage (where Icon and color are
computed) accordingly to match your chosen approach.
In `@components/hackathons/overview/joinHackathon.tsx`:
- Line 51: The component JoinHackathon has inconsistent theming: replace
hard-coded hex utility classes (e.g., via-[`#8fd93f`]/20, to-[`#8fd93f`],
hover:from-[`#8fd93f`]) with the corresponding design-token utilities (e.g.,
via-primary/20, to-primary, hover:from-primary) so the className strings use
only token-based colors; update the className on the div containing
"border-primary/30 from-primary/20 to-primary/20 via-[`#8fd93f`]/20" and the other
occurrence around line 71 to use the token names (ensure plural/suffix
consistency like /20 opacity) so theming is uniform across JoinHackathon.
In `@components/hackathons/overview/RegisterHackathonModal.tsx`:
- Line 77: The button in RegisterHackathonModal.tsx currently hardcodes a hover
color via "hover:bg-[`#8fd93f`]" — replace that hardcoded hex with the project
theme token used elsewhere (e.g., the Tailwind token used for primary/hover
states) so the hover state stays consistent with design tokens; update the
className on the register button (the element with className containing
"bg-primary text-black hover:bg-[`#8fd93f`]") to use the appropriate tokenized
hover class (for example the project's primary-hover token) instead of the hex
value.
In `@components/hackathons/submissions/submissionCard.tsx`:
- Line 257: Remove the dead JSX comment in the SubmissionCard component: either
delete the commented-out line "{/* {score && <span
className="text-primary">Score: {score}</span>} */}" or implement it properly by
rendering the score when available (use the existing score prop/state in
SubmissionCard and render <span className="text-primary">Score: {score}</span>
conditionally). Ensure you update the component's props/type definitions if
needed to include score and run type checks so no unused/commented placeholders
remain.
In `@components/hackathons/submissions/SubmissionDetailModal.tsx`:
- Around line 168-174: Replace the template-literal + nested ternary className
usages in SubmissionDetailModal with clsx: import clsx at the top, then compute
className for the status badge using clsx(condition && className, ...) based on
submission.status === 'shortlisted' / 'disqualified' (and a default), and do the
same for the second occurrence; update the JSX where className is currently
built (the status badge in the SubmissionDetailModal component that references
submission.status) to use the new clsx expression for clearer conditional
composition.
In `@components/hackathons/submissions/SubmissionForm.tsx`:
- Line 1561: Both CTA buttons in SubmissionForm.tsx are duplicating the same
class string and use a hard-coded hover hex; extract a shared class constant
(e.g., ctaButtonClass) and replace both usages (the className at the occurrences
around the current strings) with that constant, and swap the literal hover color
(`#8fd93f`) for a semantic theme token (e.g., a Tailwind semantic class or CSS
variable like hover:bg-cta-hover or hover:bg-[var(--color-cta-hover)]) so
styling is centralized and consistent with the theme system.
In `@components/hackathons/submissions/submissionTab.tsx`:
- Around line 289-299: Replace the hard-coded hover color on the submission
Button: update the className on the Button (the component rendering Plus and
using router.push with hackathonSlug, and props isDeadlinePassed) to remove
hover:bg-[`#8fd93f`] and use the design-system primary hover token instead (for
example hover:bg-primary/90 or the project’s defined primary-hover token) so the
hover state matches bg-primary and the design system.
- Around line 128-146: The handler handleConfirmDelete currently calls
window.location.reload() after removeSubmission; remove that reload and instead
update the React Query cache (or trigger a refetch) so the UI reflects deletion
without a full page reload: after await removeSubmission(submissionToDelete)
call setSubmissionToDelete(null), toast.success(...), then use your query client
(or the useSubmission hook's refetch/invalidate method) to invalidate or refetch
the submissions query key so the list refreshes; keep the existing
error/reporting/finally logic and ensure setIsDeleting(false) remains in
finally.
- Line 37: Remove the unused import useExpandableScreen from the top of
submissionTab.tsx; locate the import statement "import { useExpandableScreen }
from '@/components/ui/expandable-screen';" and delete it so the file no longer
imports a hook that isn't referenced anywhere in the SubmissionTab component.
In `@components/hackathons/team-formation/InviteUserModal.tsx`:
- Around line 94-108: In InviteUserModal replace the inline style and arbitrary
hover color on the Button (the element using onClick={handleInvite},
disabled={isInviting}) with semantic Tailwind color tokens (e.g., use classes
like bg-primary text-primary-foreground and a hover state such as
hover:bg-primary/90 or hover:bg-primary-dark) instead of hard-coded hex and
hover:bg-[`#8ae63a`]; if your Tailwind theme does not define primary and
primary-foreground, add those mappings to the theme so they resolve to the
original colors (`#a7f950` and `#000000`).
In `@components/hackathons/team-formation/MyInvitationsList.tsx`:
- Around line 40-41: The code uses unsafe any casts for user.profile and user to
read username and userId; define a proper interface (e.g., UserProfile {
username?: string } and extend or refine the User type to include profile?:
UserProfile and userId?: string) and update the usages in MyInvitationsList.tsx
to use typed properties (replace (user.profile as any)?.username and (user as
any).userId with user.profile?.username and user.userId or user.id) so
TypeScript can check these accesses; update the function/component prop or hook
that supplies user (where User is declared) to use the new types.
- Around line 198-212: Replace the ad-hoc any usage by declaring proper types
for the invitation payload and parties (e.g., define interfaces InvitationParty
{ name?: string; username?: string; image?: string; user?: { profile?: { name?:
string; username?: string; image?: string } } } and TeamInvitation { id: string;
status: string; message?: string; createdAt: string; expiresAt: string; inviter:
InvitationParty; invitee: InvitationParty }), then change the map to operate on
invitations typed as TeamInvitation[] and type the map parameter as (invite:
TeamInvitation) so you can remove all (as any) assertions; also update local
variables (otherParty, otherPartyName, otherPartyUsername, otherPartyAvatar) to
use the typed InvitationParty properties directly to restore type safety.
In `@components/hackathons/team-formation/TeamInvitationsList.tsx`:
- Around line 77-81: Replace the template-literal ternary used in the className
prop inside TeamInvitationsList (the JSX where className={`h-4 px-1 py-0
text-[10px] ${ invite.status === 'pending' ? 'border-primary/30 text-primary' :
'border-zinc-700 text-zinc-500' }`}) with clsx: import clsx from 'clsx' at the
top of the file, build a single clsx expression that always includes 'h-4 px-1
py-0 text-[10px]' and conditionally adds 'border-primary/30 text-primary' when
invite.status === 'pending' otherwise 'border-zinc-700 text-zinc-500'; ensure
the expression references the same invite.status check and replace the existing
template literal with that clsx call.
In `@components/landing-page/blog/BlogCard.tsx`:
- Around line 94-96: The span in BlogCard.tsx (the Continue Reading label) uses
a redundant hover class: 'text-primary group-hover/btn:text-primary' on the span
element; either remove the redundant 'group-hover/btn:text-primary' if no hover
color change is desired, or change the base color class (e.g., replace
'text-primary' with a muted/base color like 'text-muted' or 'text-secondary')
and keep 'group-hover/btn:text-primary' to create a visible hover
transition—update the class list on the span accordingly in the BlogCard
component.
In `@components/landing-page/blog/BlogPostDetails.tsx`:
- Around line 180-269: SVG icons inside the share buttons (the button elements
with onClick handlers handleShare('twitter'|'discord'|'send'|'link') and the
conditional Check render) use a hardcoded stroke "#99FF2D"; update each SVG to
use stroke="currentColor" (or a CSS variable) and control the visible color via
the button's text color utilities (the existing className on these buttons) so
hover/focus theme tokens (e.g., primary) automatically apply; ensure both the
default and focused/hover states set the appropriate text color classes so the
icons follow the design token color changes.
In `@components/landing-page/blog/StreamingBlogGrid.tsx`:
- Around line 292-298: The "View More" button in StreamingBlogGrid.tsx (the
button with onClick={handleShowMore}) still uses hard-coded colors
(border-[`#2B2B2B`], bg-[`#1A1A1A`], hover:bg-[`#2A2A2A`]); replace those hex classes
with the project’s semantic token utility classes (for example use the token
equivalents for border, background and hover background such as
border-<semantic-token>, bg-<semantic-token>, hover:bg-<semantic-token>) so the
component matches the earlier focus:ring-primary change and the token migration
across the codebase.
- Around line 125-126: The background and hover color hex values in the class
strings inside StreamingBlogGrid (the ternary that currently yields
'border-primary/30 text-primary bg-[`#0F1A0B`]' and the alternate 'border-white/24
text-white hover:bg-[`#2A2A2A`]') need to use semantic tokens instead of
hard-coded hexes; replace bg-[`#0F1A0B`] with the appropriate design token (e.g.,
bg-primary/10 or bg-surface-strong) and replace hover:bg-[`#2A2A2A`] with the
corresponding token (e.g., hover:bg-primary/20 or hover:bg-surface-hover), and
apply the same replacements for the other occurrence noted (same pattern around
the second occurrence) so all background and hover colors consistently use
semantic tokens like bg-*/hover:bg-* rather than hex codes.
In `@components/landing-page/navbar.tsx`:
- Line 340: Two Link elements in the navbar share the same long CTA class
string; extract that shared class list into a single constant (e.g.,
CTA_BUTTON_CLASSES) and replace the repeated className literals with that
constant in the Navbar component (the locations currently using the 'bg-primary
shadow-primary/20 hover:bg-primary/90 inline-flex h-9 min-h-[44px] items-center
justify-center rounded-[10px] px-4 text-sm font-medium text-black shadow-sm
transition-colors' string). This centralizes the style, prevents drift, and
keeps both CTA links synchronized.
In `@components/profile/update/SecurityTab.tsx`:
- Line 154: Replace the inline ternary in the JSX className with the cn()
utility for consistency: in the SecurityTab component locate the element using
className={`h-2 w-2 rounded-full ${user.twoFactorEnabled ? 'bg-primary' :
'bg-[`#B5B5B5`]'}`} and change it to use cn('h-2 w-2 rounded-full',
user.twoFactorEnabled ? 'bg-primary' : 'bg-[`#B5B5B5`]') (import cn from lib/utils
if not already imported) so conditional classes are handled via the cn helper.
In `@components/profile/update/TwoFactorTab.tsx`:
- Line 327: The step indicator divs in TwoFactorTab.tsx use a hard-coded color
class "text-[`#141414`]" which bypasses theme tokens; replace that class in both
occurrences (the step indicator elements around the 'bg-primary flex h-8 w-8
...' blocks at the existing occurrences) with the project’s semantic text token
(e.g., the existing token used elsewhere such as "text-foreground" or
"text-primary-foreground") so the indicator follows theme changes; update both
places (the one near the current step marker and the other at the alternate
step) to use the same semantic token.
In `@components/project-details/project-about.tsx`:
- Around line 56-62: The AvatarFallback element uses a hard-coded hex color in
its className ("to-[`#8fd93f`]") which breaks the design-token consolidation;
update the gradient to use a design token (e.g., replace "to-[`#8fd93f`]" with a
secondary token like "to-secondary" or a defined token name in your theme) or
add the intended secondary token to the theme and reference it in
AvatarFallback's className to keep gradients consistent with the existing
"from-primary" token.
- Around line 84-99: The map over project.links in the ProjectAbout component
uses the array index as the React key; change the key on the anchor from index
to a stable unique identifier (use link.url since each link has a unique url) to
avoid reordering/filtering bugs — update the key prop inside the
project.links.map callback where getIcon(link.icon) and link.url are used.
In `@features/projects/components/CreateProjectModal/SuccessScreen.tsx`:
- Around line 9-15: Remove the dead commented-out Image JSX in the SuccessScreen
component: delete the commented block containing the Image tag
(src='/Success@4x.png' ... className='max-h-[150px] max-w-[150px]') so the
CreateProjectModal/SuccessScreen.tsx file contains no commented-out JSX; if the
image might be needed later, rely on VCS history rather than keeping it in the
SuccessScreen component.
In `@features/projects/components/MilestoneSubmissionModal.tsx`:
- Around line 312-332: The focus border color is hard-coded as '#A7F950' in the
inline style for the input; change this to use the project's primary design
token instead: remove the inline borderColor conditional (borderColor:
focusedInput === index ? '#A7F950' : '#2B2B2B') and instead toggle a class that
applies the primary token (e.g., add conditional className when focusedInput ===
index such as 'focus:border-primary' or a utility class like 'border-primary'
when focused), or set the inline value to reference a CSS variable for primary
(e.g., var(--color-primary)) if dynamic inline styling is required; update the
component around the input (referencing focusedInput, index, and link) so the
focused state uses the semantic token rather than the hard-coded hex.
- Around line 357-362: Replace the hard-coded hover color in the className
string in MilestoneSubmissionModal (the JSX block that builds the submit button
using files and loading) with a semantic token variant; specifically, locate the
className expression in MilestoneSubmissionModal.tsx (the conditional branch
that uses 'border-primary bg-primary text-black hover:bg-[`#8BE03A`]') and change
the hover utility to a design token (for example use 'hover:bg-primary/90' or
your project's dedicated hover token such as 'hover:bg-primary-dark') so the
hover state uses the same semantic token system as the base bg-primary.
In `@features/projects/components/MilestoneSubmissionSuccess.tsx`:
- Around line 35-50: The hover styles in MilestoneSubmissionSuccess are using
hardcoded hex values in the Link and BoundlessButton className (the occurrences
of hover:text-[`#8BE03A`] and hover:bg-[`#8BE03A`]); update these to use the theme
semantic tokens instead (for example replace with a hover:text-primary variant
or a named token such as hover:text-primary-hover / hover:bg-primary-hover, or
use Tailwind opacity modifiers like hover:text-primary/90 or
hover:bg-primary/90) so hover states stay in sync with the primary color in your
Tailwind theme.
In `@hooks/use-require-auth-for-action.ts`:
- Around line 20-22: Add a brief JSDoc to the withAuth hook/function
(use-require-auth-for-action.ts) explaining that the returned async action is a
silent no-op while isLoading is true and that callers should use the exported
isLoading flag to disable UI (e.g., buttons) or show a spinner; reference the
withAuth function and the returned wrapper (the async (...args: Args) => { if
(isLoading) return; }) so consumers understand that the wrapper will not execute
during loading and must consult isLoading to avoid confusing silent behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bd206652-c4a1-4fff-a0e9-813f08d0252d
📒 Files selected for processing (83)
app/(landing)/blog/[slug]/not-found.tsxapp/(landing)/code-of-conduct/CodeOfConductContent.tsxapp/(landing)/disclaimer/DisclaimerContent.tsxapp/(landing)/hackathons/[slug]/components/header/ActionButtons.tsxapp/(landing)/hackathons/[slug]/components/header/TitleAndInfo.tsxapp/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/resources/ResourceCard.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/resources/header.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/teams/MyTeamView.tsxapp/(landing)/hackathons/[slug]/components/tabs/contents/teams/TeamCard.tsxapp/(landing)/newsletter/confirm/error/page.tsxapp/(landing)/newsletter/confirmed/page.tsxapp/(landing)/newsletter/unsubscribe/error/page.tsxapp/(landing)/newsletter/unsubscribed/page.tsxapp/(landing)/org/[slug]/org-profile-client.tsxapp/(landing)/privacy/PrivacyContent.tsxapp/(landing)/privacy/TermsContent.tsxapp/me/crowdfunding/[slug]/edit/components/TeamSection.tsxapp/me/hackathons/submissions/page.tsxapp/me/hackathons/submissions/submission-components.tsxapp/me/settings/SettingsContent.tsxcomponents/ComingSoon.tsxcomponents/ProjectCard.tsxcomponents/auth/AuthModalProvider.tsxcomponents/auth/LoginForm.tsxcomponents/auth/LoginWrapper.tsxcomponents/auth/SignupForm.tsxcomponents/auth/TwoFactorVerify.tsxcomponents/common/SharePopover.tsxcomponents/hackathons/HackathonsPageHero.tsxcomponents/hackathons/discussion/comment.tsxcomponents/hackathons/hackathonBanner.tsxcomponents/hackathons/hackathonNavTabs.tsxcomponents/hackathons/hackathonStickyCard.tsxcomponents/hackathons/overview/RegisterHackathonModal.tsxcomponents/hackathons/overview/hackathonPrizes.tsxcomponents/hackathons/overview/hackathonTimeline.tsxcomponents/hackathons/overview/joinHackathon.tsxcomponents/hackathons/participants/hackathonParticipant.tsxcomponents/hackathons/participants/participantAvatar.tsxcomponents/hackathons/participants/participantFilter.tsxcomponents/hackathons/participants/profileCard.tsxcomponents/hackathons/submissions/SubmissionDetailModal.tsxcomponents/hackathons/submissions/SubmissionForm.tsxcomponents/hackathons/submissions/submissionCard.tsxcomponents/hackathons/submissions/submissionCard2.tsxcomponents/hackathons/submissions/submissionTab.tsxcomponents/hackathons/team-formation/InviteUserModal.tsxcomponents/hackathons/team-formation/MyInvitationsList.tsxcomponents/hackathons/team-formation/TeamDetailsSheet.tsxcomponents/hackathons/team-formation/TeamFormationTab.tsxcomponents/hackathons/team-formation/TeamInvitationsList.tsxcomponents/hackathons/team-formation/TeamRecruitmentPostCard.tsxcomponents/landing-page/Explore.tsxcomponents/landing-page/Hero2.tsxcomponents/landing-page/blog/BlogCard.tsxcomponents/landing-page/blog/BlogGrid.tsxcomponents/landing-page/blog/BlogPostDetails.tsxcomponents/landing-page/blog/BlogSectionClient.tsxcomponents/landing-page/blog/StreamingBlogGrid.tsxcomponents/landing-page/navbar.tsxcomponents/layout/sidebar.tsxcomponents/messages/TetherMessage.tsxcomponents/modals/Success.tsxcomponents/nav-user.tsxcomponents/overview/Newsletter.tsxcomponents/profile/update/SecurityTab.tsxcomponents/profile/update/TwoFactorTab.tsxcomponents/project-details/project-about.tsxcomponents/project-details/project-details.tsxcomponents/project-details/project-layout.tsxcomponents/project-details/project-sidebar/ProjectSidebarActions.tsxcomponents/project-details/project-sidebar/ProjectSidebarHeader.tsxcomponents/testimonials/TestimonialCard.tsxcomponents/waitlist/WaitlistForm.tsxfeatures/projects/components/CreateProjectModal/Contact.tsxfeatures/projects/components/CreateProjectModal/SuccessScreen.tsxfeatures/projects/components/MilestoneSubmissionModal.tsxfeatures/projects/components/MilestoneSubmissionSuccess.tsxfeatures/projects/components/ProjectPageHero.tsxhooks/hackathon/use-hackathon-queries.tshooks/hackathon/use-team-posts.tshooks/use-require-auth-for-action.ts
✅ Files skipped from review due to trivial changes (4)
- components/landing-page/blog/BlogSectionClient.tsx
- features/projects/components/CreateProjectModal/Contact.tsx
- app/(landing)/newsletter/confirmed/page.tsx
- components/auth/LoginForm.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
- app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx
- app/(landing)/hackathons/[slug]/components/tabs/contents/resources/header.tsx
- components/hackathons/participants/profileCard.tsx
- components/auth/AuthModalProvider.tsx
- app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx
- components/common/SharePopover.tsx
Summary by CodeRabbit
New Features
Styling Updates