Skip to content

Submission page#430

Merged
Benjtalkshow merged 15 commits intoboundlessfi:mainfrom
Michaelkingsdev:submission-page
Mar 3, 2026
Merged

Submission page#430
Benjtalkshow merged 15 commits intoboundlessfi:mainfrom
Michaelkingsdev:submission-page

Conversation

@Michaelkingsdev
Copy link
Contributor

@Michaelkingsdev Michaelkingsdev commented Mar 2, 2026

Closes #395

feat: Implement Unified Hackathon Submissions Dashboard

Summary

This PR replaces the /coming-soon stub at /me/hackathons/submissions with a fully functional submissions dashboard.

The page provides users with complete visibility into the lifecycle of their hackathon submissions. All data is sourced directly from the existing user.profile session, introducing zero new API requests.


Key Features

Submissions Table

  • Responsive and sortable table view
  • Sort by: Project Name, Hackathon, Status, Submitted Date, Rank
  • Keyboard accessible rows with subtle entrance animations
  • Click row to open submission details
  • Middle-click / Cmd/Ctrl + Click opens submission in new tab

Status Badges

  • Reusable StatusBadge component
  • Color-coded by status (Ranked, Under Review, Submitted, Disqualified, Draft)
  • Matches existing hackathon UI styling

Submission Details Drawer

  • Bottom sheet (size="xl") opens on row click
  • Displays:
    • Hackathon banner
    • Project logo
    • Rank
    • Important dates
    • Description
    • Demo & custom links
  • Includes direct "View Page" CTA

Sidebar Integration

  • Displays total submission count in AppSidebar
  • Count derived from session and safely deduplicated
  • Matches existing navigation badge patterns

Empty State

  • Reusable EmptyState component
  • Clear guidance when no submissions exist
  • CTA directs users to /hackathons

Technical Notes

  • Zero API architecture: Data sourced exclusively from useAuthStatus()
  • Merges:
    • user.profile.user.hackathonSubmissionsAsParticipant
    • user.profile.hackathonSubmissionsAsParticipant
  • Extracted reusable components into submission-components.tsx
  • Prevented layout shifts using CSS opacity transitions instead of conditional rendering

Video Proof

Summary by CodeRabbit

  • New Features

    • Full Submissions page: client-side sortable multi-criteria table, row selection, detail sheet, keyboard/mouse accessibility, loading and empty states, and animated transitions.
  • UI

    • Status badges, summary strip, per-row metadata, interactive row behaviors, demo/detail view, and submission count badge added to sidebar navigation.
  • Documentation

    • Project detail redesign doc expanded with loading, empty, and error states, per-tab guidance, and deliverables.
  • Style

    • Minor class ordering tweak in profile earnings header.

@vercel
Copy link

vercel bot commented Mar 2, 2026

@Michaelkingsdev is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces a placeholder with a client-side SubmissionsPage and supporting components that normalize and deduplicate session-provided hackathon submissions, provide a sortable table UI, selection with a BoundlessSheet detail drawer, status badges, loading/empty states, and related types/UI primitives.

Changes

Cohort / File(s) Summary
Submissions UI
app/me/hackathons/submissions/page.tsx, app/me/hackathons/submissions/submission-components.tsx
Adds SubmissionsPage client component plus UI primitives and types (SubmissionRow, SortField, SortDir). Implements session-based normalization/deduplication, multi-criteria sorting with toggleable direction, SortIcon, StatusBadge, interactive TableRow, SubmissionsSheetContent, loading/empty states, and accessibility behaviors.
Layout & Navigation
app/me/layout.tsx, components/app-sidebar.tsx
MeLayout now computes submissionsCount and AppSidebar/getNavigationData accept and render a submissions badge; updates counts prop shape across layout/sidebar boundary ({ participating?: number; submissions?: number }).
Minor UI / Docs
components/profile/PublicEarningsTab.tsx, docs/project-detail-redesign.md
Tiny Tailwind class ordering tweak in PublicEarningsTab. Large documentation rewrite for Project Detail redesign (loading/empty/error strategies, per-tab guidance); purely docs changes.

Sequence Diagram

sequenceDiagram
    participant User
    participant SubmissionsPage
    participant Session (profile)
    participant Table
    participant BoundlessSheet

    User->>SubmissionsPage: Navigate to /me/hackathons/submissions
    SubmissionsPage->>Session (profile): Read hackathon submissions from session.user.profile
    Session (profile)-->>SubmissionsPage: Return submissions array
    SubmissionsPage->>SubmissionsPage: Normalize & dedupe -> SubmissionRow[]
    SubmissionsPage->>Table: Render rows with sortField/sortDir
    Table-->>User: Display sortable rows

    User->>Table: Click column header
    Table->>SubmissionsPage: Update sort state
    SubmissionsPage->>Table: Re-render sorted rows

    User->>Table: Click submission row
    Table->>SubmissionsPage: Set selected submission
    SubmissionsPage->>BoundlessSheet: Open sheet with submission
    BoundlessSheet-->>User: Show detailed submission metadata
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • 0xdevcollins
  • Benjtalkshow

Poem

🐰 I hopped to the submissions table bright,
Badges glowing soft in emerald light,
Rows that sort and drawers that slide,
Details tucked safe on the side,
A rabbit cheers — all submissions in sight!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Minor out-of-scope change: PublicEarningsTab.tsx className reordering is unrelated to the submissions page implementation, though the change is purely stylistic with no functional impact. Remove the unrelated PublicEarningsTab.tsx className reordering change (line modification in components/profile/PublicEarningsTab.tsx) to keep the PR focused on submission page implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Submission page' is vague and generic, lacking specificity about the main change such as implementation details, dashboard features, or sortable table functionality. Consider using a more descriptive title such as 'Implement submissions dashboard with sortable table and detail sheet' or 'Add Submissions page with sortable table at /me/hackathons/submissions'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request comprehensively implements all UI and technical objectives from issue #395: sortable submissions table with multi-field sorting, color-coded status badges with glowing effects, BoundlessSheet integration for submission details, sidebar integration with submission count, zero new API calls, and proper component reuse.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/me/layout.tsx (1)

32-46: Harden source normalization before merge/dedupe.

Line 38 assumes both sources are iterable arrays. If one path is truthy but non-array, this throws. Also, mixed numeric/string IDs can bypass dedupe. Normalize both inputs with Array.isArray and coerce dedupe keys.

♻️ Proposed hardening
-    const fromUser =
-      (profile as any)?.user?.hackathonSubmissionsAsParticipant || [];
+    const fromUserRaw =
+      (profile as any)?.user?.hackathonSubmissionsAsParticipant;
+    const fromUser = Array.isArray(fromUserRaw) ? fromUserRaw : [];
@@
-    const fromProfile =
-      (profile as any)?.hackathonSubmissionsAsParticipant || [];
+    const fromProfileRaw =
+      (profile as any)?.hackathonSubmissionsAsParticipant;
+    const fromProfile = Array.isArray(fromProfileRaw) ? fromProfileRaw : [];
@@
-    const seen = new Set<string>();
+    const seen = new Set<string>();
     return merged.filter((s: any) => {
-      const id = s?.id || s?._id;
-      if (!id || seen.has(id)) return false;
-      seen.add(id);
+      const rawId = s?.id ?? s?._id;
+      const id = rawId != null ? String(rawId) : '';
+      if (!id || seen.has(id)) return false;
+      seen.add(id);
       return true;
     }).length;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/layout.tsx` around lines 32 - 46, Normalize and guard both sources
before merging/dedupe: ensure (profile as
any)?.user?.hackathonSubmissionsAsParticipant and (profile as
any)?.hackathonSubmissionsAsParticipant are treated as arrays (use Array.isArray
and default to [] for fromUser and fromProfile), build merged from those safe
arrays, and coerce dedupe keys to a consistent string form (e.g. compute idKey =
String(s?.id ?? s?._id ?? '') and skip empty keys) when using seen/set to filter
unique entries in the merged array; update references to fromUser, fromProfile,
merged, and seen accordingly.
app/me/hackathons/submissions/submission-components.tsx (1)

28-50: Model disqualificationReason in SubmissionRow instead of casting to any.

The current cast at Line 345/351 bypasses type safety in a core UI path. Add the field to SubmissionRow and remove the casts.

🧩 Proposed type-safe update
 export type SubmissionRow = {
   id: string;
@@
   comments?: number | any[];
+  disqualificationReason?: string;
   hackathon?: {
@@
-      {(submission.status || '').toLowerCase() === 'disqualified' &&
-        (submission as any).disqualificationReason && (
+      {(submission.status || '').toLowerCase() === 'disqualified' &&
+        submission.disqualificationReason && (
           <div className='rounded-xl border border-red-500/30 bg-red-500/10 p-4'>
@@
-              {(submission as any).disqualificationReason}
+              {submission.disqualificationReason}
             </p>
           </div>
         )}

Also applies to: 344-352

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 28 -
50, SubmissionRow is missing a typed disqualificationReason field and the code
currently casts to any; add a strongly-typed field to SubmissionRow (e.g.
disqualificationReason?: string | null or a small object type like { reason:
string; details?: string } depending on backend shape) and then remove the casts
to any where disqualificationReason is read (the places that currently cast at
the submission usage sites). Update any code that expects the cast to instead
use the new typed field (SubmissionRow.disqualificationReason) and adjust
null/undefined checks accordingly to restore type safety.
🤖 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/me/hackathons/submissions/page.tsx`:
- Around line 219-266: Headers with onClick handlers (e.g., the <th> elements
using thClass and calling handleSort('projectName') and rendering SortIcon) must
be made keyboard-accessible: move the click interaction into a focusable
<button> inside each <th> (keep existing classes on the <th> for layout), remove
onClick from the <th>, and call handleSort from the button’s onClick/onKey
handlers; set aria-sort on the <th> based on sortField and sortDir (e.g.,
"ascending", "descending" or "none") so assistive tech can read the current
state; ensure the button has an accessible label (visible text plus the existing
SortIcon) and apply the same change for the other sortable columns ('hackathon',
'status', 'submittedAt', 'rank') so SortIcon, sortField and sortDir logic
remains intact.

In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 382-402: The row currently routes all auxiliary clicks and any
non-left mouse button into handleClick and only supports Enter for keyboard
activation; update the click/key handling so right-clicks don't open the drawer
and Space activates the button. In handleClick (and the onAuxClick usage) only
treat middle-click (e.button === 1) or Ctrl/Cmd modifiers as "open in new tab",
and only treat left-click (e.button === 0) as activation that calls onClick;
ensure onAuxClick checks e.button === 1 before delegating to handleClick or call
a separate onAuxClick that only handles middle-click. For keyboard, change the
onKeyDown handler to call onClick() for both 'Enter' and ' ' (Space), and call
e.preventDefault() for Space to prevent page scrolling. Reference: handleClick,
onAuxClick, onKeyDown, role='button', tabIndex.

---

Nitpick comments:
In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 28-50: SubmissionRow is missing a typed disqualificationReason
field and the code currently casts to any; add a strongly-typed field to
SubmissionRow (e.g. disqualificationReason?: string | null or a small object
type like { reason: string; details?: string } depending on backend shape) and
then remove the casts to any where disqualificationReason is read (the places
that currently cast at the submission usage sites). Update any code that expects
the cast to instead use the new typed field
(SubmissionRow.disqualificationReason) and adjust null/undefined checks
accordingly to restore type safety.

In `@app/me/layout.tsx`:
- Around line 32-46: Normalize and guard both sources before merging/dedupe:
ensure (profile as any)?.user?.hackathonSubmissionsAsParticipant and (profile as
any)?.hackathonSubmissionsAsParticipant are treated as arrays (use Array.isArray
and default to [] for fromUser and fromProfile), build merged from those safe
arrays, and coerce dedupe keys to a consistent string form (e.g. compute idKey =
String(s?.id ?? s?._id ?? '') and skip empty keys) when using seen/set to filter
unique entries in the merged array; update references to fromUser, fromProfile,
merged, and seen accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc1293 and da8de17.

📒 Files selected for processing (6)
  • app/me/hackathons/submissions/page.tsx
  • app/me/hackathons/submissions/submission-components.tsx
  • app/me/layout.tsx
  • components/app-sidebar.tsx
  • components/profile/PublicEarningsTab.tsx
  • docs/project-detail-redesign.md

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
app/me/hackathons/submissions/submission-components.tsx (1)

383-387: ⚠️ Potential issue | 🟠 Major

Cmd/Ctrl+click on row does not follow the new-tab behavior requirement.

Middle-click is handled, but Ctrl/Cmd + left-click currently falls through to opening the drawer.

🖱️ Suggested interaction fix
   const handleLeftClick = (e: React.MouseEvent) => {
     // Left click only
     if (e.button !== 0) return;
+    if (e.metaKey || e.ctrlKey) {
+      e.preventDefault();
+      window.open(viewUrl, '_blank', 'noopener,noreferrer');
+      return;
+    }
     onClick();
   };

Also applies to: 389-394

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 383 -
387, The left-click handler (handleLeftClick) currently only ignores non-left
buttons but still runs on Ctrl/Cmd+click; update handleLeftClick to also
early-return when e.ctrlKey or e.metaKey (and optionally e.shiftKey if desired),
so Ctrl/Cmd+Click falls through to browser default (open in new tab) instead of
calling onClick; apply the same fix to the similar handler around the other
click handling block (the other left-click handler in this file) so both respect
modifier keys.
🧹 Nitpick comments (4)
app/me/hackathons/submissions/page.tsx (2)

26-26: Use const-arrow export with explicit return type for the page component.

This file uses a function declaration for the exported component; project TSX guideline prefers const-arrow + explicit type.

♻️ Suggested change
-export default function SubmissionsPage() {
+const SubmissionsPage = (): JSX.Element => {
   const router = useRouter();
   const { user, isLoading } = useAuthStatus();
@@
-}
+};
+
+export default SubmissionsPage;

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

Also applies to: 369-369

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/page.tsx` at line 26, Replace the function
declaration export default function SubmissionsPage() with a const-arrow export
using an explicit React/Next.js component return type (e.g., const
SubmissionsPage: React.FC or NextPage) and export default that const; update the
other occurrence referenced (the same SubmissionsPage at the other location) to
the same pattern so both use a const arrow with explicit type annotation.

37-47: Reduce any in submission normalization to keep type guarantees.

Lines 39/43/46/58 rely on any, which weakens safety in a critical data-mapping path. If Trustless Work profile/submission types are available, this mapping should be typed end-to-end.

As per coding guidelines, "Use provided Types from Trustless Work documentation when applicable".

Also applies to: 58-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/page.tsx` around lines 37 - 47, The submission
normalization uses unsafe any types in the useMemo block (variables profile,
fromUser, fromProfile) and weakens guarantees for rawSubmissions/SubmissionRow;
replace the any usages with the appropriate Trustless Work types (e.g.,
ProfileType, HackathonSubmissionType or whatever the SDK/definitions export),
update the local variables fromUser and fromProfile to be typed arrays of that
submission type, and adjust the mapping logic (the normalization found around
the map block that produces SubmissionRow between lines ~58-74) to accept and
return strongly typed fields so rawSubmissions: SubmissionRow[] is produced
end-to-end with proper type checks via the exported types rather than any.
app/me/hackathons/submissions/submission-components.tsx (2)

29-51: Type drift: disqualificationReason is read via any but missing from SubmissionRow.

This model mismatch forces unsafe casts and can hide typos/runtime bugs.

♻️ Suggested change
 export type SubmissionRow = {
   id: string;
   projectName: string;
@@
   votes?: number | any[];
   comments?: number | any[];
+  disqualificationReason?: string;
   hackathon?: {
@@
-      {(submission.status || '').toLowerCase() === 'disqualified' &&
-        (submission as any).disqualificationReason && (
+      {(submission.status || '').toLowerCase() === 'disqualified' &&
+        submission.disqualificationReason && (
           <div className='rounded-xl border border-red-500/30 bg-red-500/10 p-4'>
@@
-            <p className='text-sm text-red-300'>
-              {(submission as any).disqualificationReason}
-            </p>
+            <p className='text-sm text-red-300'>
+              {submission.disqualificationReason}
+            </p>
           </div>
         )}

As per coding guidelines, "Use provided Types from Trustless Work documentation when applicable".

Also applies to: 345-353

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 29 -
51, The SubmissionRow type is missing the disqualificationReason field which is
currently being read as any elsewhere; update the SubmissionRow type declaration
to include disqualificationReason?: any (or a more specific union such as string
| null | any[] if you know the shape) so callers no longer need unsafe casts;
make the same addition to the equivalent submission DTO/type definitions
referenced later (the other SubmissionRow-like declarations around the file,
e.g., the block noted at lines 345-353) and run TypeScript type checks to remove
the unsafe any casts.

55-58: Align exported TSX functions with const-arrow + explicit type style.

This file’s exported functions/components are function declarations; project TSX style prefers typed const-arrow exports.

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

Also applies to: 101-102, 114-122, 132-136, 360-368

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 55 -
58, Convert the exported function declarations to typed const-arrow exports:
replace the function declaration for getStatusConfig with a const
getStatusConfig: (status: string) => { label: string; className: string } =
(status) => { ... } and do the same for the other exported functions/components
referenced (the ones around lines 101-102, 114-122, 132-136, 360-368), ensuring
each has an explicit parameter and return type annotation and uses a const-arrow
form instead of the function keyword so they conform to the project's TSX style.
🤖 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/me/hackathons/submissions/page.tsx`:
- Line 28: The page is calling useAuthStatus(), which triggers getMe() on mount;
remove this hook usage and rely on session-only data to avoid the extra request:
delete or replace the line using useAuthStatus() (const { user, isLoading } =
useAuthStatus()) and instead use the existing session (session?.user and
session.status) from the component (e.g., via useSession or the session prop
already present), and update the getMe() trigger logic so it no longer runs when
userProfile is null on initial render (adjust the condition that references
session, 'user' in session, userProfile, and profileLoading or require a
distinct “needsFetch” flag). Ensure references to user and isLoading are
replaced with session?.user and session.status so getMe() is never implicitly
called by useAuthStatus().

In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 309-313: The anchors directly bind user-supplied
submission.videoUrl and link.url — validate and sanitize these before use: parse
the value (e.g., new URL(...) or URL constructor) and allow only safe schemes
such as http and https (optionally mailto if intended); if parsing fails or the
scheme is not allowed, do not set the unsafe value on href (use a safe fallback
like '#' or omit href and/or disable the anchor). Apply this check where
submission.videoUrl and link.url are used in submission-components.tsx so clicks
cannot execute javascript: or other unsafe schemes.
- Around line 441-448: The anchor for opening a submission (the <a ...
className='text-zinc-500 opacity-0 ... group-hover:opacity-100' ...
aria-label={`Open ${submission.projectName} in new tab`}> element) is
keyboard-focusable while hidden; add keyboard handling so it is removed from the
tab order when visually hidden and becomes focusable when visible. Implement a
small local state (e.g., isHoverOrFocus) in the component and set
tabIndex={isHoverOrFocus ? 0 : -1} and aria-hidden={!isHoverOrFocus} on that
anchor, toggling isHoverOrFocus on onMouseEnter/onMouseLeave and onFocus/onBlur
(or onKeyDown if needed) so keyboard users cannot tab to the icon when opacity-0
but can once it becomes visible.

---

Duplicate comments:
In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 383-387: The left-click handler (handleLeftClick) currently only
ignores non-left buttons but still runs on Ctrl/Cmd+click; update
handleLeftClick to also early-return when e.ctrlKey or e.metaKey (and optionally
e.shiftKey if desired), so Ctrl/Cmd+Click falls through to browser default (open
in new tab) instead of calling onClick; apply the same fix to the similar
handler around the other click handling block (the other left-click handler in
this file) so both respect modifier keys.

---

Nitpick comments:
In `@app/me/hackathons/submissions/page.tsx`:
- Line 26: Replace the function declaration export default function
SubmissionsPage() with a const-arrow export using an explicit React/Next.js
component return type (e.g., const SubmissionsPage: React.FC or NextPage) and
export default that const; update the other occurrence referenced (the same
SubmissionsPage at the other location) to the same pattern so both use a const
arrow with explicit type annotation.
- Around line 37-47: The submission normalization uses unsafe any types in the
useMemo block (variables profile, fromUser, fromProfile) and weakens guarantees
for rawSubmissions/SubmissionRow; replace the any usages with the appropriate
Trustless Work types (e.g., ProfileType, HackathonSubmissionType or whatever the
SDK/definitions export), update the local variables fromUser and fromProfile to
be typed arrays of that submission type, and adjust the mapping logic (the
normalization found around the map block that produces SubmissionRow between
lines ~58-74) to accept and return strongly typed fields so rawSubmissions:
SubmissionRow[] is produced end-to-end with proper type checks via the exported
types rather than any.

In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 29-51: The SubmissionRow type is missing the
disqualificationReason field which is currently being read as any elsewhere;
update the SubmissionRow type declaration to include disqualificationReason?:
any (or a more specific union such as string | null | any[] if you know the
shape) so callers no longer need unsafe casts; make the same addition to the
equivalent submission DTO/type definitions referenced later (the other
SubmissionRow-like declarations around the file, e.g., the block noted at lines
345-353) and run TypeScript type checks to remove the unsafe any casts.
- Around line 55-58: Convert the exported function declarations to typed
const-arrow exports: replace the function declaration for getStatusConfig with a
const getStatusConfig: (status: string) => { label: string; className: string }
= (status) => { ... } and do the same for the other exported
functions/components referenced (the ones around lines 101-102, 114-122,
132-136, 360-368), ensuring each has an explicit parameter and return type
annotation and uses a const-arrow form instead of the function keyword so they
conform to the project's TSX style.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between da8de17 and 4788c79.

📒 Files selected for processing (2)
  • app/me/hackathons/submissions/page.tsx
  • app/me/hackathons/submissions/submission-components.tsx

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: 1

♻️ Duplicate comments (1)
app/me/hackathons/submissions/submission-components.tsx (1)

404-415: ⚠️ Potential issue | 🟠 Major

Restore Cmd/Ctrl+click behavior for row interactions.

Line 404 currently treats all primary clicks as drawer-open, so Cmd/Ctrl+left-click no longer opens a new tab.

🔧 Suggested fix
 const handleLeftClick = (e: React.MouseEvent) => {
-  // Left click only
   if (e.button !== 0) return;
+  // Cmd/Ctrl + left click -> open in new tab
+  if (e.metaKey || e.ctrlKey) {
+    e.stopPropagation();
+    window.open(viewUrl, '_blank', 'noopener,noreferrer');
+    return;
+  }
   onClick();
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 404 -
415, The left-click handler handleLeftClick currently treats any primary click
as a normal click and calls onClick(), which breaks Cmd/Ctrl+click new-tab
behavior; update handleLeftClick to detect modifier keys (e.metaKey or
e.ctrlKey) and if pressed, prevent the normal drawer open and instead open
viewUrl in a new tab (window.open with '_blank' and 'noopener,noreferrer'),
otherwise call onClick(); keep the existing handleAuxClick behavior for middle
clicks and ensure you call e.stopPropagation() when opening a new tab to avoid
triggering onClick.
🧹 Nitpick comments (3)
app/me/hackathons/submissions/submission-components.tsx (2)

45-67: Make disqualification metadata type-safe instead of using as any.

Lines 366-373 rely on (submission as any).disqualificationReason, which bypasses type safety and can hide shape regressions.

🧩 Suggested typing cleanup
 export type SubmissionRow = {
   id: string;
   projectName: string;
@@
-  votes?: number | any[];
-  comments?: number | any[];
+  votes?: number | Array<unknown>;
+  comments?: number | Array<unknown>;
+  disqualificationReason?: string;
   hackathon?: {
@@
-      {(submission.status || '').toLowerCase() === 'disqualified' &&
-        (submission as any).disqualificationReason && (
+      {(submission.status || '').toLowerCase() === 'disqualified' &&
+        submission.disqualificationReason && (
           <div className='rounded-xl border border-red-500/30 bg-red-500/10 p-4'>
@@
-              {(submission as any).disqualificationReason}
+              {submission.disqualificationReason}
             </p>
           </div>
         )}

Also applies to: 365-373

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 45 -
67, The code is using (submission as any).disqualificationReason which bypasses
type checking; add a proper disqualification shape to the SubmissionRow type
(e.g., disqualification?: { reason?: string; disqualifiedBy?: string; at?:
string } or a named Disqualification interface) and update all usages (replace
(submission as any).disqualificationReason with
submission.disqualification?.reason or submission.disqualificationReason if you
choose a flat field) so the compiler enforces the shape and remove the unsafe
`as any` casts; ensure any components/functions referencing disqualification
fields use the new typed property names (SubmissionRow and the component reading
disqualification).

24-35: Extract inline JSX callbacks to named handlers with handle* prefix and convert exported functions to const arrow syntax.

This file has two convention violations:

  1. Function declarations: All exported utilities and components use export function syntax. Per repo conventions for .tsx files, these should be const arrow functions with explicit type annotations (e.g., const getSafeUrl = (urlString?: string): string | undefined => {...}).

  2. Inline event handlers: Anonymous inline callbacks (lines 202, 435–436, 468–470) lack named handlers. These should be extracted to named functions prefixed with handle* (e.g., const handleMouseEnter = () => setIsHoverOrFocus(true)).

Affected lines: 24–35, 71–75, 117–126, 130–138, 148–152, 202–203, 380–388, 435–436, 468–470.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/submission-components.tsx` around lines 24 -
35, Change exported function declarations to const arrow exports with explicit
type annotations (e.g., replace "export function getSafeUrl(...)" with "export
const getSafeUrl = (urlString?: string): string | undefined => { ... }"). Also
extract all inline JSX callbacks into named handler functions prefixed with
"handle" (for the inline callbacks at the noted locations create handlers such
as handleMouseEnter, handleMouseLeave, handleClick, handleFocus, handleBlur as
appropriate) and replace the inline arrow callbacks in the JSX with those
handler references; ensure handlers reference the same state setters (e.g.,
setIsHoverOrFocus) and are defined in the same component scope so TypeScript
types remain correct.
app/me/hackathons/submissions/page.tsx (1)

26-26: Convert component to const arrow function with explicit return type annotation.

The function declaration should be converted to const SubmissionsPage: React.FC<void> = () => { ... } per the **/*.{ts,tsx} guideline to prefer const arrow functions with explicit type annotations over function declarations.

Additionally, the inline callback at line 349 (onAddClick={() => router.push('/hackathons')}) should be extracted as a named handler (e.g., const handleExploreClick = () => { ... }) to follow the handle* naming convention for event handlers.

Note: The callbacks at lines 237, 255, 273, 291, 309, and 330 correctly use the handleSort and handleRowClick handlers and do not require changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/page.tsx` at line 26, Convert the
SubmissionsPage function declaration into a const arrow component with an
explicit type annotation (replace `export default function SubmissionsPage()`
with `const SubmissionsPage: React.FC<void> = () => { ... }` and export it as
default), and extract the inline callback passed to onAddClick (currently `() =>
router.push('/hackathons')`) into a named handler such as `const
handleExploreClick = () => { router.push('/hackathons') }` and pass
`handleExploreClick` to onAddClick; keep existing handlers like handleSort and
handleRowClick unchanged.
🤖 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/me/hackathons/submissions/page.tsx`:
- Around line 187-191: The "Under Review" count uses a raw string check that
misses the "under review" variant; inside the rawSubmissions.filter callback
normalize s.status (e.g., lowercase and collapse spaces/underscores/dashes to a
single normalized token) before comparing, then test the normalized value
against "under_review" and "submitted" so both "under review", "under_review",
"under-review", etc. are counted consistently.

---

Duplicate comments:
In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 404-415: The left-click handler handleLeftClick currently treats
any primary click as a normal click and calls onClick(), which breaks
Cmd/Ctrl+click new-tab behavior; update handleLeftClick to detect modifier keys
(e.metaKey or e.ctrlKey) and if pressed, prevent the normal drawer open and
instead open viewUrl in a new tab (window.open with '_blank' and
'noopener,noreferrer'), otherwise call onClick(); keep the existing
handleAuxClick behavior for middle clicks and ensure you call
e.stopPropagation() when opening a new tab to avoid triggering onClick.

---

Nitpick comments:
In `@app/me/hackathons/submissions/page.tsx`:
- Line 26: Convert the SubmissionsPage function declaration into a const arrow
component with an explicit type annotation (replace `export default function
SubmissionsPage()` with `const SubmissionsPage: React.FC<void> = () => { ... }`
and export it as default), and extract the inline callback passed to onAddClick
(currently `() => router.push('/hackathons')`) into a named handler such as
`const handleExploreClick = () => { router.push('/hackathons') }` and pass
`handleExploreClick` to onAddClick; keep existing handlers like handleSort and
handleRowClick unchanged.

In `@app/me/hackathons/submissions/submission-components.tsx`:
- Around line 45-67: The code is using (submission as
any).disqualificationReason which bypasses type checking; add a proper
disqualification shape to the SubmissionRow type (e.g., disqualification?: {
reason?: string; disqualifiedBy?: string; at?: string } or a named
Disqualification interface) and update all usages (replace (submission as
any).disqualificationReason with submission.disqualification?.reason or
submission.disqualificationReason if you choose a flat field) so the compiler
enforces the shape and remove the unsafe `as any` casts; ensure any
components/functions referencing disqualification fields use the new typed
property names (SubmissionRow and the component reading disqualification).
- Around line 24-35: Change exported function declarations to const arrow
exports with explicit type annotations (e.g., replace "export function
getSafeUrl(...)" with "export const getSafeUrl = (urlString?: string): string |
undefined => { ... }"). Also extract all inline JSX callbacks into named handler
functions prefixed with "handle" (for the inline callbacks at the noted
locations create handlers such as handleMouseEnter, handleMouseLeave,
handleClick, handleFocus, handleBlur as appropriate) and replace the inline
arrow callbacks in the JSX with those handler references; ensure handlers
reference the same state setters (e.g., setIsHoverOrFocus) and are defined in
the same component scope so TypeScript types remain correct.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4788c79 and 26129b2.

📒 Files selected for processing (2)
  • app/me/hackathons/submissions/page.tsx
  • app/me/hackathons/submissions/submission-components.tsx

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: 1

🧹 Nitpick comments (1)
app/me/hackathons/submissions/page.tsx (1)

26-59: Convert to arrow function and replace any casts with proper types.

The component uses a function declaration (line 26) and relies on multiple any casts (lines 38, 43, 46, 58, 79–80), which weakens type safety in the session/profile data mapping path. Convert to a const arrow function with explicit types per TSX guidelines.

♻️ Proposed refactor
-export default function SubmissionsPage() {
+type SessionSubmission = {
+  id?: string;
+  _id?: string;
+  projectName?: string;
+  title?: string;
+  name?: string;
+  description?: string;
+  introduction?: string;
+  logo?: string;
+  videoUrl?: string;
+  category?: string;
+  links?: Array<{ type: string; url: string }>;
+  status?: string;
+  rank?: number | null;
+  submittedAt?: string;
+  submissionDate?: string;
+  createdAt?: string;
+  votes?: number | any[];
+  comments?: number | any[];
+  hackathon?: SubmissionRow['hackathon'];
+  disqualificationReason?: string;
+};
+
+const SubmissionsPage = (): JSX.Element => {
   const router = useRouter();
   const { data: session, status } = useSession();
@@
-    const sessionUser = session?.user as any;
+    const sessionUser = session?.user as {
+      profile?: {
+        user?: { hackathonSubmissionsAsParticipant?: SessionSubmission[] };
+        hackathonSubmissionsAsParticipant?: SessionSubmission[];
+      };
+    } | undefined;
@@
-    const fromUser: any[] =
+    const fromUser: SessionSubmission[] =
       profile?.user?.hackathonSubmissionsAsParticipant || [];
@@
-    const fromProfile: any[] = profile?.hackathonSubmissionsAsParticipant || [];
+    const fromProfile: SessionSubmission[] =
+      profile?.hackathonSubmissionsAsParticipant || [];
@@
-    return deduped.map((s: any) => ({
+    return deduped.map((s): SubmissionRow => ({
       id: s.id || s._id || '',
@@
-}
+};
+
+export default SubmissionsPage;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/page.tsx` around lines 26 - 59, The component
is declared as a function and uses many any casts in the session/profile
mapping; change to a typed arrow component (e.g., declare const SubmissionsPage:
React.FC = () => { ...) and replace the any usages with explicit types: use the
session type for session (e.g., Session from next-auth), a Profile type for
profile (or an inline interface matching
profile.hackathonSubmissionsAsParticipant), and SubmissionRow[] for
fromUser/fromProfile/rawSubmissions; update variables sessionUser, profile,
fromUser, fromProfile, merged, deduped and the rawSubmissions useMemo signature
to use these concrete types so you can remove all any casts while preserving the
existing logic in rawSubmissions and keep setSelectedSubmission, sheetOpen,
sortField, sortDir unchanged.
🤖 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/me/hackathons/submissions/page.tsx`:
- Around line 189-192: Normalize status to collapse spaces, dashes and
underscores by updating the replacement applied to s.status (the st computation)
so multiple underscores are also collapsed; change the regex used when creating
st (currently replacing /[\s\-]+/g) to include underscores (e.g., include _ in
the character class) so inputs like under__review normalize to under_review and
the comparison against 'under_review' and 'submitted' remains correct.

---

Nitpick comments:
In `@app/me/hackathons/submissions/page.tsx`:
- Around line 26-59: The component is declared as a function and uses many any
casts in the session/profile mapping; change to a typed arrow component (e.g.,
declare const SubmissionsPage: React.FC = () => { ...) and replace the any
usages with explicit types: use the session type for session (e.g., Session from
next-auth), a Profile type for profile (or an inline interface matching
profile.hackathonSubmissionsAsParticipant), and SubmissionRow[] for
fromUser/fromProfile/rawSubmissions; update variables sessionUser, profile,
fromUser, fromProfile, merged, deduped and the rawSubmissions useMemo signature
to use these concrete types so you can remove all any casts while preserving the
existing logic in rawSubmissions and keep setSelectedSubmission, sheetOpen,
sortField, sortDir unchanged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 26129b2 and 1dd31a1.

📒 Files selected for processing (1)
  • app/me/hackathons/submissions/page.tsx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/me/hackathons/submissions/page.tsx (2)

38-47: Replace any-based session parsing with typed profile/submission models.

This path is the core data contract for the page; any here removes compile-time guarantees and makes shape drift easy to miss.

As per coding guidelines **/*.{ts,tsx}: Use provided Types from Trustless Work documentation when applicable.

Also applies to: 58-74

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/hackathons/submissions/page.tsx` around lines 38 - 47, The current
parsing uses any for sessionUser/profile/submissions (sessionUser, profile,
fromUser, fromProfile, hackathonSubmissionsAsParticipant) which loses
type-safety; replace these any types with the canonical types from the Trustless
Work typings (e.g., import and use the exported SessionUser, UserProfile and
HackathonSubmission types or their equivalent), update the session extraction to
cast session?.user to SessionUser, type profile as UserProfile, and type both
fromUser and fromProfile as HackathonSubmission[] (or the provided submission
list type), then narrow null checks accordingly so the rest of the code uses
typed properties instead of any.

26-26: Prefer a typed const arrow component export for TSX consistency.

Consider switching from a function declaration to a typed const arrow component to align with the project’s TSX convention.

As per coding guidelines **/*.{ts,tsx}: 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/me/hackathons/submissions/page.tsx` at line 26, Replace the function
declaration export default function SubmissionsPage() with a typed const arrow
component: declare const SubmissionsPage with an explicit React function
component type (e.g., React.FC or React.FunctionComponent) and assign an arrow
function, then export default SubmissionsPage; ensure you import the React type
(import React from 'react' or import type { FC } from 'react') and update any
internal references to the SubmissionsPage symbol accordingly.
🤖 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/me/hackathons/submissions/page.tsx`:
- Around line 96-98: In the sort comparator that assigns aVal and bVal from
a.submittedAt and b.submittedAt, guard against invalid dates by parsing the
timestamp (e.g., Date.parse or new Date(...).getTime()) and if the result is NaN
use a safe fallback (0 or -Infinity) so comparisons remain deterministic; update
the assignments for aVal and bVal (the places referencing a.submittedAt and
b.submittedAt) to validate Number.isFinite(parsed) or !Number.isNaN(parsed)
before using the value and otherwise assign the chosen fallback.
- Line 4: The import of motion and AnimatePresence from 'framer-motion' in
app/me/hackathons/submissions/page.tsx requires adding framer-motion to the
project dependencies; update package.json to include "framer-motion" (choose
dependencies if used at runtime in the client bundle or devDependencies if
appropriate), pick a compatible version, run your package manager
(npm/yarn/pnpm) to install so the lockfile is updated, and commit the updated
package.json and lockfile to avoid build/runtime failures when importing
motion/AnimatePresence.

---

Nitpick comments:
In `@app/me/hackathons/submissions/page.tsx`:
- Around line 38-47: The current parsing uses any for
sessionUser/profile/submissions (sessionUser, profile, fromUser, fromProfile,
hackathonSubmissionsAsParticipant) which loses type-safety; replace these any
types with the canonical types from the Trustless Work typings (e.g., import and
use the exported SessionUser, UserProfile and HackathonSubmission types or their
equivalent), update the session extraction to cast session?.user to SessionUser,
type profile as UserProfile, and type both fromUser and fromProfile as
HackathonSubmission[] (or the provided submission list type), then narrow null
checks accordingly so the rest of the code uses typed properties instead of any.
- Line 26: Replace the function declaration export default function
SubmissionsPage() with a typed const arrow component: declare const
SubmissionsPage with an explicit React function component type (e.g., React.FC
or React.FunctionComponent) and assign an arrow function, then export default
SubmissionsPage; ensure you import the React type (import React from 'react' or
import type { FC } from 'react') and update any internal references to the
SubmissionsPage symbol accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd31a1 and 08b4ce6.

📒 Files selected for processing (1)
  • app/me/hackathons/submissions/page.tsx

Copy link
Collaborator

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

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

LGTM!

@Benjtalkshow Benjtalkshow merged commit 9d26bd3 into boundlessfi:main Mar 3, 2026
5 of 6 checks passed
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.

Implementation of "Submissions" Page

2 participants