Skip to content

Feat/new hackathon layout#486

Open
0xdevcollins wants to merge 2 commits intomainfrom
feat/new-hackathon-layout
Open

Feat/new hackathon layout#486
0xdevcollins wants to merge 2 commits intomainfrom
feat/new-hackathon-layout

Conversation

@0xdevcollins
Copy link
Collaborator

@0xdevcollins 0xdevcollins commented Mar 13, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Redesigned hackathon detail pages with enhanced layouts, banners, and dynamic sidebars
    • Tab-based interface to explore hackathon information including announcements, participants, submissions, winners, and resources
    • Team formation and discovery system with search, filtering, and role management
    • Improved submission exploration with filtering and pagination
    • Submission-based winners display with podium rankings
    • Resource library for hackathon participants
    • Share functionality for hackathons and content
  • Improvements

    • Enhanced data synchronization and loading performance
    • Better mobile responsiveness across all pages

…sidebar, and tabbed content sections including teams, announcements, winners, participants, and resources.
@vercel
Copy link

vercel bot commented Mar 13, 2026

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

Project Deployment Actions Updated (UTC)
boundless-kd16 Ready Ready Preview, 💬 1 unresolved Mar 13, 2026 1:16pm

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

This PR redesigns the hackathon detail page architecture by migrating from local component state to React Query-based data fetching with server-side data seeding. A new composable component hierarchy replaces HackathonPageClient with Banner, Header, ActionButtons, Sidebar, and HackathonTabs components, enabling comprehensive team/announcement/submission/participant management with interactive features across multiple tabbed sections.

Changes

