Skip to content

Conversation

@mitgajera
Copy link

@mitgajera mitgajera commented Dec 17, 2025

Summary

  • This PR fixes an access control issue where PRO-only Earn listing data was exposed to non-PRO users via API responses visible in the browser Network tab.
  • The fix enforces server-side authorization and returns a sanitized preview response for non-PRO users instead of full listing details.

Problem

Although the UI blocks access to PRO-only listings, the backend API returned the full listing payload regardless of user membership, leading to information disclosure.

Solution

  • Enforced role-based authorization at the API layer
  • Introduced a sanitized public listing response for non-PRO users
  • Prevented PRO-only fields from being returned unless the user is authorized
  • Ensured backend, not frontend, controls data access

Changes Included

  • Added PRO membership checks before returning listing details
  • Sanitized API responses for non-PRO users
  • Prevented exposure of:
    • Description
    • Submission details
    • Eligibility questions
    • Internal metadata

Related Issue

Closes #1295 : PRO-only listing data exposed to non-PRO users via Network tab (information disclosure)

Summary by CodeRabbit

  • New Features
    • Transaction links in sponsor dashboard submissions now support expandable views for projects with multiple funding tranches.
    • Listing details pages now display personalized content based on user tier, with certain fields restricted from non-Pro users.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 17, 2025

@mitgajera is attempting to deploy a commit to the Superteam Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Walkthrough

This PR implements server-side access control for PRO-only listing data by integrating Privy-based authentication to sanitize sensitive fields (description, eligibility, requirements, etc.) from API responses when viewers are non-PRO. Additionally, it enhances the payout submission UI with transaction link visualization for multi-tranche projects.

Changes

Cohort / File(s) Summary
API Data Sanitization & Auth
src/pages/api/listings/details/[slug].ts
Implements Privy token-based viewer authentication; introduces sanitizeListingForViewer() to mask sensitive listing fields for non-PRO users; removes previous rate-limiting logic; updates getListingDetailsBySlug() signature to accept optional { viewerIsPro } parameter; adds ListingWithProFlag type and SENSITIVE_LISTING_FIELDS constant.
Page Integration
src/pages/listing/[slug]/index.tsx, src/pages/listing/[slug]/winner.tsx
Adds Privy token fetching and Prisma queries to determine viewer Pro status; passes { viewerIsPro } option to getListingDetailsBySlug(); handles array-context slug extraction with fallback logic.
Payout UI Enhancement
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
Adds conditional rendering for transaction link access on paid payouts; introduces "View transaction links" toggle with chevron for multi-tranche projects; displays truncated txId link to Solscan for non-multi-tranche cases; respects shouldHideTxLinks flag in payment details rendering.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Page as Listing Page
    participant API as API Route
    participant Privy
    participant DB as Prisma/DB
    participant Sanitizer as Data Sanitizer

    Client->>Page: Request listing page (with auth context)
    Page->>Privy: getPrivyToken(context.req)
    Privy-->>Page: privyDid token
    Page->>DB: Query user isPro flag (via privyDid)
    DB-->>Page: { isPro: boolean }
    Page->>API: fetch listing details + { viewerIsPro }
    API->>DB: Fetch full listing data
    DB-->>API: Complete listing payload
    API->>Sanitizer: sanitizeListingForViewer(listing, viewerIsPro)
    alt viewerIsPro = true
        Sanitizer-->>API: Return full listing
    else viewerIsPro = false
        Sanitizer-->>API: Mask sensitive fields (description, eligibility, requirements, etc.)
    end
    API-->>Page: Sanitized listing response
    Page-->>Client: Render listing (data filtered by Pro status)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • API data sanitization logic: Review the correctness of sanitizeListingForViewer() masking all intended sensitive fields and handling null preservation for non-PRO responses.
  • Privy authentication across files: Verify consistent token fetching, error handling, and fallback behavior in both page files; ensure privyDid resolution doesn't introduce race conditions or missing user lookups.
  • Method signature compatibility: Confirm the updated getListingDetailsBySlug() signature with optional options parameter doesn't break existing call sites or introduce breaking changes.
  • Slug array handling: Validate the array-context slug extraction and fallback logic correctly handles both string and array context.query.slug cases.
  • PayoutSection UI logic: Verify the new transaction link conditional branches (multi-tranche toggle vs. single txId link) correctly integrate with existing payout rendering and shouldHideTxLinks flag.

Possibly related PRs

  • Staging #1068: Adds Privy token-based authentication to other listing-related API routes, sharing the same viewer authentication pattern and approach to access control.

Poem

🐰 In the garden of data, we plant with care,
Privy fences now shield what's fair,
PRO and non-PRO see their own view,
While transactions link, shiny and new—
Security whiskers, all preened with delight!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes changes to PayoutSection UI that add transaction link visibility toggles for multi-tranche payouts, which appear unrelated to the PRO listing access control objective and issue #1295. The PayoutSection component changes should be moved to a separate PR, as they address transaction link UI behavior rather than PRO-only data access control.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main objective: preventing unauthorized access to PRO-only listing data by non-PRO users.
Linked Issues check ✅ Passed The PR implements all required objectives from issue #1295: server-side access control for PRO-only listings via Privy token validation, sanitization of sensitive fields (description, eligibility, requirements, commitmentDate, applicationLink, references) for non-PRO users, and prevention of data exposure in API responses.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/pages/api/listings/details/[slug].ts (1)

135-186: Rate limiting has been removed from this public endpoint.

Per retrieved learnings, public API endpoints should implement rate limiting to prevent abuse. The AI summary indicates rate-limit checks were removed. For a publicly accessible endpoint that performs database queries, this could enable:

  • Enumeration attacks on listing slugs
  • Resource exhaustion via repeated requests

Consider re-adding rate limiting or confirming this is intentional and handled elsewhere (e.g., at the infrastructure/CDN layer).

Based on learnings, implement rate limiting and proper authorization checks for public API endpoints to enhance security and prevent abuse.

src/app/api/server-time/route.ts (1)

5-48: Implement rate limiting for this public endpoint to prevent abuse.

A serverTimeRateLimiter exists in the codebase (30 requests per minute) but is not being used in this route. Public endpoints require rate limiting per the app-api-guidelines and to protect against denial-of-service attacks. Import and apply serverTimeRateLimiter to the GET handler:

import { serverTimeRateLimiter } from '@/lib/ratelimit';

export async function GET(request: NextRequest) {
  const { success } = await serverTimeRateLimiter.limit(request.ip || '127.0.0.1');
  if (!success) {
    return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });
  }
  // ... rest of handler
}
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1)

247-303: Fix logic error: payout button shown for paid submissions when transaction links are hidden.

When submission.isPaid is true and shouldHideTxLinks is true, the first condition at line 247 fails, causing the code to fall through to line 282. Since shouldHideTxLinks implies listingStatus === 'Completed', both bounty.isWinnersAnnounced and !isFndnToPay are true, incorrectly displaying the PayoutButton for an already-paid submission.

Apply this diff to fix the fallthrough logic:

                  <TableCell>
                    <div className="flex items-center gap-2">
                      {submission.isPaid && !shouldHideTxLinks ? (
                        isProject && hasMultipleTranches ? (
                          <div
                            className="flex cursor-pointer items-center gap-1 text-sm font-medium text-slate-600 hover:text-slate-900"
                            onClick={() => toggleExpandRow(submission.id)}
                          >
                            <span>View transaction links</span>
                            <ChevronDown
                              className={cn(
                                'h-4 w-4 text-slate-400 transition-transform duration-300 ease-in-out',
                                isExpanded ? 'rotate-180' : 'rotate-0',
                              )}
                            />
                          </div>
                        ) : (
                          <div
                            className="flex cursor-pointer items-center text-sm font-medium text-slate-600 hover:text-slate-900"
                            onClick={() => {
                              const txId = submission.paymentDetails?.[0]?.txId;
                              if (txId) {
                                window.open(
                                  `https://solscan.io/tx/${txId}?cluster=${process.env.NEXT_PUBLIC_PAYMENT_CLUSTER}`,
                                  '_blank',
                                );
                              }
                            }}
                          >
                            {submission.paymentDetails?.[0]?.txId &&
                              truncatePublicKey(
                                submission.paymentDetails[0].txId,
                                5,
                              )}
                            <ExternalLink className="ml-1 h-4 w-4" />
                          </div>
                        )
-                     ) : bounty.isWinnersAnnounced && !isFndnToPay ? (
+                     ) : !submission.isPaid && bounty.isWinnersAnnounced && !isFndnToPay ? (
                        <div className="flex items-center">
                          <PayoutButton
                            bounty={bounty}
                            submission={submission}
                          />
                          {hasMultipleTranches && (
                            <span
                              onClick={() => toggleExpandRow(submission.id)}
                            >
                              <ChevronDown
                                className={cn(
                                  'ml-8 h-4 w-4 text-slate-400 transition-transform duration-300 ease-in-out',
                                  isExpanded ? 'rotate-180' : 'rotate-0',
                                )}
                              />
                            </span>
                          )}
                        </div>
                      ) : (
                        <></>
                      )}
                    </div>
                  </TableCell>
♻️ Duplicate comments (3)
src/pages/listing/[slug]/index.tsx (1)

94-107: Duplicate viewerIsPro resolution pattern.

Same code block exists in winner.tsx (lines 106-117) and the API handler. See the refactoring suggestion in the other file's review.

src/pages/api/listings/details/[slug].ts (1)

152-163: Duplicate viewerIsPro resolution pattern.

This is the third occurrence of the same pattern. Extracting to a shared helper would improve maintainability.

src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx (1)

50-52: Duplicate logic with PayoutSection.tsx.

The same shouldHideTxLinks computation logic is duplicated here and in PayoutSection.tsx (lines 126-130). See the review comment on PayoutSection.tsx for the suggested refactor.

🧹 Nitpick comments (6)
src/pages/listing/[slug]/winner.tsx (1)

106-117: Consider extracting the viewerIsPro resolution logic into a shared helper.

This pattern of resolving privyDid → querying user → extracting isPro is duplicated in src/pages/listing/[slug]/index.tsx (lines 94-105) and src/pages/api/listings/details/[slug].ts (lines 152-163). Extract to a reusable utility to reduce duplication.

Example helper in @/features/auth/utils/:

export async function getViewerIsPro(req: NextApiRequest | IncomingMessage): Promise<boolean> {
  const privyDid = await getPrivyToken(req);
  if (!privyDid) return false;
  
  const viewer = await prisma.user.findUnique({
    where: { privyDid },
    select: { isPro: true },
  });
  
  return viewer?.isPro ?? false;
}
src/pages/api/listings/details/[slug].ts (1)

69-72: Consider a more specific return type than Promise<any>.

Per coding guidelines, top-level module functions should have declared return types. Consider returning Promise<ListingWithProFlag | null> or a union type that reflects the sanitized structure.

 export async function getListingDetailsBySlug(
   slug: string,
   options?: { viewerIsPro?: boolean },
-): Promise<any> {
+): Promise<ListingWithProFlag | null> {
src/app/api/server-time/route.ts (1)

8-13: Remove unnecessary Accept header from HEAD request.

The Accept: application/json header has no effect on HEAD requests since they do not return a response body. The Date header you're extracting is returned regardless of this header.

       const response = await fetch('https://httpbin.org/get', {
         method: 'HEAD',
-        headers: {
-          Accept: 'application/json',
-        },
       });
src/layouts/Grants.tsx (3)

71-87: Consider moving function outside component and adding JSDoc.

The getRewardText function is defined inside the component body, which means it's recreated on every render. For better performance, define it outside the component or memoize it if it needs to access component state. Additionally, per coding guidelines, add a JSDoc comment to document the function's behavior.

Apply this diff to move the function outside and add JSDoc:

+/**
+ * Generates reward text for grant metadata based on min/max reward values.
+ * Returns "between $X" if minReward is valid, "up to $X" otherwise, or empty string if no maxReward.
+ */
+const getRewardText = (grant: GrantWithApplicationCount | null): string => {
+  if (!grant?.maxReward) return '';
+
+  const { minReward, maxReward } = grant;
+  const amount = grantAmount({
+    minReward: minReward ?? 0,
+    maxReward,
+  });
+
+  const hasValidMinReward = minReward && minReward > 0;
+
+  if (hasValidMinReward) {
+    return `between $${amount}`;
+  }
+
+  return `up to $${amount.replace('Up to ', '')}`;
+};
+
 export function GrantPageLayout({
   grant: initialGrant,
   children,
 }: GrantPageProps) {
   const [grant] = useState<typeof initialGrant>(initialGrant);
   const encodedTitle = encodeURIComponent(initialGrant?.title || '');
 
   // ... rest of component
 
-  const getRewardText = (): string => {
-    if (!initialGrant?.maxReward) return '';
-
-    const { minReward, maxReward } = initialGrant;
-    const amount = grantAmount({
-      minReward: minReward ?? 0,
-      maxReward,
-    });
-
-    const hasValidMinReward = minReward && minReward > 0;
-
-    if (hasValidMinReward) {
-      return `between $${amount}`;
-    }
-
-    return `up to $${amount.replace('Up to ', '')}`;
-  };
-
-  const rewardText = getRewardText();
+  const rewardText = getRewardText(initialGrant);

76-76: Inconsistent handling of minReward = 0.

Line 76 passes minReward ?? 0 to grantAmount, meaning if minReward is null or undefined, it becomes 0. However, line 80 checks minReward && minReward > 0 to determine validity. This creates an inconsistency: if minReward is explicitly 0, it's passed to grantAmount but then treated as invalid for text generation. Consider making the handling consistent—either treat 0 as a valid minimum (and pass it along) or treat it as invalid (and pass undefined).

Apply this diff to make the handling consistent:

   const { minReward, maxReward } = initialGrant;
   const amount = grantAmount({
-    minReward: minReward ?? 0,
+    minReward: minReward && minReward > 0 ? minReward : undefined,
     maxReward,
   });
 
   const hasValidMinReward = minReward && minReward > 0;

Alternatively, if grantAmount requires a number and 0 is semantically valid, adjust the validation logic instead.

Also applies to: 80-80


86-86: Fragile string manipulation depends on grantAmount output format.

Line 86 uses amount.replace('Up to ', '') which assumes grantAmount returns a string with the exact prefix "Up to " when minReward is not provided. While this assumption is currently correct (the function returns "Up to " + formatNumberWithSuffix(maxReward) in that case), this creates a hidden dependency on the implementation details of grantAmount. If the formatting logic changes, this replacement will silently fail or produce incorrect text.

Consider refactoring grantAmount to accept a parameter controlling output format (e.g., includeLabel: boolean), or extract the raw numeric amount into a separate return value to eliminate the need for string manipulation.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7709ed and 097f597.

📒 Files selected for processing (36)
  • src/app/api/grants/route.ts (0 hunks)
  • src/app/api/hackathon/route.ts (0 hunks)
  • src/app/api/homepage/feed/route.ts (0 hunks)
  • src/app/api/homepage/recent-earners/route.ts (0 hunks)
  • src/app/api/homepage/sponsor-count/route.ts (0 hunks)
  • src/app/api/homepage/stats/route.ts (0 hunks)
  • src/app/api/homepage/user-count/route.ts (0 hunks)
  • src/app/api/image/sign/route.ts (0 hunks)
  • src/app/api/listings/count/route.ts (0 hunks)
  • src/app/api/listings/route.ts (0 hunks)
  • src/app/api/og/get/route.ts (0 hunks)
  • src/app/api/report-listing/route.ts (0 hunks)
  • src/app/api/server-time/route.ts (1 hunks)
  • src/app/api/wallet/price/route.ts (0 hunks)
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (3 hunks)
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx (3 hunks)
  • src/layouts/Grants.tsx (2 hunks)
  • src/pages/api/comment/[id]/index.ts (0 hunks)
  • src/pages/api/comment/create.ts (0 hunks)
  • src/pages/api/feed/[type]/[id]/get.ts (0 hunks)
  • src/pages/api/feed/get.ts (0 hunks)
  • src/pages/api/grants/[slug].ts (0 hunks)
  • src/pages/api/grants/live.ts (0 hunks)
  • src/pages/api/hackathon/public-stats.ts (0 hunks)
  • src/pages/api/listings/[listingId]/related.ts (0 hunks)
  • src/pages/api/listings/details/[slug].ts (3 hunks)
  • src/pages/api/listings/live.ts (0 hunks)
  • src/pages/api/pow/create.ts (0 hunks)
  • src/pages/api/pow/get.ts (0 hunks)
  • src/pages/api/search/[query].ts (0 hunks)
  • src/pages/api/sponsors/check-name.ts (0 hunks)
  • src/pages/api/sponsors/check-slug.ts (0 hunks)
  • src/pages/listing/[slug]/index.tsx (2 hunks)
  • src/pages/listing/[slug]/winner.tsx (3 hunks)
  • src/utils/getClientIP.ts (0 hunks)
  • src/utils/getClientIPPages.ts (0 hunks)
💤 Files with no reviewable changes (29)
  • src/app/api/homepage/sponsor-count/route.ts
  • src/pages/api/comment/create.ts
  • src/pages/api/feed/get.ts
  • src/pages/api/sponsors/check-name.ts
  • src/pages/api/pow/create.ts
  • src/pages/api/listings/live.ts
  • src/utils/getClientIP.ts
  • src/pages/api/grants/live.ts
  • src/app/api/homepage/recent-earners/route.ts
  • src/app/api/homepage/feed/route.ts
  • src/app/api/homepage/user-count/route.ts
  • src/pages/api/pow/get.ts
  • src/pages/api/comment/[id]/index.ts
  • src/pages/api/grants/[slug].ts
  • src/pages/api/listings/[listingId]/related.ts
  • src/app/api/hackathon/route.ts
  • src/app/api/og/get/route.ts
  • src/utils/getClientIPPages.ts
  • src/app/api/grants/route.ts
  • src/pages/api/feed/[type]/[id]/get.ts
  • src/pages/api/search/[query].ts
  • src/app/api/listings/route.ts
  • src/app/api/listings/count/route.ts
  • src/app/api/report-listing/route.ts
  • src/app/api/image/sign/route.ts
  • src/app/api/homepage/stats/route.ts
  • src/pages/api/sponsors/check-slug.ts
  • src/pages/api/hackathon/public-stats.ts
  • src/app/api/wallet/price/route.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/any-inside-generic-functions.mdc)

**/*.{ts,tsx}: When building generic functions in TypeScript where the return type depends on the input type parameter, use any type assertion when TypeScript cannot match runtime logic to type logic (e.g., conditional return types)
Outside of generic functions, use any type extremely sparingly

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes, such as when sending events between environments
Use switch statements to handle the results of discriminated unions
Use discriminated unions to prevent the 'bag of optionals' problem when describing state with multiple mutually exclusive conditions (e.g., fetching state with status, data, and error fields)

**/*.{ts,tsx}: Do not introduce new enums into the codebase. Retain existing enums.
If you require enum-like behaviour, use an as const object instead of enums
Understand that numeric enums produce reverse mappings, resulting in double the keys compared to the number of members defined

**/*.{ts,tsx}: Use import type whenever you are importing a type
Prefer top-level import type over inline import { type ... } syntax

**/*.{ts,tsx}: Prefer interfaces when modelling inheritance in TypeScript
Avoid using the & (intersection) operator in TypeScript; only use it where interface extends is not possible due to performance concerns

**/*.{ts,tsx}: Use UpperCamelCase (PascalCase) for classes, types, and interfaces (e.g., MyClass, MyInterface)
Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Be aware that with noUncheckedIndexedAccess enabled in tsconfig.json, indexing into objects and arrays returns Type | undefined instead of just Type. Ensure null/undefined checks are performed when accessing object properties or array elements.

Use property: Type | undefined instead of property?: Type for TypeScript type definitions to force explicit property passing and prevent bugs from...

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/app/api/server-time/route.ts
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/default-exports.mdc)

Do not use default exports unless explicitly required by the framework. Use named exports instead (e.g., export function myFunction() rather than export default function myFunction())

Import motion components from 'motion/react' instead of 'framer-motion'

**/*.{js,jsx,ts,tsx}: PostHog event names should follow the 'action_location' convention with lowercase words separated by underscores (e.g., 'getting started_sponsor resources', 'login_navbar')
Be specific when naming PostHog events: the location should clearly identify the UI section (e.g., sponsor resources, navbar, sidebar, banner)
Use action verbs in PostHog event names to describe what the user is doing (e.g., getting started, rate card, view profile, open)
Keep PostHog event names concise using short, descriptive phrases
Implement PostHog event tracking using posthog.capture('action_location') for link clicks, button clicks, and programmatic events

**/*.{js,jsx,ts,tsx}: Use updater functions (functional form) for state updates when multiple updates occur sequentially or when the new state depends on the previous state value
Avoid using stale state values in asynchronous operations; always use the updater function pattern (e.g., setPending(p => p + 1)) instead of direct state references (e.g., setPending(pending + 1))
Include all reactive values (props, state variables, and component-scoped variables) used inside a useEffect in its dependency array
Never suppress the exhaustive-deps linter warning; instead, refactor the Effect to fix the underlying dependency issue using updater functions, Effect Events, or moving logic outside the Effect
Use useEffectEvent (when available) to read non-reactive values inside Effects without triggering re-runs when those values change
Calculate derived values during rendering instead of using Effects for computations; reserve Effects for synchronizing with external systems
Handle event-driven logic directly in event handlers rather than using Effects with condit...

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/app/api/server-time/route.ts
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/jsdoc-comments.mdc)

**/*.{ts,tsx,js,jsx}: Use JSDoc comments to annotate functions and types
Be concise in JSDoc comments, and only provide JSDoc comments if the function's behaviour is not self-evident
Use the JSDoc inline @link tag to link to other functions and types within the same file

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/app/api/server-time/route.ts
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

Use kebab-case for file names (e.g., my-component.ts)

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/app/api/server-time/route.ts
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*.{js,ts,jsx,tsx}: Use camelCase for variables and function names (e.g., myVariable, myFunction())
Use ALL_CAPS for constants and enum values (e.g., MAX_COUNT, Color.RED)

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/app/api/server-time/route.ts
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/return-types.mdc)

React components returning JSX do not require explicit return type declarations

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/listing/[slug]/index.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/we-might-not-need-an-effect.mdc)

**/*.{tsx,ts}: Avoid using Effects for data transformations during rendering; calculate values directly during render instead of using useState and useEffect
Use useMemo instead of Effects for caching expensive calculations
Use the key prop to reset component state when props change, rather than using Effects with setState
Calculate derived state during render based on props rather than adjusting state in Effects
Place event-specific logic in event handlers rather than Effects
Avoid chains of Effects that trigger state updates; calculate all derived state at once in event handlers or during render
Show notifications and post-render feedback in event handlers rather than Effects
When fetching data in Effects, implement proper cleanup to prevent race conditions by ignoring stale responses
Use custom Hooks to encapsulate data fetching and subscription logic instead of implementing Effects directly in components
Use Effects only for synchronizing with external systems such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets

Files:

  • src/layouts/Grants.tsx
  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx
  • src/app/api/server-time/route.ts
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/app-api-guidelines.mdc)

src/app/api/**/*.{ts,tsx}: Use NextResponse.json() with async GET/POST handlers as the standard structure for Next.js App Router API endpoints
Route handlers should accept request parameters with async props destructuring for accessing dynamic segments (e.g., props: { params: Promise<{ id: string }> })
Use getUserSession(await headers()) and getSponsorSession(await headers()) from @/features/auth/utils for session authentication in API routes
Check session.error and session.data before using session data, returning NextResponse.json with appropriate error status
Use checkGrantSponsorAuth() to validate sponsor authorization and return NextResponse.json with error details when authorization fails
Extract URL query parameters using new URL(request.url) and searchParams.get() for request handling
Parse request body using await request.json() and log it using safeStringify() for debugging
Return success responses with NextResponse.json({ data, message: 'Success' }, { status: 200 })
Return error responses with NextResponse.json({ error, message: userFriendlyMessage }, { status: errorCode })
Use logger from @/lib/logger for all logging with appropriate levels (debug, info, warn, error)
Wrap main API route logic in try-catch block and log errors using logger.error()
Use waitUntil() from @vercel/functions for non-blocking background task operations with error logging
Use Zod schemas with safeParse() to validate request data and return validation error details on failure
Implement response caching with Cache-Control headers (e.g., 'public, s-maxage=60, stale-while-revalidate=30') for GET endpoints
Use maxDuration export to configure route segment timeout limits (e.g., export const maxDuration = 300)
Organize API route files with imports first, followed by type definitions, then route handler exports
Keep route handlers focused and single-responsibility, avoiding multiple unrelated operations
Always validate input data in API endpoints before processing
Use appropriate HTT...

Files:

  • src/app/api/server-time/route.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/return-types.mdc)

Declare return types for top-level module functions in TypeScript (exception: React components returning JSX)

Files:

  • src/app/api/server-time/route.ts
  • src/pages/api/listings/details/[slug].ts
src/pages/api/**/*

📄 CodeRabbit inference engine (.cursor/rules/pages-api-guidelines.mdc)

src/pages/api/**/*: Use withAuth HOC for basic user authentication and withSponsorAuth HOC for sponsor-specific routes in Next.js API endpoints
Type protected requests with appropriate interfaces (NextApiRequestWithUser or NextApiRequestWithSponsor) from '@/features/auth/types'
Always use the logger from '@/lib/logger' for consistent logging in API endpoints
Use debug logs for request data, info logs for successful operations, warning logs for handled issues, and error logs for exceptions
Always wrap main logic in try-catch blocks and log errors using safeStringify before returning 500 status
Use HTTP status codes: 200 for success, 400 for validation errors, 401 for unauthorized (no auth token), 403 for forbidden (insufficient permissions), 404 for not found, 500 for internal server error
Use Zod schemas for request validation with safeParse and return 403 status with validation errors on failure
Return success responses with format { data: result, message: 'Operation successful' } and error responses with format { error: errorMessage, message: userFriendlyMessage }
Use Prisma client from '@/prisma' for database operations in Next.js API endpoints
Always use TypeScript types for request/response objects, specifically importing NextApiResponse from 'next'
Organize API endpoint files with: imports first, then type imports, then handler function, then export with auth wrapper
Use setCacheHeaders utility for setting appropriate caching headers on responses in Next.js API endpoints

Files:

  • src/pages/api/listings/details/[slug].ts
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-06-24T03:45:32.567Z
Learning: Implement rate limiting and proper authorization checks for public API endpoints to enhance security and prevent abuse.
📚 Learning: 2025-11-27T08:11:43.002Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:11:43.002Z
Learning: Applies to src/pages/api/**/* : Use Prisma client from '@/prisma' for database operations in Next.js API endpoints

Applied to files:

  • src/pages/listing/[slug]/winner.tsx
  • src/pages/api/listings/details/[slug].ts
  • src/pages/listing/[slug]/index.tsx
📚 Learning: 2025-11-27T08:11:43.002Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:11:43.002Z
Learning: Applies to src/pages/api/**/* : Type protected requests with appropriate interfaces (`NextApiRequestWithUser` or `NextApiRequestWithSponsor`) from '@/features/auth/types'

Applied to files:

  • src/pages/listing/[slug]/winner.tsx
  • src/pages/api/listings/details/[slug].ts
📚 Learning: 2025-11-27T08:10:14.867Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:10:14.867Z
Learning: Applies to src/app/api/**/*.{ts,tsx} : Use maxDuration export to configure route segment timeout limits (e.g., export const maxDuration = 300)

Applied to files:

  • src/app/api/server-time/route.ts
📚 Learning: 2025-11-27T08:10:14.867Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:10:14.867Z
Learning: Applies to src/app/api/**/*.{ts,tsx} : Keep route handlers focused and single-responsibility, avoiding multiple unrelated operations

Applied to files:

  • src/app/api/server-time/route.ts
📚 Learning: 2025-11-27T08:10:14.867Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:10:14.867Z
Learning: Applies to src/app/api/**/*.{ts,tsx} : Implement rate limiting where necessary for API endpoints

Applied to files:

  • src/app/api/server-time/route.ts
📚 Learning: 2025-11-27T08:10:14.867Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:10:14.867Z
Learning: Applies to src/app/api/**/*.{ts,tsx} : Use appropriate HTTP methods (GET, POST, etc.) for API route handlers matching the operation semantics

Applied to files:

  • src/app/api/server-time/route.ts
📚 Learning: 2025-11-27T08:11:43.002Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:11:43.002Z
Learning: Use proper rate limiting for public Next.js API endpoints

Applied to files:

  • src/app/api/server-time/route.ts
  • src/pages/api/listings/details/[slug].ts
📚 Learning: 2025-05-30T08:55:12.723Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1089
File: src/features/credits/components/CreditLog.tsx:226-227
Timestamp: 2025-05-30T08:55:12.723Z
Learning: CREDIT_REFUND entries in CreditLog.tsx should remain linkable (not included in isNonLinkableEntry check) even though they relate to unpublished listings.

Applied to files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
🧬 Code graph analysis (4)
src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx (1)
src/features/listings/utils/status.ts (1)
  • getListingStatus (26-79)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1)
src/components/ui/table.tsx (2)
  • TableRow (110-110)
  • TableCell (106-106)
src/pages/api/listings/details/[slug].ts (3)
src/utils/safeStringify.ts (1)
  • convertDatesToISO (18-32)
src/features/auth/utils/getPrivyToken.ts (1)
  • getPrivyToken (9-57)
src/prisma.ts (1)
  • prisma (60-60)
src/pages/listing/[slug]/index.tsx (3)
src/pages/api/listings/details/[slug].ts (1)
  • getListingDetailsBySlug (69-133)
src/features/auth/utils/getPrivyToken.ts (1)
  • getPrivyToken (9-57)
src/prisma.ts (1)
  • prisma (60-60)
🔇 Additional comments (9)
src/pages/listing/[slug]/winner.tsx (1)

121-141: Verify whether winner submission data should also be sanitized for non-PRO users.

The PR objective is to prevent PRO-only listing data exposure. Currently, if bountyData.id exists (even for PRO listings viewed by non-PRO users), winner submissions are still fetched and returned. Confirm that exposing winner names/photos for PRO listings is intentional for non-PRO viewers.

src/pages/api/listings/details/[slug].ts (2)

31-38: Verify all sensitive fields from the security issue are included.

Cross-reference with issue #1295 requirements:

  • description (full listing description)
  • eligibility (eligibility fields)
  • requirements (application/submission requirements)
  • applicationLink (submission details)
  • references
  • commitmentDate

Confirm no additional sensitive fields (e.g., submissionRequirements, skills, pocSocials, internal IDs) should be redacted per your security requirements.


40-67: Sanitization logic is sound.

The guard at line 44 correctly short-circuits when:

  1. Listing is null
  2. Listing is not PRO-only (isPro !== true)
  3. Viewer has PRO access

Using null instead of undefined for stable JSON shape is a good practice.

src/pages/listing/[slug]/index.tsx (1)

78-81: No action needed. DescriptionUI already handles null description gracefully through two mechanisms: (1) when isPro && !user?.isPro, it displays a PRO paywall UI (lines 186-259); (2) for normal rendering, the description ?? '' fallback on line 85 converts null to an empty string, preventing any crashes or "null" text from appearing. The component safely renders empty content when description is null.

src/layouts/Grants.tsx (2)

97-97: LGTM: Proper conditional inclusion of reward text in metadata.

The Meta description correctly uses optional chaining and conditional rendering to include reward text only when available. The template string construction handles missing values gracefully.


104-107: LGTM: Improved Open Graph image alt text for accessibility.

The dynamic alt text provides better context for the Open Graph image, using appropriate fallbacks for missing data. This enhances SEO and accessibility.

src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (2)

307-318: Conditional rendering is consistent with transaction link hiding.

The expanded payment details row is correctly gated by !shouldHideTxLinks, which aligns with the feature's intent to hide transaction information when the foundation handles payments and the listing is completed.


126-130: Verify alignment with PR objectives.

The PR title indicates this change should "prevent PRO-only listing data exposure to non-PRO users" with server-side access control enforcement. However, the changes in this file hide transaction links based on foundation payment status (isFndnPaying and listingStatus === 'Completed'), which appears unrelated to PRO membership or access control.

Please confirm:

  1. Are these transaction-hiding changes intentionally part of the PRO access control fix, or were they bundled separately?
  2. Should transaction links be hidden when the foundation pays regardless of user PRO status?
  3. If these are separate concerns, consider splitting them into separate PRs for clearer change tracking.
src/features/sponsor-dashboard/components/Submissions/SubmissionPanel.tsx (1)

111-130: Conditional gating is correct and consistent.

The addition of !shouldHideTxLinks at line 115 correctly prevents the "View Payment Tx" button from displaying when transaction links should be hidden. The condition appropriately uses optional chaining for selectedSubmission and aligns with the transaction-hiding logic in PayoutSection.tsx.

Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1)

279-424: Critical: File has severe syntax errors and will not compile.

The static analysis reports 12 parse errors in this region, and manual inspection confirms the JSX structure is broken:

  1. Line 279: Orphaned )} after closing </TableCell> from Prize column
  2. Lines 280-321: A TableCell block appears with transaction link logic
  3. Lines 322-334: % Paid column logic appears mid-stream
  4. Lines 335-409: Duplicate transaction link logic nearly identical to lines 280-321
  5. Lines 420-424: Duplicate closing tags (</React.Fragment>, </TableBody>, </Table>)

This appears to be a corrupted merge or incomplete edit. The intended change is to add shouldHideTxLinks checks, but the file structure is invalid.

The file needs to be restructured. The correct flow after the Prize <TableCell> (ending at line 278) should be:

                    </TableCell>
                    {isProject && !isFndnToPay && (
                      <TableCell>
                        {/* % Paid progress bar */}
                      </TableCell>
                    )}
                    {!isFndnToPay && (
                      <TableCell>
                        <div className="flex items-center gap-2">
                          {submission.isPaid && !shouldHideTxLinks ? (
                            // Transaction link logic (single or multi-tranche)
                          ) : bounty.isWinnersAnnounced && !isFndnToPay ? (
                            // PayoutButton logic
                          ) : (
                            <></>
                          )}
                        </div>
                      </TableCell>
                    )}
                  </TableRow>
                  {isExpanded && isProject && hasMultipleTranches && !shouldHideTxLinks && (
                    <TableRow>
                      <TableCell />
                      <PaymentDetailsRow ... />
                    </TableRow>
                  )}
                </React.Fragment>

Please review the file diff carefully and ensure all opening/closing tags match. Run a local build to verify the fix compiles correctly.

♻️ Duplicate comments (1)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1)

145-149: Extract duplicated shouldHideTxLinks logic to a shared utility.

This computation (bounty.isFndnPaying && listingStatus === 'Completed') is duplicated in SubmissionPanel.tsx. Consider extracting to src/features/listings/utils/status.ts as suggested in the previous review.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 097f597 and 3d4813d.

📒 Files selected for processing (3)
  • src/app/api/wallet/price/route.ts (0 hunks)
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (4 hunks)
  • src/pages/listing/[slug]/winner.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • src/app/api/wallet/price/route.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/any-inside-generic-functions.mdc)

**/*.{ts,tsx}: When building generic functions in TypeScript where the return type depends on the input type parameter, use any type assertion when TypeScript cannot match runtime logic to type logic (e.g., conditional return types)
Outside of generic functions, use any type extremely sparingly

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes, such as when sending events between environments
Use switch statements to handle the results of discriminated unions
Use discriminated unions to prevent the 'bag of optionals' problem when describing state with multiple mutually exclusive conditions (e.g., fetching state with status, data, and error fields)

**/*.{ts,tsx}: Do not introduce new enums into the codebase. Retain existing enums.
If you require enum-like behaviour, use an as const object instead of enums
Understand that numeric enums produce reverse mappings, resulting in double the keys compared to the number of members defined

**/*.{ts,tsx}: Use import type whenever you are importing a type
Prefer top-level import type over inline import { type ... } syntax

**/*.{ts,tsx}: Prefer interfaces when modelling inheritance in TypeScript
Avoid using the & (intersection) operator in TypeScript; only use it where interface extends is not possible due to performance concerns

**/*.{ts,tsx}: Use UpperCamelCase (PascalCase) for classes, types, and interfaces (e.g., MyClass, MyInterface)
Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Be aware that with noUncheckedIndexedAccess enabled in tsconfig.json, indexing into objects and arrays returns Type | undefined instead of just Type. Ensure null/undefined checks are performed when accessing object properties or array elements.

Use property: Type | undefined instead of property?: Type for TypeScript type definitions to force explicit property passing and prevent bugs from...

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/default-exports.mdc)

Do not use default exports unless explicitly required by the framework. Use named exports instead (e.g., export function myFunction() rather than export default function myFunction())

Import motion components from 'motion/react' instead of 'framer-motion'

**/*.{js,jsx,ts,tsx}: PostHog event names should follow the 'action_location' convention with lowercase words separated by underscores (e.g., 'getting started_sponsor resources', 'login_navbar')
Be specific when naming PostHog events: the location should clearly identify the UI section (e.g., sponsor resources, navbar, sidebar, banner)
Use action verbs in PostHog event names to describe what the user is doing (e.g., getting started, rate card, view profile, open)
Keep PostHog event names concise using short, descriptive phrases
Implement PostHog event tracking using posthog.capture('action_location') for link clicks, button clicks, and programmatic events

**/*.{js,jsx,ts,tsx}: Use updater functions (functional form) for state updates when multiple updates occur sequentially or when the new state depends on the previous state value
Avoid using stale state values in asynchronous operations; always use the updater function pattern (e.g., setPending(p => p + 1)) instead of direct state references (e.g., setPending(pending + 1))
Include all reactive values (props, state variables, and component-scoped variables) used inside a useEffect in its dependency array
Never suppress the exhaustive-deps linter warning; instead, refactor the Effect to fix the underlying dependency issue using updater functions, Effect Events, or moving logic outside the Effect
Use useEffectEvent (when available) to read non-reactive values inside Effects without triggering re-runs when those values change
Calculate derived values during rendering instead of using Effects for computations; reserve Effects for synchronizing with external systems
Handle event-driven logic directly in event handlers rather than using Effects with condit...

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/jsdoc-comments.mdc)

**/*.{ts,tsx,js,jsx}: Use JSDoc comments to annotate functions and types
Be concise in JSDoc comments, and only provide JSDoc comments if the function's behaviour is not self-evident
Use the JSDoc inline @link tag to link to other functions and types within the same file

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

Use kebab-case for file names (e.g., my-component.ts)

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*.{js,ts,jsx,tsx}: Use camelCase for variables and function names (e.g., myVariable, myFunction())
Use ALL_CAPS for constants and enum values (e.g., MAX_COUNT, Color.RED)

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/return-types.mdc)

React components returning JSX do not require explicit return type declarations

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/we-might-not-need-an-effect.mdc)

**/*.{tsx,ts}: Avoid using Effects for data transformations during rendering; calculate values directly during render instead of using useState and useEffect
Use useMemo instead of Effects for caching expensive calculations
Use the key prop to reset component state when props change, rather than using Effects with setState
Calculate derived state during render based on props rather than adjusting state in Effects
Place event-specific logic in event handlers rather than Effects
Avoid chains of Effects that trigger state updates; calculate all derived state at once in event handlers or during render
Show notifications and post-render feedback in event handlers rather than Effects
When fetching data in Effects, implement proper cleanup to prevent race conditions by ignoring stale responses
Use custom Hooks to encapsulate data fetching and subscription logic instead of implementing Effects directly in components
Use Effects only for synchronizing with external systems such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets

Files:

  • src/pages/listing/[slug]/winner.tsx
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-27T08:11:43.002Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:11:43.002Z
Learning: Applies to src/pages/api/**/* : Use Prisma client from '@/prisma' for database operations in Next.js API endpoints

Applied to files:

  • src/pages/listing/[slug]/winner.tsx
📚 Learning: 2025-11-27T08:11:43.002Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-11-27T08:11:43.002Z
Learning: Applies to src/pages/api/**/* : Type protected requests with appropriate interfaces (`NextApiRequestWithUser` or `NextApiRequestWithSponsor`) from '@/features/auth/types'

Applied to files:

  • src/pages/listing/[slug]/winner.tsx
📚 Learning: 2025-05-30T08:55:12.723Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1089
File: src/features/credits/components/CreditLog.tsx:226-227
Timestamp: 2025-05-30T08:55:12.723Z
Learning: CREDIT_REFUND entries in CreditLog.tsx should remain linkable (not included in isNonLinkableEntry check) even though they relate to unpublished listings.

Applied to files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
🧬 Code graph analysis (1)
src/pages/listing/[slug]/winner.tsx (6)
src/features/auth/utils/getPrivyToken.ts (1)
  • getPrivyToken (9-57)
src/prisma.ts (1)
  • prisma (60-60)
src/pages/api/listings/details/[slug].ts (1)
  • getListingDetailsBySlug (69-133)
src/pages/api/listings/[listingId]/winners.ts (2)
  • getWinningSubmissionsByListingId (8-35)
  • submission (37-63)
src/features/listing-builder/constants/index.ts (1)
  • BONUS_REWARD_POSITION (3-3)
src/interface/submission.ts (1)
  • SubmissionWithUser (43-43)
🪛 Biome (2.1.2)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx

[error] 279-279: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 318-318: Expected corresponding JSX closing tag for 'div'.

Opening tag

closing tag

(parse)


[error] 280-280: Expected corresponding JSX closing tag for 'TableCell'.

Opening tag

closing tag

(parse)


[error] 351-351: expected : but instead found }

Remove }

(parse)


[error] 355-355: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 359-359: expected ) but instead found {

Remove {

(parse)


[error] 359-359: expected , but instead found &&

Remove &&

(parse)


[error] 360-360: expected , but instead found isProject

Remove isProject

(parse)


[error] 360-360: expected , but instead found &&

Remove &&

(parse)


[error] 361-361: expected , but instead found hasMultipleTranches

Remove hasMultipleTranches

(parse)


[error] 361-361: expected , but instead found &&

Remove &&

(parse)


[error] 362-362: expected , but instead found !

Remove !

(parse)


[error] 420-420: Expected an expression but instead found '<'.

Expected an expression here.

(parse)

🔇 Additional comments (5)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1)

148-149: shouldHideTxLinks logic looks correct.

The flag correctly derives from bounty.isFndnPaying and completed status to determine when transaction links should be hidden.

src/pages/listing/[slug]/winner.tsx (4)

9-9: LGTM! Necessary imports for PRO access control.

These imports support the server-side PRO status determination, which is the core security fix for this PR.

Also applies to: 13-13


101-103: Good defensive handling of slug parameter.

The array-safe extraction prevents potential issues with Next.js query parameter formats.


112-125: Excellent implementation of server-side PRO access control.

The logic correctly:

  • Retrieves and verifies the Privy token from the request
  • Looks up the user's PRO status from the database
  • Defaults to non-PRO (secure default) when no authentication is present
  • Passes the PRO status to the listing details function for proper sanitization

This addresses the core PR objective of preventing PRO-only data exposure at the backend level.


127-147: Good defensive guard for winner processing.

The conditional check ensures winner submissions are only fetched when a valid listing ID exists, preventing errors if the listing doesn't exist or was sanitized for non-PRO viewers. The existing winner processing logic is correctly preserved.

@brighton-ifaya
Copy link

Check chat in #1296 first please🙂

@a20hek a20hek changed the base branch from staging to main December 18, 2025 16:26
@a20hek a20hek changed the base branch from main to staging December 18, 2025 16:27
Copy link
Contributor

@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

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d4813d and da41de9.

📒 Files selected for processing (1)
  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/any-inside-generic-functions.mdc)

**/*.{ts,tsx}: When building generic functions in TypeScript where the return type depends on the input type parameter, use any type assertion when TypeScript cannot match runtime logic to type logic (e.g., conditional return types)
Outside of generic functions, use any type extremely sparingly

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes, such as when sending events between environments
Use switch statements to handle the results of discriminated unions
Use discriminated unions to prevent the 'bag of optionals' problem when describing state with multiple mutually exclusive conditions (e.g., fetching state with status, data, and error fields)

**/*.{ts,tsx}: Do not introduce new enums into the codebase. Retain existing enums.
If you require enum-like behaviour, use an as const object instead of enums
Understand that numeric enums produce reverse mappings, resulting in double the keys compared to the number of members defined

**/*.{ts,tsx}: Use import type whenever you are importing a type
Prefer top-level import type over inline import { type ... } syntax

**/*.{ts,tsx}: Prefer interfaces when modelling inheritance in TypeScript
Avoid using the & (intersection) operator in TypeScript; only use it where interface extends is not possible due to performance concerns

**/*.{ts,tsx}: Use UpperCamelCase (PascalCase) for classes, types, and interfaces (e.g., MyClass, MyInterface)
Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Be aware that with noUncheckedIndexedAccess enabled in tsconfig.json, indexing into objects and arrays returns Type | undefined instead of just Type. Ensure null/undefined checks are performed when accessing object properties or array elements.

Use property: Type | undefined instead of property?: Type for TypeScript type definitions to force explicit property passing and prevent bugs from...

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/default-exports.mdc)

Do not use default exports unless explicitly required by the framework. Use named exports instead (e.g., export function myFunction() rather than export default function myFunction())

Import motion components from 'motion/react' instead of 'framer-motion'

**/*.{js,jsx,ts,tsx}: PostHog event names should follow the 'action_location' convention with lowercase words separated by underscores (e.g., 'getting started_sponsor resources', 'login_navbar')
Be specific when naming PostHog events: the location should clearly identify the UI section (e.g., sponsor resources, navbar, sidebar, banner)
Use action verbs in PostHog event names to describe what the user is doing (e.g., getting started, rate card, view profile, open)
Keep PostHog event names concise using short, descriptive phrases
Implement PostHog event tracking using posthog.capture('action_location') for link clicks, button clicks, and programmatic events

**/*.{js,jsx,ts,tsx}: Use updater functions (functional form) for state updates when multiple updates occur sequentially or when the new state depends on the previous state value
Avoid using stale state values in asynchronous operations; always use the updater function pattern (e.g., setPending(p => p + 1)) instead of direct state references (e.g., setPending(pending + 1))
Include all reactive values (props, state variables, and component-scoped variables) used inside a useEffect in its dependency array
Never suppress the exhaustive-deps linter warning; instead, refactor the Effect to fix the underlying dependency issue using updater functions, Effect Events, or moving logic outside the Effect
Use useEffectEvent (when available) to read non-reactive values inside Effects without triggering re-runs when those values change
Calculate derived values during rendering instead of using Effects for computations; reserve Effects for synchronizing with external systems
Handle event-driven logic directly in event handlers rather than using Effects with condit...

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/jsdoc-comments.mdc)

**/*.{ts,tsx,js,jsx}: Use JSDoc comments to annotate functions and types
Be concise in JSDoc comments, and only provide JSDoc comments if the function's behaviour is not self-evident
Use the JSDoc inline @link tag to link to other functions and types within the same file

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

Use kebab-case for file names (e.g., my-component.ts)

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-conventions.mdc)

**/*.{js,ts,jsx,tsx}: Use camelCase for variables and function names (e.g., myVariable, myFunction())
Use ALL_CAPS for constants and enum values (e.g., MAX_COUNT, Color.RED)

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/return-types.mdc)

React components returning JSX do not require explicit return type declarations

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursor/rules/we-might-not-need-an-effect.mdc)

**/*.{tsx,ts}: Avoid using Effects for data transformations during rendering; calculate values directly during render instead of using useState and useEffect
Use useMemo instead of Effects for caching expensive calculations
Use the key prop to reset component state when props change, rather than using Effects with setState
Calculate derived state during render based on props rather than adjusting state in Effects
Place event-specific logic in event handlers rather than Effects
Avoid chains of Effects that trigger state updates; calculate all derived state at once in event handlers or during render
Show notifications and post-render feedback in event handlers rather than Effects
When fetching data in Effects, implement proper cleanup to prevent race conditions by ignoring stale responses
Use custom Hooks to encapsulate data fetching and subscription logic instead of implementing Effects directly in components
Use Effects only for synchronizing with external systems such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets

Files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
🧠 Learnings (1)
📚 Learning: 2025-05-30T08:55:12.723Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1089
File: src/features/credits/components/CreditLog.tsx:226-227
Timestamp: 2025-05-30T08:55:12.723Z
Learning: CREDIT_REFUND entries in CreditLog.tsx should remain linkable (not included in isNonLinkableEntry check) even though they relate to unpublished listings.

Applied to files:

  • src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx
🧬 Code graph analysis (1)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (2)
src/components/ui/table.tsx (1)
  • TableCell (106-106)
src/features/sponsor-dashboard/components/Submissions/PayoutButton.tsx (1)
  • PayoutButton (58-517)
🪛 Biome (2.1.2)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx

[error] 279-279: Unexpected token. Did you mean {'}'} or &rbrace;?

(parse)


[error] 318-318: Expected corresponding JSX closing tag for 'div'.

Opening tag

closing tag

(parse)


[error] 281-281: Expected corresponding JSX closing tag for 'div'.

Opening tag

closing tag

(parse)


[error] 280-280: Expected corresponding JSX closing tag for 'TableCell'.

Opening tag

closing tag

(parse)

🔇 Additional comments (1)
src/features/sponsor-dashboard/components/Submissions/PayoutSection.tsx (1)

335-398: Transaction link rendering logic looks good.

The implementation correctly:

  • Gates transaction link visibility behind isPaid && !shouldHideTxLinks (line 338)
  • Handles both multi-tranche projects (toggle with chevron) and single-payment cases (direct link)
  • Uses truncatePublicKey for display and ExternalLink icon for visual affordance
  • Ensures expanded PaymentDetailsRow respects shouldHideTxLinks at line 403
  • Aligns with the PR objective to conditionally hide transaction data

Removed transaction link display logic for paid submissions.
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.

2 participants