Cohort / File(s) Summary
Page Structure & Client Initialization
app/(landing)/hackathons/[slug]/page.tsx, app/(landing)/hackathons/[slug]/HackathonPageClient.tsx, app/(landing)/hackathons/[slug]/layout.tsx
Refactored main page to accept server-provided initialHackathon, wrapping page content with HackathonDataProvider seeding initial data via React Query. HackathonPageClient now accepts initialHackathon prop and uses it to eliminate local initialization logic. Layout safeguards null slug with empty string default.
Header Components
app/(landing)/hackathons/[slug]/components/header/index.tsx, app/(landing)/hackathons/[slug]/components/header/Logo.tsx, app/(landing)/hackathons/[slug]/components/header/TitleAndInfo.tsx, app/(landing)/hackathons/[slug]/components/header/ActionButtons.tsx
New header composition with Logo (circular image), TitleAndInfo (title, status badge, participant/venue info, avatar group), and ActionButtons (join/leave, team management, share popover with toast notifications).
Banner & UI Primitives
app/(landing)/hackathons/[slug]/components/Banner.tsx, components/ui/avatar.tsx, components/ui/tabs.tsx, components/ui/separator.tsx, components/ui/popover-cult.tsx
New Banner component with responsive image; extended Avatar with size variants and new AvatarBadge/AvatarGroup/AvatarGroupCount exports; Tabs refactored with variant system; new comprehensive Popover UI system (PopoverRoot, PopoverTrigger, PopoverContent, PopoverForm, PopoverButton, etc.).
Sidebar Components
app/(landing)/hackathons/[slug]/components/sidebar/index.tsx, app/(landing)/hackathons/[slug]/components/sidebar/PoolAndAction.tsx, app/(landing)/hackathons/[slug]/components/sidebar/FollowAndMessage.tsx
Sidebar composition with PoolAndAction (prize pool display, countdown, live status, submission button) and FollowAndMessage (organizer info, follow toggle, messaging action).
Tab Content Components
app/(landing)/hackathons/[slug]/components/tabs/index.tsx, app/(landing)/hackathons/[slug]/components/tabs/Lists.tsx, app/(landing)/hackathons/[slug]/components/tabs/contents/*
Dynamic tab management with Lists component for tab headers; content sections for Overview (timeline, prizes), Participants (filters, grid), Submissions (search/filter, pagination), Announcements (filtering, cards), Discussions, Winners (podium/honorable mentions), FindTeam (search, team cards, modals), and Resources (category tabs).
Announcement Features
app/(landing)/hackathons/[slug]/announcements/[announcementId]/page.tsx, app/(landing)/hackathons/[slug]/components/tabs/contents/announcements/*
Updated announcement page to navigate back to hackathon tab instead of closing window; AnnouncementsIndex component with filtering (Technical/Logistics/Socials), skeleton loading, and AnnouncementCard grid rendering.
Team Management Components
app/(landing)/hackathons/[slug]/components/tabs/contents/teams/*, components/hackathons/team-formation/*
MyTeamView (leader/member roles, invite builders, transfer leadership modals), TeamCard (team preview with roles and members), plus updated ContactTeamModal and multi-step CreateTeamPostModal (IDENTITY→ROLES→CONTACT flow).
Avatar & Share Utilities
components/avatars/BasicAvatar.tsx, components/avatars/GroupAvatar.tsx, components/common/SharePopover.tsx, components/common/share.tsx
New BasicAvatar (name/username display with avatar), GroupAvatar (multi-member list with +N indicator), and SharePopover (copy/Twitter/LinkedIn/email share actions).
Hackathon UI Components
components/hackathons/ExtendedBadge.tsx, components/hackathons/submissions/SubmissionForm.tsx, components/hackathons/team-formation/TeamDetailsSheet.tsx, components/hackathons/team-formation/TeamFormationTab.tsx, components/hackathons/team-formation/TeamRecruitmentPostCard.tsx
New ExtendedBadge for deadline extensions; SubmissionForm updated with safe leader/member access (myTeam?.leader?.id); team formation components refactored to support nested leader objects and flexible member typing.
React Query Hooks
hooks/hackathon/use-hackathon-queries.ts, hooks/hackathon/use-hackathon-submissions.ts, hooks/hackathon/use-team-posts.ts, hooks/use-follow.ts
Comprehensive query hooks library for hackathon data (useHackathon, useHackathonParticipants, useSubmissions, etc.), mutations (join/leave, invite, transfer leadership), and utilities (refresh, query keys). Replaced effect-based data fetching with React Query. Updated follow status parsing with fallback handling.
API Layer Updates
lib/api/hackathon.ts, lib/api/hackathons.ts, lib/api/hackathons/core.ts, lib/api/hackathons/teams.ts
Fixed hackathon endpoint from /hackathons/s/{slug} to /hackathons/{slug}; updated Hackathon type with submissionDeadlineOriginal and adjusted deadline fields; refactored team APIs from TeamRecruitmentPost to Team interface with new team-centric endpoints (getTeams, createTeam, inviteToTeam, transferLeadership, toggleRoleHired); changed getExploreSubmissions return type to ExploreSubmissionsApiResponse.
Provider & State Management
lib/providers/hackathonProvider.tsx, app/providers.tsx
Rewrote HackathonDataProvider to be slim, server-seeded container using React Query hooks (useHackathon, useHackathonSubmissions, etc.); removed heavy local state in favor of hook-based data. Added QueryClientProvider with configured defaults (staleTime: 1min, gcTime: 5min, retry: 1, no refetchOnWindowFocus).
Submit Page Updates
app/(landing)/hackathons/[slug]/submit/page.tsx
Replaced manual state management with direct useHackathon hook integration; removed effect-based initialization in favor of hook-provided data for currentHackathon and hackathonLoading.
Type Definitions
types/follow.ts, types/hackathon/participant.ts
Updated ApiFollowStatusResponse with nested data structure and fallback fields; added optional skills array to Participant type.

Sequence Diagram

sequenceDiagram
    participant Server as Server
    participant Page as HackathonPage
    participant Provider as HackathonDataProvider
    participant ReactQuery as React Query
    participant Components as UI Components

    Server->>Page: GET /hackathons/[slug] + initialHackathon
    Page->>Provider: Render with initialData={hackathon}
    Provider->>ReactQuery: setQueryData(hackathonKeys, initialData)
    Provider->>Components: Provide currentHackathon via context
    Components->>ReactQuery: useHackathon(slug) / useHackathonAnnouncements(slug)
    ReactQuery-->>Components: Return seeded/cached data immediately
    Components->>Components: Render with currentHackathon
    Components->>ReactQuery: User action (join/invite/follow)
    ReactQuery->>Server: POST mutation
    Server-->>ReactQuery: Response
    ReactQuery->>ReactQuery: Invalidate related query keys
    ReactQuery-->>Components: Updated data
    Components->>Components: Re-render with fresh state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Add hackathon detail page redesign (Figma link) #466 — Implements the comprehensive hackathon detail page redesign architecture with new component hierarchy and server-side data seeding as proposed in this design PR.
  • Hackathon analytics #378 — Related refactoring of HackathonDataProvider to integrate winners and explore submissions data alongside the main PR's React Query migration.
  • UI fixes #454 — Overlaps with HackathonPageClient initialization changes, removing isInitializing flow in favor of server-seeded initialHackathon prop.

Suggested reviewers

  • Benjtalkshow

Poem

🐰 With banners high and tabs aligned,
The hackathon page's redesigned!
React Query seeds the data flow,
Server-side, the hackathons grow,
Components compose in harmony divine ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/new hackathon layout' accurately describes the primary change—a comprehensive redesign of the hackathon page layout with new components (Banner, Header, Sidebar, HackathonTabs) and restructured data flow.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/new-hackathon-layout
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 | 🟡 Minor

Use unknown instead of any in catch clause.

As per coding guidelines, avoid using any type. For error handling, use unknown and 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 | 🟠 Major

Don't drop maxSize and skills at submit time.

The form collects both fields, but updatePayload omits maxSize and both payloads flatten lookingFor down 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 | 🟠 Major

CTA 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

statusFilter is 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 | 🟠 Major

Add 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 | 🟠 Major

Harden window.open against reverse-tabnabbing.

Opening links with _blank should include noopener,noreferrer as the third parameter to prevent the opened page from accessing window.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 | 🟠 Major

Replace any and implement the onViewClick handler before merge.

Line 157 uses any instead of the proper ExploreSubmissionsResponse type. Line 161 has a TODO placeholder with an empty function instead of handling the click action—it should call onViewClick(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 | 🟠 Major

Use the Participant type instead of any in participant membership check.

Line 83 uses (p: any) which violates the coding guideline. Replace with the proper Participant type from types/hackathon/participant.ts. The Participant interface has the required userId field 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 | 🟠 Major

Give PopoverTextarea a real accessible label.

PopoverLabel is hidden from assistive tech and isn't associated with the textarea, so screen readers get an unnamed field. Please make the label programmatic or accept aria-label/aria-labelledby on 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 | 🟠 Major

Replace any types in legacy compatibility aliases with proper type definitions.

The getTeamPosts() and updateTeamPost() compatibility aliases use any types, 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 | 🟠 Major

Protect the opener when launching third-party share windows.

Both handleTwitterShare and handleLinkedinShare use window.open(..., '_blank') without noopener,noreferrer, leaving the current tab exposed to reverse-tabnabbing attacks. Third-party pages opened via _blank can access window.opener to 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 | 🟠 Major

Don't ship the new team API surface with exported any types.

The contactInfo, hackathon, invitee, and inviter fields are part of the public API contract. Replace them with proper types:

  • Line 55: contactInfo: anycontactInfo: string
  • Lines 86-88: Use Hackathon (already imported), User for invitee/inviter

Additionally, line 122's options?: any parameter should use the existing GetTeamOptions type 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 | 🟠 Major

Keep myTeam in sync when these mutations succeed.

After switching this hook over to Team, these responses represent the same entity returned by getMyTeam, but only posts and myPosts are updated. Any consumer reading myTeam will 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 | 🟠 Major

The skill filter is currently a non-functional placeholder.

Changing skillFilter only 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 | 🟠 Major

The provider drops loading/error from two of the queries it exposes.

exploreSubmissions and winners can still be fetching or fail while context reports loading = false and error = null, which makes those sections indistinguishable from genuine empty states for consumers. Fold their isLoading/error values 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 page swaps the query to page N, but the component only renders participantsData.participants from that single page. This also needs to reset page back 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 | 🟠 Major

Include params in the submission query cache key to prevent stale data when filters change.

The useHackathonSubmissions hook accepts page, limit, status, and sort parameters 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) and useHackathonTeams (line 244), which include their params in the query key.

Suggested fix

Update hackathonKeys.submissions to 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 | 🟠 Major

Use one identifier for the winners cache key.

The useHackathonWinners query is keyed by currentHackathon.id, but useRefreshHackathon invalidates via hackathonKeys.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 hackathonSlug consistently 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 | 🟠 Major

Fix cache invalidation for team invitation mutations to use actual teamId.

Both useInviteToTeam and useInvitationActions.cancelAction invalidate the team invitations cache with an empty teamId (hackathonKeys.teamInvitations(slug, '')), which fails to match queries created by useTeamInvitations(slug, teamId, ...). This leaves sent/cancelled invitations stale in the cache.

  • useInviteToTeam receives teamId in params but doesn't use it in invalidation
  • useInvitationActions.cancelAction needs its mutation signature updated to accept teamId so the caller can provide it for proper cache invalidation

Update both mutations to pass the actual teamId to hackathonKeys.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 | 🟠 Major

Use hackathon?.isParticipant instead 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. The useHackathon hook already provides the viewer-scoped isParticipant flag—use it directly. Remove the useHackathonParticipants import and the any cast on line 43, which masks the properly-typed userId field 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 | 🟠 Major

Replace any types with proper typed parameters in cache keys.

The participants and teams cache key builders use any type for optional params, violating the coding guidelines (do not use any; always search for proper Trustless Work entity types). Additionally, useHackathonSubmissions accepts params but the queryKey only includes slug, so different page/filter/sort combinations share the same cache entry, causing stale data issues.

Use the already-imported GetTeamOptions type 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 useHackathonSubmissions to use hackathonKeys.submissionsList(slug, params) instead of hackathonKeys.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 | 🟠 Major

Toast message shows stale state.

isFollowing reflects the state before toggleFollow() completes. The toast will show the opposite of what actually happened (e.g., shows "Unfollowed" when user just followed).

Also, avoid using any type 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 | 🟠 Major

Don't invalidate ?tab=announcements before the announcements query finishes.

On a direct visit to ?tab=announcements, the first render sees announcements = [], drops that tab from hackathonTabs, and this effect immediately rewrites the URL to overview. 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 | 🟠 Major

Hold the tab redirect until announcements have resolved.

This version has the same race: announcements starts as [], so ?tab=announcements is treated as invalid and replaced with overview before 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 | 🟠 Major

Split lookup failures from invitation failures.

getUserProfileByUsername(...) and inviteMutation.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 | 🟠 Major

Make the category and role controls actually filter the list.

categoryFilter and roleFilter only change the dropdown labels. useHackathonTeams(...) still fetches by page/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 | 🟠 Major

Surface save failures in the modal.

The catch block only logs to the console. If createPost or updatePost rejects, 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 | 🟠 Major

Replace any and @ts-ignore with proper Trustless Work entity types.

The HackathonResourceItem and ResourceCardProps types are available and should be used throughout this file. Using any and @ts-ignore bypasses 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 any parameter type

Also remove the @ts-ignore comment and as any assertion on lines 148–150; the isComingSoon property should extend ResourceCardProps through 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 | 🟡 Minor

Remove stray s from className.

The className contains s which 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 | 🟡 Minor

Avoid using any type.

As per coding guidelines, avoid using any type. The leader variable 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 DisplayLeader interface that covers both member object shapes and the post.leader shape.

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 | 🟡 Minor

Add type="button" to prevent accidental form submission on reusable button primitives.

Buttons without an explicit type attribute default to type="submit". If PopoverTrigger or PopoverButton are 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 | 🟡 Minor

Remove the as any escape 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. The as any coercion 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 | 🟡 Minor

Add null safety for _count access.

hackathon._count.participants may throw if _count is 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 | 🟡 Minor

Badge active-state styling won't apply.

The badge uses group-data-[state=active]: classes, but the parent TabsTrigger doesn't have the group class. The active state styles won't be applied to the badge.

🐛 Suggested fix

Add group class 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 | 🟡 Minor

Avoid href="#" for better accessibility.

Using href="#" causes the page to scroll to top and isn't ideal for accessibility. If actionHref is not provided for a non-coming-soon card, consider either making actionHref required or using a button element instead.

🛡️ Suggested approaches

Option 1: Make actionHref required when isComingSoon is 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 | 🟡 Minor

Add guard to prevent API calls when org.id is missing.

The component's early return only checks !org, not !org?.id. If org exists but lacks an id, an empty string is passed to useFollow. 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?.id to the guard at line 61, or add validation in the follow/unfollow button handler to check org.id before 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 | 🟡 Minor

Remove the debug logging.

These console.log calls 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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant