Skip to content

Staging#1275

Merged
JayeshVP24 merged 35 commits into
mainfrom
staging
Nov 18, 2025
Merged

Staging#1275
JayeshVP24 merged 35 commits into
mainfrom
staging

Conversation

@JayeshVP24
Copy link
Copy Markdown
Contributor

@JayeshVP24 JayeshVP24 commented Nov 11, 2025

What does this PR do?

Where should the reviewer start?

How should this be manually tested?

Any background context you want to provide?

What are the relevant issues?

Screenshots (if appropriate)

Summary by CodeRabbit

  • New Features

    • Expanded sponsor-stage UX: stage-specific banners, sidebar sponsor card, header snackbar, welcome video, feature carousel, resources, and stage-aware CTAs.
    • Add Listing card and country-specific banners/pages for region-aware browsing.
  • Improvements

    • Email-impression estimates surfaced for boosting; tiny estimates now suppressed.
    • Refined AI submission labelling and review thresholds.
    • Region and sitemap handling revamped for multi-country support.
  • Chores

    • CSV/Sheets exports include a new "Twitter Link" column.

@vercel
Copy link
Copy Markdown

vercel Bot commented Nov 11, 2025

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

Project Deployment Preview Updated (UTC)
earn Ready Ready Preview Nov 18, 2025 1:23pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 11, 2025

Walkthrough

Adds a sponsor-stage backend API and client-side sponsor workflow; extracts boost email/feature helpers; introduces many sponsor-stage UI components; expands region/country utilities and updates listing/grants query builders, region pages, sitemap, AI submission thresholds, and export fields.

Changes

Cohort / File(s) Summary
Sponsor Stage API & Types
src/app/api/homepage/sponsor-stage/route.ts, src/features/home/types/sponsor-stage.ts, src/features/home/queries/sponsor-stage.ts
New GET route computing sponsor stage and selecting a listing; adds SponsorStage enum and React Query config.
Email Estimate Server Helpers & Route
src/features/listing-builder/components/Form/Boost/server-queries.ts, src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
Extracts Prisma-based email/featured logic into getEmailEstimate and getFeaturedAvailability; email-estimate route now delegates to helper and simplifies request handling.
Sponsor-Stage UI Components
src/features/home/components/SponsorStage/*, src/features/home/components/SponsorFeatures.tsx, src/features/home/components/SponsorResources.tsx, src/features/home/components/SponsorWelcomeVideo.tsx
Adds ~15 client components and banners (NewSponsorBanner, BoostBanner, BoostedBanner, ReviewAiBanner, ReviewBanner, ReviewUrgentBanner, PaymentPendingBanner, UnderVerificationBanner, SponsorStageBanner, SponsorStageSnackbar, SponsorSidebarListing, SponsorWelcomeVideo, SponsorFeatures, SponsorResources, etc.) with CTAs and analytics.
Homepage / Sidebar / Listings Integration
src/pages/index.tsx, src/layouts/Home.tsx, src/features/home/components/SideBar.tsx, src/features/listings/components/ListingsSection.tsx, src/features/listings/components/AddListingCard.tsx
Conditionally render sponsor banners/snackbar and sponsor-specific sidebar widgets on lg+; ListingsSection queries sponsor-stage to surface AddListingCard for NEW_SPONSOR/NEXT_LISTING; AddListingCard added.
Region & Country Utilities
src/features/listings/utils/region.ts
Large API expansion: slug generation, eligible-country discovery, findCountryBySlug, getAllRegionSlugs, and getRegionsFor* helpers (superteam/multi-country/country/user-location).
Query Builders (Listings & Grants)
src/features/listings/utils/query-builder.ts, src/features/grants/utils/query-builder.ts
Refactor region handling to use new region helpers; replace prior uppercase/fallback logic with guarded branches and consolidated AND/OR conditions.
Region Pages, OG, Sitemap
src/pages/regions/[slug]/index.tsx, src/pages/regions/[slug]/all.tsx, src/app/api/dynamic-og/region/route.tsx, src/app/sitemap.ts
Regions pages now accept either superteam (st) or countryData; OG route adds guard path for non-superteam/code cases; sitemap splits regions into multiple sitemaps using getAllRegionSlugs; SitemapBoundaries updated.
AI Submission & Exports
src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts, src/pages/api/sponsor-dashboard/application/export-sheets.ts, src/pages/api/sponsor-dashboard/application/export.ts
Low-quality threshold raised to <15, bottom percentile moved to 25%, committed AI evals include finalLabel; exports add "Twitter Link" column.
Navigation / Footer / Header
src/features/navbar/components/Header.tsx, src/features/navbar/components/Footer.tsx
Header dynamically imports SponsorStageSnackbar; Footer replaces SuperteamCombobox with RegionCombobox and adds region routing/slug logic using new region utilities.
Misc / Utilities / Logs
src/features/announcements/components/SponsorAnnouncements.tsx, src/features/home/components/Banner/TalentBanner.tsx, src/utils/json-ld.ts, src/app/api/grants/route.ts
SponsorAnnouncements conditional widened; TalentBanner click tracking moved outward; json-ld region param type simplified; grants route adds debug log.
Small UX & Boost Helpers
src/features/listing-builder/components/Form/Boost/utils.ts, src/features/listing-builder/components/Form/Rewards/*
resolveEmailImpressions threshold updated (min 1000 fallback); Rewards sheet/footer updated to support boost dismiss prompt and setOpen bypass option.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Query as sponsorStageQuery
    participant API as GET /api/homepage/sponsor-stage
    participant DB as Prisma

    Client->>Query: request sponsor-stage
    Query->>API: GET /api/homepage/sponsor-stage
    activate API
    API->>DB: fetch sponsor listings (OPEN / VERIFYING)
    DB-->>API: listings
    API->>DB: parallel helper calls (email estimates, unpaid winners, uncommitted AI, featured availability)
    DB-->>API: aggregated metrics
    note right of API: determineSponsorStage + urgent tiebreaker
    API-->>Query: { stage, listing|null }
    deactivate API
    Query-->>Client: data -> render banner/snackbar
Loading
sequenceDiagram
    participant Client
    participant Route as POST /api/sponsor-dashboard/listing/email-estimate
    participant Helper as getEmailEstimate
    participant DB as Prisma

    Client->>Route: POST { skills, region }
    Route->>Helper: getEmailEstimate(skills, region)
    activate Helper
    Helper->>DB: count users matching skill/region conditions + email settings
    DB-->>Helper: count
    Helper-->>Route: estimate
    deactivate Helper
    Route-->>Client: { estimate }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas to focus during review:

  • src/app/api/homepage/sponsor-stage/route.ts — orchestration, parallel queries, caching, timing/logs, error handling, and correctness of stage/tiebreak logic.
  • src/features/listings/utils/region.ts — slug generation, findCountryBySlug, getAllRegionSlugs, and region aggregation across superteams/multi-country cases.
  • src/features/listings/utils/query-builder.ts & src/features/grants/utils/query-builder.ts — AND/OR composition and branch correctness for region behavior.
  • src/features/listing-builder/components/Form/Boost/server-queries.ts — parity of getEmailEstimate with previous counting logic and regional/skill mapping.
  • Sponsor-stage UI components that assume stage/listing shapes (SponsorSidebarListing, SponsorStageBanner, SponsorStageSnackbar, BoostBanner) — props/data assumptions and analytics wiring.

Possibly related PRs

Suggested reviewers

  • smustaphaarab16

Poem

🐰 A sponsor-stage blooms bright today,
Banners hop and regions find their way.
Emails counted, slugs set neat,
Boosts and videos skip a beat.
Hop on—new listings light the stay! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title 'Staging' is vague and generic, providing no meaningful information about the actual changes in the changeset, which include sponsor stage features, regional improvements, and API enhancements. Replace with a descriptive title that captures the main feature or change, such as 'Add sponsor stage workflow and regional improvements' or similar to clarify the primary purpose.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch staging

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
Copy Markdown
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: 23

🧹 Nitpick comments (3)
src/app/api/sponsor-dashboard/listing/email-estimate/route.ts (1)

21-21: Use the shared logger instead of console.error.

API handlers are expected to log through our shared logger, not console, so this should be updated accordingly.

+import { logger } from '@/lib/logger';-    console.error('email-estimate error', error);
+    logger.error({ err: error }, 'email-estimate error');

As per coding guidelines

src/features/home/components/SponsorStage/NextListingBanner.tsx (1)

7-79: LGTM! Component structure is clean and follows established patterns.

The NextListingBanner component properly handles analytics events and provides clear sponsor-facing UI. The event handlers are simple and well-named.

Consider extracting the event handlers outside the component if you want to avoid recreating them on each render, though the current approach is acceptable for readability:

const handleCreateListingClick = () => {
  posthog.capture('create listing_sponsor stage banner', {
    stage: 'NEXT_LISTING',
  });
  posthog.capture('click_sponsor stage banner', {
    stage: 'NEXT_LISTING',
  });
};

const handleGetHelpClick = () => {
  posthog.capture('get help_sponsor stage banner', {
    stage: 'NEXT_LISTING',
  });
};

export function NextListingBanner() {
  // ...
}
src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1)

13-87: LGTM! Component properly integrates analytics and navigation.

The PaymentPendingBanner component correctly uses the listing slug for navigation and captures detailed PostHog events with stage, listing type, and slug metadata.

Consider extracting the inline onClick handlers into named functions to avoid creating new function instances on each render:

export function PaymentPendingBanner({ listing }: PaymentPendingBannerProps) {
  const handleClearPaymentsClick = () => {
    posthog.capture('clear payments_sponsor stage banner', {
      stage: 'PAYMENT_PENDING',
      listing_type: listing.type,
      listing_slug: listing.slug,
    });
    posthog.capture('click_sponsor stage banner', {
      stage: 'PAYMENT_PENDING',
      listing_type: listing.type,
      listing_slug: listing.slug,
    });
  };

  const handleGetHelpClick = () => {
    posthog.capture('get help_sponsor stage banner', {
      stage: 'PAYMENT_PENDING',
      listing_type: listing.type,
      listing_slug: listing.slug,
    });
  };

  return (
    // ... use handleClearPaymentsClick and handleGetHelpClick
  );
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf5e544 and a5e9658.

📒 Files selected for processing (28)
  • src/app/api/homepage/sponsor-stage/route.ts (1 hunks)
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts (2 hunks)
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts (2 hunks)
  • src/features/announcements/components/SponsorAnnouncements.tsx (1 hunks)
  • src/features/home/components/SideBar.tsx (3 hunks)
  • src/features/home/components/SponsorFeatures.tsx (1 hunks)
  • src/features/home/components/SponsorResources.tsx (1 hunks)
  • src/features/home/components/SponsorStage/BoostBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/BoostedBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/NextListingBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx (1 hunks)
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx (1 hunks)
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx (1 hunks)
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx (1 hunks)
  • src/features/home/queries/sponsor-stage.ts (1 hunks)
  • src/features/home/types/sponsor-stage.ts (1 hunks)
  • src/features/listing-builder/components/Form/Boost/server-queries.ts (1 hunks)
  • src/features/listings/components/AddListingCard.tsx (1 hunks)
  • src/features/listings/components/ListingsSection.tsx (7 hunks)
  • src/features/listings/hooks/useListingState.ts (2 hunks)
  • src/features/navbar/components/Header.tsx (2 hunks)
  • src/pages/index.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/features/home/types/sponsor-stage.ts
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorFeatures.tsx
  • src/features/announcements/components/SponsorAnnouncements.tsx
  • src/features/home/components/SponsorResources.tsx
  • src/app/api/homepage/sponsor-stage/route.ts
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/listing-builder/components/Form/Boost/server-queries.ts
  • src/features/home/queries/sponsor-stage.ts
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx
  • src/pages/index.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/hooks/useListingState.ts
  • src/features/listings/components/ListingsSection.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/features/home/types/sponsor-stage.ts
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorFeatures.tsx
  • src/features/announcements/components/SponsorAnnouncements.tsx
  • src/features/home/components/SponsorResources.tsx
  • src/app/api/homepage/sponsor-stage/route.ts
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/listing-builder/components/Form/Boost/server-queries.ts
  • src/features/home/queries/sponsor-stage.ts
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx
  • src/pages/index.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/hooks/useListingState.ts
  • src/features/listings/components/ListingsSection.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/features/home/types/sponsor-stage.ts
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorFeatures.tsx
  • src/features/announcements/components/SponsorAnnouncements.tsx
  • src/features/home/components/SponsorResources.tsx
  • src/app/api/homepage/sponsor-stage/route.ts
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/listing-builder/components/Form/Boost/server-queries.ts
  • src/features/home/queries/sponsor-stage.ts
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx
  • src/pages/index.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/hooks/useListingState.ts
  • src/features/listings/components/ListingsSection.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
**/*.*

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

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

Files:

  • src/features/home/types/sponsor-stage.ts
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorFeatures.tsx
  • src/features/announcements/components/SponsorAnnouncements.tsx
  • src/features/home/components/SponsorResources.tsx
  • src/app/api/homepage/sponsor-stage/route.ts
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/listing-builder/components/Form/Boost/server-queries.ts
  • src/features/home/queries/sponsor-stage.ts
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx
  • src/pages/index.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/hooks/useListingState.ts
  • src/features/listings/components/ListingsSection.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/features/home/types/sponsor-stage.ts
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorFeatures.tsx
  • src/features/announcements/components/SponsorAnnouncements.tsx
  • src/features/home/components/SponsorResources.tsx
  • src/app/api/homepage/sponsor-stage/route.ts
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/listing-builder/components/Form/Boost/server-queries.ts
  • src/features/home/queries/sponsor-stage.ts
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx
  • src/pages/index.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/hooks/useListingState.ts
  • src/features/listings/components/ListingsSection.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorFeatures.tsx
  • src/features/announcements/components/SponsorAnnouncements.tsx
  • src/features/home/components/SponsorResources.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx
  • src/pages/index.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/components/ListingsSection.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
src/app/api/**/*.{js,ts}

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

src/app/api/**/*.{js,ts}: API endpoints in src/app/api must use the standard Next.js App Router route handler structure, exporting async functions for HTTP methods (e.g., GET, POST) with correct signatures.
When route handlers require route parameters, define the handler function to accept a props object with a params property (e.g., props: { params: Promise<{ id: string }> }).
Use getUserSession or getSponsorSession from '@/features/auth/utils' for session management in API endpoints, and check for session errors before proceeding.
Perform authorization checks (e.g., using checkGrantSponsorAuth) before executing protected operations in API endpoints, and return appropriate error responses if authorization fails.
Extract URL parameters using the URL API (e.g., new URL(request.url).searchParams) in API route handlers.
Parse request bodies using await request.json() and log the request body using logger.debug with safeStringify.
Return successful responses using NextResponse.json with a consistent structure (e.g., { data, message: 'Success' }) and status 200.
Return error responses using NextResponse.json with an error object and appropriate HTTP status code.
Always use the logger from '@/lib/logger' for logging debug, info, warning, and error messages in API endpoints.
Wrap main logic in API route handlers with try-catch blocks, log errors using logger.error, and return a 500 error response if an exception occurs.
Use waitUntil from '@vercel/functions' for non-blocking background tasks in API endpoints, and handle errors within the background task.
Validate incoming request data using Zod schemas and return a 400 error response with validation details if validation fails.
Implement response caching in API endpoints using Next.js built-in cache control headers (e.g., 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30').
Set route segment configuration such as maxDuration (e.g., export const maxDuration = 300) in API endpoint files as needed.
Organize A...

Files:

  • src/app/api/homepage/sponsor-stage/route.ts
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
src/app/api/**/*.{ts,tsx}

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

Use type-safe request and response handling in API endpoints.

Files:

  • src/app/api/homepage/sponsor-stage/route.ts
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
  • src/app/api/sponsor-dashboard/submissions/ai/commit-reviewed/route.ts
**/use*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/react-state-and-effects.mdc)

**/use*.{js,jsx,ts,tsx}: Design custom Hooks to encapsulate reusable logic with clear intent, focusing on concrete use cases and minimal configuration.
Name custom Hooks after their domain-specific purpose, not after generic lifecycle events or implementation details.
Each component calling a custom Hook should receive isolated state; custom Hooks should not share state between components.
Prefer exposing primitive values over complex objects when designing custom Hooks.
Custom Hooks should create composition chains where data flows between Hooks, enabling modular and reusable logic.
Custom Hooks should encapsulate complete logic for a concern (such as networking, state management, or subscriptions), not just partial or generic lifecycle logic.
Create custom Hooks around specific use cases rather than abstract concepts.
Use the state encapsulation, subscription, and side effect encapsulation patterns when implementing custom Hooks.

Files:

  • src/features/listings/hooks/useListingState.ts
🧠 Learnings (27)
📚 Learning: 2025-06-23T03:48:17.263Z
Learnt from: JayeshVP24
Repo: SuperteamDAO/earn PR: 1101
File: src/features/announcements/types/announcement.ts:7-11
Timestamp: 2025-06-23T03:48:17.263Z
Learning: In the Announcement interface (src/features/announcements/types/announcement.ts), the user JayeshVP24 prefers to keep the cta field with optional link and onClick properties rather than using a discriminated union. This flexible approach is intentional to allow for various CTA configurations.

Applied to files:

  • src/features/announcements/components/SponsorAnnouncements.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : Place new icon components in src/components/icons/

Applied to files:

  • src/features/home/components/SponsorResources.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/listings/components/AddListingCard.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
📚 Learning: 2025-07-22T13:07:34.937Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Use `withSponsorAuth` HOC for sponsor-specific API routes

Applied to files:

  • src/features/home/components/SponsorResources.tsx
  • src/app/api/homepage/sponsor-stage/route.ts
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/SponsorStageBanner.tsx
  • src/pages/index.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SideBar.tsx
  • src/features/listings/components/ListingsSection.tsx
📚 Learning: 2025-07-19T18:14:01.505Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-07-19T18:14:01.505Z
Learning: Applies to src/app/api/**/*.{js,ts} : API endpoints in src/app/api must use the standard Next.js App Router route handler structure, exporting async functions for HTTP methods (e.g., GET, POST) with correct signatures.

Applied to files:

  • src/app/api/homepage/sponsor-stage/route.ts
📚 Learning: 2025-07-22T13:07:34.937Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Always type the request with appropriate interface, such as `NextApiRequestWithUser` or `NextApiRequestWithSponsor`

Applied to files:

  • src/app/api/homepage/sponsor-stage/route.ts
📚 Learning: 2025-07-19T18:14:01.505Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-07-19T18:14:01.505Z
Learning: Applies to src/app/api/**/*.{js,ts} : Set route segment configuration such as maxDuration (e.g., export const maxDuration = 300) in API endpoint files as needed.

Applied to files:

  • src/app/api/homepage/sponsor-stage/route.ts
📚 Learning: 2025-07-19T18:14:01.505Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-07-19T18:14:01.505Z
Learning: Applies to src/app/api/**/*.{js,ts} : Keep route handlers focused and single-responsibility.

Applied to files:

  • src/app/api/homepage/sponsor-stage/route.ts
  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
📚 Learning: 2025-07-19T18:14:01.505Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-07-19T18:14:01.505Z
Learning: Applies to src/app/api/**/*.{js,ts} : Use getUserSession or getSponsorSession from '@/features/auth/utils' for session management in API endpoints, and check for session errors before proceeding.

Applied to files:

  • src/app/api/homepage/sponsor-stage/route.ts
  • src/pages/index.tsx
📚 Learning: 2025-06-24T03:45:32.567Z
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: Use Zod schemas for validating incoming request data in Next.js API routes to ensure type safety and provide clear error responses.

Applied to files:

  • src/app/api/sponsor-dashboard/listing/email-estimate/route.ts
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : File header must start with: /* eslint-disable prettier/prettier */ and import { GenIcon, type IconBaseProps } from '@/components/icons/helpers/GenIcon';

Applied to files:

  • src/features/listings/components/AddListingCard.tsx
  • src/features/navbar/components/Header.tsx
  • src/features/home/components/SponsorStage/BoostedBanner.tsx
📚 Learning: 2025-07-22T13:09:13.711Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-07-22T13:09:13.711Z
Learning: Applies to **/use*.{js,jsx,ts,tsx} : Create custom Hooks around specific use cases rather than abstract concepts.

Applied to files:

  • src/pages/index.tsx
📚 Learning: 2025-07-22T13:09:13.711Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-07-22T13:09:13.711Z
Learning: Applies to **/use*.{js,jsx,ts,tsx} : Design custom Hooks to encapsulate reusable logic with clear intent, focusing on concrete use cases and minimal configuration.

Applied to files:

  • src/pages/index.tsx
📚 Learning: 2025-06-24T03:45:48.987Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-06-24T03:45:48.987Z
Learning: Never suppress the React Hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps). Instead, refactor your code to avoid missing dependencies, often by using updater functions or restructuring logic.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-07-22T13:09:13.711Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-07-22T13:09:13.711Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : To reset all component state in React, use the key prop rather than useEffect.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-06-24T03:45:52.047Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-06-24T03:45:52.047Z
Learning: Never suppress the dependency linter for useEffect; instead, refactor your code (e.g., by using updater functions) to avoid missing dependencies.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-07-22T13:09:13.711Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-07-22T13:09:13.711Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-06-24T03:45:48.987Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-06-24T03:45:48.987Z
Learning: Best practices for React state and Effects: use updater functions when new state depends on previous state, prefer small and focused state variables, treat state as immutable, keep Effects focused on a single concern, extract repeated Effect logic into custom Hooks, use Effect Events for non-reactive code, never suppress the dependency linter, prefer calculation during rendering over Effects, always handle cleanup to prevent memory leaks, and create custom Hooks around specific use cases.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-07-22T13:09:13.711Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-07-22T13:09:13.711Z
Learning: Applies to **/use*.{js,jsx,ts,tsx} : Use the state encapsulation, subscription, and side effect encapsulation patterns when implementing custom Hooks.

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-06-24T03:45:52.047Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-06-24T03:45:52.047Z
Learning: Common custom Hook implementation patterns include: state encapsulation (returning value and handlers), subscription (setting up and cleaning up listeners in useEffect), and side effect encapsulation (e.g., updating document title with cleanup).

Applied to files:

  • src/features/listings/hooks/useListingState.ts
📚 Learning: 2025-06-24T03:46:07.639Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-06-24T03:46:07.639Z
Learning: Encapsulate data fetching and subscriptions in custom Hooks in React for better code reuse and separation of concerns.

Applied to files:

  • src/features/listings/components/ListingsSection.tsx
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : Consider using custom Hooks for data fetching and subscriptions in React.

Applied to files:

  • src/features/listings/components/ListingsSection.tsx
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.

Applied to files:

  • src/features/listings/components/ListingsSection.tsx
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.

Applied to files:

  • src/features/listings/components/ListingsSection.tsx
📚 Learning: 2025-06-24T03:46:07.639Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-06-24T03:46:07.639Z
Learning: For expensive calculations in React components, prefer useMemo over useEffect to avoid redundant state and unnecessary re-renders.

Applied to files:

  • src/features/listings/components/ListingsSection.tsx
📚 Learning: 2025-06-24T03:46:07.014Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-06-24T03:46:07.014Z
Learning: For data fetching in React, always implement proper cleanup in useEffect to prevent race conditions and memory leaks. Consider encapsulating data fetching logic in custom Hooks for better reuse and encapsulation.

Applied to files:

  • src/features/listings/components/ListingsSection.tsx
🧬 Code graph analysis (22)
src/features/home/types/sponsor-stage.ts (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/features/home/components/SponsorFeatures.tsx (2)
src/components/ui/carousel.tsx (4)
  • Carousel (247-247)
  • CarouselDots (250-250)
  • CarouselContent (249-249)
  • CarouselItem (251-251)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/app/api/homepage/sponsor-stage/route.ts (7)
src/features/listing-builder/components/Form/Boost/constants.ts (3)
  • BOOST_STEP_TO_AMOUNT_USD (4-9)
  • BOOST_STEPS (1-1)
  • DEFAULT_EMAIL_IMPRESSIONS (14-14)
src/features/listings/types/index.ts (4)
  • BountiesAi (168-178)
  • Listing (12-61)
  • Rewards (97-99)
  • ProjectApplicationAi (135-138)
src/features/listing-builder/components/Form/Boost/utils.ts (2)
  • amountToStep (28-45)
  • getTotalImpressionsForValue (141-153)
src/features/auth/utils/getUserSession.ts (1)
  • getUserSession (15-63)
src/prisma.ts (1)
  • prisma (41-41)
src/features/listing-builder/components/Form/Boost/server-queries.ts (2)
  • getFeaturedAvailability (76-89)
  • getEmailEstimate (14-74)
src/utils/safeStringify.ts (1)
  • safeStringify (4-16)
src/features/home/components/SponsorStage/ReviewBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/app/api/sponsor-dashboard/listing/email-estimate/route.ts (1)
src/features/listing-builder/components/Form/Boost/server-queries.ts (1)
  • getEmailEstimate (14-74)
src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (3)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/utils/dayjs.ts (1)
  • dayjs (28-28)
src/components/ui/button.tsx (1)
  • Button (58-58)
src/features/home/components/SponsorStage/BoostBanner.tsx (5)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/store/user.ts (1)
  • useUser (23-101)
src/features/listing-builder/components/Form/Boost/constants.ts (2)
  • BOOST_STEPS (1-1)
  • BOOST_STEP_TO_AMOUNT_USD (4-9)
src/features/listing-builder/components/Form/Boost/queries.ts (2)
  • featuredAvailabilityQuery (26-43)
  • emailEstimateQuery (45-62)
src/features/listing-builder/components/Form/Boost/utils.ts (3)
  • resolveEmailImpressions (101-108)
  • amountToStep (28-45)
  • getTotalImpressionsForValue (141-153)
src/features/home/components/SponsorStage/ReviewAiBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx (4)
src/store/user.ts (1)
  • useUser (23-101)
src/hooks/use-breakpoint.ts (1)
  • useBreakpoint (20-30)
src/features/home/queries/sponsor-stage.ts (1)
  • sponsorStageQuery (12-17)
src/utils/dayjs.ts (1)
  • dayjs (28-28)
src/features/home/components/SponsorStage/SponsorStageBanner.tsx (2)
src/hooks/use-breakpoint.ts (1)
  • useBreakpoint (20-30)
src/features/home/queries/sponsor-stage.ts (1)
  • sponsorStageQuery (12-17)
src/features/listings/components/AddListingCard.tsx (2)
src/store/user.ts (1)
  • useUser (23-101)
src/features/listings/utils/getListingIcon.tsx (1)
  • getListingIcon (36-50)
src/features/listing-builder/components/Form/Boost/server-queries.ts (2)
src/features/listings/utils/region.ts (1)
  • getCombinedRegion (4-42)
src/prisma.ts (1)
  • prisma (41-41)
src/features/home/queries/sponsor-stage.ts (1)
src/features/home/types/sponsor-stage.ts (1)
  • SponsorStageResponse (15-18)
src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx (2)
src/store/user.ts (1)
  • useUser (23-101)
src/features/home/queries/sponsor-stage.ts (1)
  • sponsorStageQuery (12-17)
src/pages/index.tsx (3)
src/store/user.ts (1)
  • useUser (23-101)
src/features/home/components/SponsorStage/SponsorStageBanner.tsx (1)
  • SponsorStageBanner (21-74)
src/features/home/components/UserStatsBanner.tsx (1)
  • UserStatsBanner (24-70)
src/features/home/components/SponsorStage/SponsorSidebarListing.tsx (8)
src/hooks/use-breakpoint.ts (1)
  • useBreakpoint (20-30)
src/features/home/queries/sponsor-stage.ts (1)
  • sponsorStageQuery (12-17)
src/features/listings/queries/submission-count.ts (1)
  • submissionCountQuery (12-19)
src/constants/tokenList.ts (1)
  • tokenList (11-474)
src/features/listings/utils/status.ts (1)
  • getListingStatus (26-79)
src/features/listings/utils/getColorStyles.ts (1)
  • getColorStyles (1-69)
src/features/listings/utils/getListingIcon.tsx (1)
  • getListingIcon (36-50)
src/features/sponsor-dashboard/components/SponsorPrize.tsx (1)
  • SponsorPrize (11-38)
src/features/navbar/components/Header.tsx (1)
src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx (1)
  • SponsorStageSnackbar (18-178)
src/features/home/components/SponsorStage/UnderVerificationBanner.tsx (2)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/store/user.ts (1)
  • useUser (23-101)
src/features/home/components/SideBar.tsx (6)
src/features/home/components/SponsorStage/SponsorWelcomeVideo.tsx (1)
  • SponsorWelcomeVideo (12-180)
src/features/home/components/SponsorStage/SponsorSidebarListing.tsx (1)
  • SponsorListing (29-411)
src/features/home/components/SponsorResources.tsx (1)
  • SponsorResources (4-58)
src/features/home/components/SponsorFeatures.tsx (1)
  • SponsorFeatures (48-114)
src/features/home/components/SponsorBanner.tsx (1)
  • SponsorBanner (10-40)
src/features/home/components/TotalStats.tsx (1)
  • TotalStats (6-49)
src/features/listings/components/ListingsSection.tsx (4)
src/hooks/use-breakpoint.ts (1)
  • useBreakpoint (20-30)
src/features/home/queries/sponsor-stage.ts (1)
  • sponsorStageQuery (12-17)
src/features/listings/components/AddListingCard.tsx (1)
  • AddListingCard (14-94)
src/components/shared/EmptySection.tsx (1)
  • EmptySection (5-26)
src/features/home/components/SponsorStage/BoostedBanner.tsx (2)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/store/user.ts (1)
  • useUser (23-101)
🔇 Additional comments (7)
src/features/listings/hooks/useListingState.ts (1)

96-96: LGTM! Removal of linter suppressions aligns with best practices.

The removal of eslint-disable-next-line react-hooks/exhaustive-deps comments is the correct approach. The code already uses functional state updates (setInternalActiveTab((currentInternalTab) => ...)) to safely access the latest state without including it in the dependency array, which prevents the effect from reverting optimistic updates.

Based on learnings.

Also applies to: 113-113

src/features/navbar/components/Header.tsx (1)

30-34: LGTM! SponsorStageSnackbar integration follows established patterns.

The dynamic import and unconditional rendering are appropriate since SponsorStageSnackbar handles its own visibility logic internally (homepage check, currentSponsorId check, viewport check, and stage filtering). This mirrors the patterns used for BountySnackbar and GrantSnackbar.

Also applies to: 163-163

src/features/announcements/components/SponsorAnnouncements.tsx (1)

36-36: Verify the intentional widening of the modal trigger condition.

The condition has been changed from !!user.user?.currentSponsor?.st to !!user.user?.currentSponsor, which removes the requirement for the st property to be truthy. This means the announcements modal will now display for all sponsors, not just those with a truthy st value.

Please confirm this behavior change is intentional and aligns with the sponsor-stage feature migration. If the st property is being deprecated in favor of the new sponsor-stage system, consider adding a comment explaining this transition.

src/pages/index.tsx (1)

10-10: LGTM! Sponsor-stage banner integration is well-structured.

The conditional rendering logic cleanly separates sponsor-specific UI (SponsorStageBanner) from user stats (UserStatsBanner) based on currentSponsorId and viewport size. The fragment wrapper and fallback behavior are appropriate.

Also applies to: 18-18, 64-64, 91-99

src/features/home/queries/sponsor-stage.ts (1)

7-17: LGTM! Query configuration follows best practices.

The fetchSponsorStage function properly declares its return type, and the sponsorStageQuery configuration uses appropriate stale and garbage collection times (5 and 10 minutes respectively) for sponsor-stage data that doesn't change frequently.

src/features/home/components/SideBar.tsx (1)

89-138: Sponsor gating looks solid
The way you branch the sidebar content on isSponsor keeps the sponsor experience isolated without regressing the existing flow. Nicely structured.

src/features/home/components/SponsorStage/SponsorStageSnackbar.tsx (1)

142-144: No issues found—dayjs ordinal format is properly configured.

The advancedFormat plugin is already loaded and extended in src/utils/dayjs.ts, which provides full support for the 'Do' format token. The code will format dates correctly as "1st Jan", "2nd Feb", etc.

Comment on lines +465 to +520
const validatedSkills = skillsArraySchema.parse(listing.skills);
if (validatedSkills.length > 0) {
const sortedSkills = validatedSkills.sort().join(',');
const cacheKey = `${sortedSkills}|${listing.region || 'GLOBAL'}`;

if (!seenEstimates.has(cacheKey)) {
seenEstimates.add(cacheKey);
estimatePromises.push(
getEmailEstimate(validatedSkills, listing.region)
.then((estimate) => {
emailEstimateCache.set(cacheKey, estimate);
})
.catch((error) => {
logger.warn('Failed to get email estimate', {
skills: validatedSkills,
region: listing.region,
error:
error instanceof Error
? error.message
: 'Unknown error',
});
emailEstimateCache.set(cacheKey, DEFAULT_EMAIL_IMPRESSIONS);
}),
);
}
}
} catch (error) {}
}
}

await Promise.all(estimatePromises);
const emailEstimateEndTime = performance.now();

const processListing = (listing: BountyWithStageFields) => {
const hasUnpaidWinners = unpaidWinnersMap.get(listing.id) || false;
const hasUncommittedProjectAiReviews =
uncommittedProjectAiReviewsMap.get(listing.id) || false;

let emailImpressions = DEFAULT_EMAIL_IMPRESSIONS;
if (listing.skills && Array.isArray(listing.skills)) {
try {
const validatedSkills = skillsArraySchema.parse(listing.skills);
if (validatedSkills.length > 0) {
const sortedSkills = validatedSkills.sort().join(',');
const cacheKey = `${sortedSkills}|${listing.region || 'GLOBAL'}`;

const estimate = emailEstimateCache.get(cacheKey);
if (estimate !== undefined && estimate > 0) {
const roundedEstimate = Math.round(estimate / 1000) * 1000;
emailImpressions = Math.max(
roundedEstimate,
DEFAULT_EMAIL_IMPRESSIONS,
);
}
}
} catch (error) {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix skills cache key collisions when computing email estimates

validatedSkills.sort().join(',') sorts an array of objects without a comparator, so every entry stringifies to [object Object]. Different skill/subskill combinations therefore collapse to the same cache key, letting one listing’s email estimate leak into unrelated listings and distorting BOOST vs BOOSTED decisions. Build the cache key from the actual skill/subskill values instead.

Apply this diff to generate a stable, unique key per skill set:

-            const sortedSkills = validatedSkills.sort().join(',');
-            const cacheKey = `${sortedSkills}|${listing.region || 'GLOBAL'}`;
+            const sortedSkillsKey = validatedSkills
+              .map((skill) => {
+                const sortedSubskills = [...(skill.subskills ?? [])]
+                  .sort()
+                  .join('|');
+                return `${skill.skills}:${sortedSubskills}`;
+              })
+              .sort()
+              .join(',');
+            const cacheKey = `${sortedSkillsKey}|${listing.region || 'GLOBAL'}`;
-            const sortedSkills = validatedSkills.sort().join(',');
-            const cacheKey = `${sortedSkills}|${listing.region || 'GLOBAL'}`;
+            const sortedSkillsKey = validatedSkills
+              .map((skill) => {
+                const sortedSubskills = [...(skill.subskills ?? [])]
+                  .sort()
+                  .join('|');
+                return `${skill.skills}:${sortedSubskills}`;
+              })
+              .sort()
+              .join(',');
+            const cacheKey = `${sortedSkillsKey}|${listing.region || 'GLOBAL'}`;

Comment on lines +1 to +12
import Autoplay from 'embla-carousel-autoplay';
import Image from 'next/image';
import Link from 'next/link';
import { useRef } from 'react';

import { Button } from '@/components/ui/button';
import {
Carousel,
CarouselContent,
CarouselDots,
CarouselItem,
} from '@/components/ui/carousel';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add 'use client' for this carousel.

useRef and the Embla carousel plugin only work in a Client Component. Without the directive the bundler treats the file as server code and the build breaks.

+'use client';
+
 import Autoplay from 'embla-carousel-autoplay';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Autoplay from 'embla-carousel-autoplay';
import Image from 'next/image';
import Link from 'next/link';
import { useRef } from 'react';
import { Button } from '@/components/ui/button';
import {
Carousel,
CarouselContent,
CarouselDots,
CarouselItem,
} from '@/components/ui/carousel';
'use client';
import Autoplay from 'embla-carousel-autoplay';
import Image from 'next/image';
import Link from 'next/link';
import { useRef } from 'react';
import { Button } from '@/components/ui/button';
import {
Carousel,
CarouselContent,
CarouselDots,
CarouselItem,
} from '@/components/ui/carousel';
🤖 Prompt for AI Agents
In src/features/home/components/SponsorFeatures.tsx lines 1-12, this file uses
useRef and the Embla autoplay plugin which require a React Client Component; add
the "use client" directive as the very first line of the file to mark it as a
client component so the bundler treats it correctly and the carousel can access
browser-only APIs.

Comment on lines +12 to +25
<Link href="#" className="flex items-start gap-4">
<div className="flex h-14 w-18 flex-shrink-0 items-center justify-center rounded-md bg-[#E7D3FF]">
<Sparkles className="h-6 w-6 text-[#321D89]" />
</div>
<div className="flex min-w-0 flex-1 flex-col gap-1">
<h3 className="text-sm font-medium text-slate-600">
Getting Started
</h3>
<p className="text-sm leading-[1.2] text-slate-500">
A quick start guide to creating your first Bounty or Project
listing
</p>
</div>
</Link>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace placeholder resource link.

Linking to # leaves the "Getting Started" card non-functional. Wire this up to the real onboarding URL (or drop the item until the link exists) so sponsors can actually reach the resource.

🤖 Prompt for AI Agents
In src/features/home/components/SponsorResources.tsx around lines 12 to 25, the
Link currently points to "#" leaving the "Getting Started" card non-functional;
update the href to the real onboarding/onboarding docs URL (e.g., the app's
onboarding route or external docs URL pulled from your routes/constants), or
remove the card entirely until the upstream content exists; ensure you
import/use the canonical route constant if available and keep Link semantics
intact so the card navigates correctly for sponsors.

Comment on lines +150 to +153
src={user?.currentSponsor?.logo ?? ''}
alt={user?.currentSponsor?.name ?? ''}
className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid empty sponsor-logo src here too
If the sponsor hasn’t set a logo, this <img> again resolves to src="", producing a broken image plus an unnecessary request for the current page. Please add the same guard/fallback here as in the other banner.

Use this diff to conditionally render the logo:

-        <img
-          src={user?.currentSponsor?.logo ?? ''}
-          alt={user?.currentSponsor?.name ?? ''}
-          className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
-        />
+        {user?.currentSponsor?.logo && (
+          <img
+            src={user.currentSponsor.logo}
+            alt={user.currentSponsor.name ?? 'Sponsor logo'}
+            className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
+          />
+        )}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/BoostBanner.tsx around lines 150 to
153, the <img> uses src={user?.currentSponsor?.logo ?? ''} which can render as
an empty string and cause a broken image/request; change it to conditionally
render the <img> only when user?.currentSponsor?.logo is a non-empty string (or
use a truthy check), and otherwise omit the <img> or render a fallback (e.g.,
null or placeholder) so no empty src is emitted; ensure alt text still uses the
sponsor name when rendering.

Comment on lines +1 to +12
import { Rocket } from 'lucide-react';

import { useUser } from '@/store/user';

import { type Listing } from '@/features/listings/types';

interface BoostedBannerProps {
listing: Listing;
}

export function BoostedBanner({ listing }: BoostedBannerProps) {
const { user } = useUser();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add the client component directive.

This component calls useUser, so the module must be marked as a Client Component. Without 'use client' the build fails when Next.js treats it as a Server Component.

+'use client';
+
 import { Rocket } from 'lucide-react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Rocket } from 'lucide-react';
import { useUser } from '@/store/user';
import { type Listing } from '@/features/listings/types';
interface BoostedBannerProps {
listing: Listing;
}
export function BoostedBanner({ listing }: BoostedBannerProps) {
const { user } = useUser();
'use client';
import { Rocket } from 'lucide-react';
import { useUser } from '@/store/user';
import { type Listing } from '@/features/listings/types';
interface BoostedBannerProps {
listing: Listing;
}
export function BoostedBanner({ listing }: BoostedBannerProps) {
const { user } = useUser();
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/BoostedBanner.tsx around lines 1 to
12, this module uses the useUser hook but lacks the Client Component directive,
causing Next.js to treat it as a Server Component and fail; add the directive
'use client' as the very first line of the file (above all imports) so
React/Next will compile it as a client component and the useUser hook will work
correctly.

Comment on lines +3 to +13
export enum SponsorStage {
NEW_SPONSOR = 'NEW_SPONSOR',
BOOST = 'BOOST',
BOOSTED = 'BOOSTED',
REVIEW_AI = 'REVIEW_AI',
REVIEW = 'REVIEW',
REVIEW_URGENT = 'REVIEW_URGENT',
PAYMENT_PENDING = 'PAYMENT_PENDING',
NEXT_LISTING = 'NEXT_LISTING',
UNDER_VERIFICATION = 'UNDER_VERIFICATION',
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace enum with as const object to align with coding guidelines.

The codebase guidelines explicitly state: "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."

Apply this diff to replace the enum with an as const object:

-export enum SponsorStage {
-  NEW_SPONSOR = 'NEW_SPONSOR',
-  BOOST = 'BOOST',
-  BOOSTED = 'BOOSTED',
-  REVIEW_AI = 'REVIEW_AI',
-  REVIEW = 'REVIEW',
-  REVIEW_URGENT = 'REVIEW_URGENT',
-  PAYMENT_PENDING = 'PAYMENT_PENDING',
-  NEXT_LISTING = 'NEXT_LISTING',
-  UNDER_VERIFICATION = 'UNDER_VERIFICATION',
-}
+export const SponsorStage = {
+  NEW_SPONSOR: 'NEW_SPONSOR',
+  BOOST: 'BOOST',
+  BOOSTED: 'BOOSTED',
+  REVIEW_AI: 'REVIEW_AI',
+  REVIEW: 'REVIEW',
+  REVIEW_URGENT: 'REVIEW_URGENT',
+  PAYMENT_PENDING: 'PAYMENT_PENDING',
+  NEXT_LISTING: 'NEXT_LISTING',
+  UNDER_VERIFICATION: 'UNDER_VERIFICATION',
+} as const;
+
+export type SponsorStage = typeof SponsorStage[keyof typeof SponsorStage];

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export enum SponsorStage {
NEW_SPONSOR = 'NEW_SPONSOR',
BOOST = 'BOOST',
BOOSTED = 'BOOSTED',
REVIEW_AI = 'REVIEW_AI',
REVIEW = 'REVIEW',
REVIEW_URGENT = 'REVIEW_URGENT',
PAYMENT_PENDING = 'PAYMENT_PENDING',
NEXT_LISTING = 'NEXT_LISTING',
UNDER_VERIFICATION = 'UNDER_VERIFICATION',
}
export const SponsorStage = {
NEW_SPONSOR: 'NEW_SPONSOR',
BOOST: 'BOOST',
BOOSTED: 'BOOSTED',
REVIEW_AI: 'REVIEW_AI',
REVIEW: 'REVIEW',
REVIEW_URGENT: 'REVIEW_URGENT',
PAYMENT_PENDING: 'PAYMENT_PENDING',
NEXT_LISTING: 'NEXT_LISTING',
UNDER_VERIFICATION: 'UNDER_VERIFICATION',
} as const;
export type SponsorStage = typeof SponsorStage[keyof typeof SponsorStage];
🤖 Prompt for AI Agents
In src/features/home/types/sponsor-stage.ts around lines 3 to 13, replace the
exported enum SponsorStage with an exported const object using string literal
values and a readonly type; specifically, remove the enum declaration and create
export const SponsorStage = { NEW_SPONSOR: 'NEW_SPONSOR', BOOST: 'BOOST',
BOOSTED: 'BOOSTED', REVIEW_AI: 'REVIEW_AI', REVIEW: 'REVIEW', REVIEW_URGENT:
'REVIEW_URGENT', PAYMENT_PENDING: 'PAYMENT_PENDING', NEXT_LISTING:
'NEXT_LISTING', UNDER_VERIFICATION: 'UNDER_VERIFICATION' } as const, then export
a type SponsorStage = typeof SponsorStage[keyof typeof SponsorStage] so callers
can use the same string-union type without introducing a new enum.

Comment on lines +15 to +18
export interface SponsorStageResponse {
stage: SponsorStage | null;
listing: Listing | null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use readonly properties in the interface.

According to the coding guidelines: "Use readonly properties for object types by default. This will prevent accidental mutation at runtime."

Apply this diff:

 export interface SponsorStageResponse {
-  stage: SponsorStage | null;
-  listing: Listing | null;
+  readonly stage: SponsorStage | null;
+  readonly listing: Listing | null;
 }

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface SponsorStageResponse {
stage: SponsorStage | null;
listing: Listing | null;
}
export interface SponsorStageResponse {
readonly stage: SponsorStage | null;
readonly listing: Listing | null;
}
🤖 Prompt for AI Agents
In src/features/home/types/sponsor-stage.ts around lines 15 to 18, the
SponsorStageResponse interface uses mutable properties; update the interface to
mark both properties as readonly (readonly stage: SponsorStage | null; readonly
listing: Listing | null;) so the type enforces immutability per the coding
guidelines.

Comment on lines +3 to +7
import {
type ParentSkills,
type skillsArraySchema,
skillSubSkillMap,
} from '@/interface/skills';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix type-only import of skillsArraySchema.

skillsArraySchema is needed as a value for the typeof inference, so importing it with type causes a compile-time error.

 import {
   type ParentSkills,
-  type skillsArraySchema,
+  skillsArraySchema,
   skillSubSkillMap,
 } from '@/interface/skills';
🤖 Prompt for AI Agents
In src/features/listing-builder/components/Form/Boost/server-queries.ts around
lines 3 to 7, the import marks skillsArraySchema as a type-only import but it is
used as a value for typeof inference; remove the type-only qualifier so
skillsArraySchema is imported as a runtime value (i.e., import it without the
"type" keyword) while keeping ParentSkills and skillSubSkillMap as type imports
if desired, then rebuild to ensure the compile-time error is resolved.

Comment on lines +67 to +69
...(regionObject?.country && {
location: { in: countries },
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Avoid filtering by an empty country list.

When regionObject.country exists but is an empty array (e.g. Global), Prisma receives { location: { in: [] } }, which returns zero results. Guard on countries.length before adding the filter.

-      ...(regionObject?.country && {
+      ...(countries.length > 0 && {
         location: { in: countries },
       }),
🤖 Prompt for AI Agents
In src/features/listing-builder/components/Form/Boost/server-queries.ts around
lines 67 to 69, the current spread adds location: { in: countries } even when
countries is an empty array, causing Prisma to return zero results; change the
condition to only add the location filter when regionObject?.country is truthy
AND countries.length > 0 so the filter is omitted for an empty country list
(e.g., Global).

Comment on lines +49 to +57
<Link
href={`/s/${sponsorSlug}`}
onClick={(e) => e.stopPropagation()}
className="flex w-min items-center gap-1 hover:underline"
>
<p className="text-xs whitespace-nowrap text-slate-500 md:text-sm">
{sponsorName}
</p>
</Link>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove nested anchors in AddListingCard

Link renders an <a> element. Placing another Link (and thus another <a>) inside it generates invalid HTML and breaks keyboard navigation/screen-reader semantics. Replace the inner Link with a non-anchor element (e.g., a button that navigates via window.open) so the sponsor name remains clickable without nesting anchors.

Apply this diff to remove the nested anchor while keeping the sponsor link behavior:

-            {sponsorSlug ? (
-              <Link
-                href={`/s/${sponsorSlug}`}
-                onClick={(e) => e.stopPropagation()}
-                className="flex w-min items-center gap-1 hover:underline"
-              >
-                <p className="text-xs whitespace-nowrap text-slate-500 md:text-sm">
-                  {sponsorName}
-                </p>
-              </Link>
-            ) : (
+            {sponsorSlug ? (
+              <button
+                type="button"
+                onClick={(e) => {
+                  e.stopPropagation();
+                  window.open(`/s/${sponsorSlug}`, '_self');
+                }}
+                className="flex w-min items-center gap-1 text-left hover:underline"
+              >
+                <p className="text-xs whitespace-nowrap text-slate-500 md:text-sm">
+                  {sponsorName}
+                </p>
+              </button>
+            ) : (

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/features/listings/components/AddListingCard.tsx around lines 49 to 57,
the inner Link creates a nested anchor which is invalid and breaks
accessibility; replace the inner Link with a non-anchor element (e.g., a button
or span) that preserves styling and onClick behavior: keep the href target
(build `/s/${sponsorSlug}`), keep e.stopPropagation(), and perform navigation
imperatively (use next/router push or window.open) inside the onClick; also add
keyboard support (onKeyDown handling Enter/Space) and appropriate accessible
attributes (role="link", tabIndex={0}) so the sponsor name remains clickable and
keyboard/screen-reader friendly without nesting anchors.

Copy link
Copy Markdown
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/CreateListingModal.tsx (1)

21-21: Remove unused hackathons prop.

The hackathons prop is no longer used after removing the hackathon creation functionality.

-  hackathons?: HackathonModel[];
🧹 Nitpick comments (3)
src/features/listing-builder/components/AutoGenerate/Type.tsx (1)

90-112: Consider removing commented code or using a feature flag.

The commented-out Cypherpunk hackathon option adds maintenance overhead. If this feature is being permanently removed, delete the code. If it's temporarily disabled pending reactivation, consider using a feature flag instead:

-        {/*   <Button
-          className="col-span-2 flex h-55 flex-col gap-4 whitespace-normal text-slate-500 hover:text-slate-500"
-          variant="outline"
-          onClick={() => {
-            setType('hackathon');
-            setHackathonSlug('cypherpunk');
-          }}
-        >
-          <CypherpunkLogo
-            styles={{
-              width: '12rem',
-            }}
-          />
-          <span className="flex max-w-6/12 flex-col gap-1">
-            <h3 className="text-base font-medium text-slate-900">
-              Cypherpunk Sidetrack
-            </h3>
-            <p className="text-sm font-normal">
-              Get developers participating in Cypherpunk to build on top of your
-              project
-            </p>
-          </span>
-        </Button> */}
+        {ENABLE_CYPHERPUNK_HACKATHON && (
+          <Button
+            className="col-span-2 flex h-55 flex-col gap-4 whitespace-normal text-slate-500 hover:text-slate-500"
+            variant="outline"
+            onClick={() => {
+              setType('hackathon');
+              setHackathonSlug('cypherpunk');
+            }}
+          >
+            {/* ...rest of button content... */}
+          </Button>
+        )}
src/features/sponsor-dashboard/components/CreateListingModal.tsx (2)

41-44: Remove commented code.

Commented code can be recovered from version control if needed. Keeping it in the codebase reduces readability.

-  // const handleCreateHackathon = (hackathon: string) => {
-  //   posthog.capture('create new hackathon_sponsor');
-  //   router.push(`/dashboard/new?type=hackathon&hackathon=${hackathon}`);
-  // };

131-152: Remove commented UI block.

The commented Button block for hackathon creation should be removed to keep the codebase clean. Version control preserves this code if it needs to be restored later.

-                {/* <Button
-                  className="col-span-2 flex h-55 flex-col gap-4 whitespace-normal text-slate-500 hover:text-slate-500"
-                  variant="outline"
-                  onClick={() => {
-                    handleCreateHackathon('cypherpunk');
-                  }}
-                >
-                  <CypherpunkLogo
-                    styles={{
-                      width: '12rem',
-                    }}
-                  />
-                  <span className="flex max-w-6/12 flex-col gap-1">
-                    <h3 className="text-base font-medium text-slate-900">
-                      Cypherpunk Sidetrack
-                    </h3>
-                    <p className="text-sm font-normal">
-                      Get developers participating in Cypherpunk to build on top
-                      of your project
-                    </p>
-                  </span>
-                </Button> */}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5e9658 and 8fd4556.

📒 Files selected for processing (2)
  • src/features/listing-builder/components/AutoGenerate/Type.tsx (3 hunks)
  • src/features/sponsor-dashboard/components/CreateListingModal.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.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/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.tsx
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.tsx
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Use `withSponsorAuth` HOC for sponsor-specific API routes
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : File header must start with: /* eslint-disable prettier/prettier */ and import { GenIcon, type IconBaseProps } from '@/components/icons/helpers/GenIcon';

Applied to files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
  • src/features/listing-builder/components/AutoGenerate/Type.tsx
📚 Learning: 2025-07-19T18:14:01.505Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-07-19T18:14:01.505Z
Learning: Applies to src/app/api/**/*.{js,ts} : Keep route handlers focused and single-responsibility.

Applied to files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
📚 Learning: 2025-07-22T13:07:34.937Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Use `withSponsorAuth` HOC for sponsor-specific API routes

Applied to files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
📚 Learning: 2025-07-28T13:48:45.118Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/motion.mdc:0-0
Timestamp: 2025-07-28T13:48:45.118Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : For React projects, always import Motion from 'motion/react' instead of 'framer-motion'.

Applied to files:

  • src/features/sponsor-dashboard/components/CreateListingModal.tsx
📚 Learning: 2025-07-22T13:09:31.699Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/return-types.mdc:0-0
Timestamp: 2025-07-22T13:09:31.699Z
Learning: Applies to **/*.tsx : No need to declare the return type of a component which returns JSX.

Applied to files:

  • src/features/listing-builder/components/AutoGenerate/Type.tsx
📚 Learning: 2025-07-19T18:14:27.763Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/import-type.mdc:0-0
Timestamp: 2025-07-19T18:14:27.763Z
Learning: Applies to **/*.{ts,tsx} : Prefer top-level `import type` over inline `import { type ... }`.

Applied to files:

  • src/features/listing-builder/components/AutoGenerate/Type.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : Component template: const ComponentName = (props: IconBaseProps) => GenIcon({/* JSON */})(props); export default ComponentName;

Applied to files:

  • src/features/listing-builder/components/AutoGenerate/Type.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : Place new icon components in src/components/icons/

Applied to files:

  • src/features/listing-builder/components/AutoGenerate/Type.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : Use a default export for each icon component

Applied to files:

  • src/features/listing-builder/components/AutoGenerate/Type.tsx
🔇 Additional comments (2)
src/features/listing-builder/components/AutoGenerate/Type.tsx (1)

39-39: Note: setHackathonSlug still required by interface but unused.

While setHackathonSlug is commented out here, the AutoGenerateTypeProps interface at line 34 still requires callers to provide it. This is consistent with the commented-out Cypherpunk hackathon UI (lines 90-112) that would have used it. If this is a temporary change, the current approach is reasonable. Otherwise, consider creating a separate interface for AutoGenerateStageType that omits unused properties.

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

57-57: The review comment is incorrect.

max-h-svh is a valid built-in Tailwind CSS utility class that was added in v3.4 (December 2023) and generates max-height: 100svh. The project uses Tailwind CSS v4.1.16, which is well after this utility became available. The change from max-h-[100svh] to max-h-svh is an improvement, as it uses the proper built-in utility instead of an arbitrary value workaround. No changes are needed.

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown
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: 5

Caution

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

⚠️ Outside diff range comments (1)
src/features/home/components/Banner/TalentBanner.tsx (1)

1-6: Add 'use client' directive.

This component calls posthog.capture in an event handler (line 58), which requires client-side execution. Without the 'use client' directive, this will fail at runtime.

Apply this diff:

+'use client';
+
 import { getImageProps } from 'next/image';
 import posthog from 'posthog-js';
♻️ Duplicate comments (5)
src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1)

9-11: Use readonly property in the interface.

The listing property should be marked as readonly to prevent accidental mutation at runtime.

Apply this diff:

 interface PaymentPendingBannerProps {
-  listing: Listing;
+  readonly listing: Listing;
 }

As per coding guidelines.

src/features/home/components/SponsorStage/BoostBanner.tsx (1)

160-162: Guard against empty sponsor logo sources
Rendering <img src=""> when the sponsor lacks a logo still causes a broken image icon and an unnecessary request for the current page. Please gate the render on a truthy logo (or supply a real fallback image) so we never emit an empty src.

-        <img
-          src={user?.currentSponsor?.logo ?? ''}
-          alt={user?.currentSponsor?.name ?? ''}
-          className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
-        />
+        {user?.currentSponsor?.logo ? (
+          <img
+            src={user.currentSponsor.logo}
+            alt={user.currentSponsor.name ?? 'Sponsor logo'}
+            className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
+          />
+        ) : null}
src/features/home/components/SponsorStage/NewSponsorBanner.tsx (1)

1-7: Add 'use client' directive.

This component uses PostHog event tracking, which requires client-side execution. The missing directive will cause build or runtime failures.

Apply this diff:

+'use client';
+
 import { PenLine } from 'lucide-react';
src/features/home/components/SponsorStage/ReviewBanner.tsx (1)

53-56: Guard against invalid commitmentDate.

Formatting listing.commitmentDate without validation will display "Invalid Date" when the field is missing or malformed. Add a check to maintain copy quality.

Apply this diff:

             <p className="max-w-sm text-base text-slate-500">
-              Gain credibility by announcing winners and paying them before your
-              commitment date, {dayjs(listing.commitmentDate).format('Do MMM')}.
+              {listing.commitmentDate && dayjs(listing.commitmentDate).isValid()
+                ? `Gain credibility by announcing winners and paying them before your commitment date, ${dayjs(listing.commitmentDate).format('Do MMM')}.`
+                : 'Gain credibility by announcing winners and paying them before your commitment date.'}
             </p>
src/features/home/components/SponsorStage/UnderVerificationBanner.tsx (1)

104-108: Conditionally render the sponsor logo.

When user?.currentSponsor?.logo is undefined, the <img> renders with src="", causing the browser to request the current page and display a broken image. Conditionally render the image only when a logo exists.

Apply this diff:

-          <img
-            src={user?.currentSponsor?.logo ?? ''}
-            alt={user?.currentSponsor?.name ?? ''}
-            className="relative z-10 size-28 rounded-full border border-slate-400"
-          />
+          {user?.currentSponsor?.logo && (
+            <img
+              src={user.currentSponsor.logo}
+              alt={user.currentSponsor.name ?? 'Sponsor logo'}
+              className="relative z-10 size-28 rounded-full border border-slate-400"
+            />
+          )}
🧹 Nitpick comments (1)
src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1)

73-78: Consider using Next.js Image component for optimization.

The component uses regular img tags for images. The Next.js Image component provides automatic optimization, lazy loading, and responsive image handling.

For line 73-78:

+import Image from 'next/image';
+
 <Link
   href="https://t.me/pratikdholani/"
   onClick={(e) => {
     e.stopPropagation();
     handleGetHelpClick();
   }}
 >
-  <img
+  <Image
     src="/assets/sponsor/pratik.webp"
     alt="Get Help"
     width={28}
     height={28}
   />
   <span>Get Help</span>
 </Link>

Similarly for the sponsor logos image at lines 89-93.

Also applies to: 89-93

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd4556 and 07bcd9f.

📒 Files selected for processing (9)
  • src/features/home/components/Banner/TalentBanner.tsx (3 hunks)
  • src/features/home/components/SponsorStage/BoostBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/NextListingBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
**/*.*

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

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

Files:

  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/features/home/components/SponsorStage/ReviewAiBanner.tsx
  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
  • src/features/home/components/SponsorStage/UnderVerificationBanner.tsx
  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
  • src/features/home/components/SponsorStage/ReviewBanner.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Use `withSponsorAuth` HOC for sponsor-specific API routes
📚 Learning: 2025-06-23T03:48:17.263Z
Learnt from: JayeshVP24
Repo: SuperteamDAO/earn PR: 1101
File: src/features/announcements/types/announcement.ts:7-11
Timestamp: 2025-06-23T03:48:17.263Z
Learning: In the Announcement interface (src/features/announcements/types/announcement.ts), the user JayeshVP24 prefers to keep the cta field with optional link and onClick properties rather than using a discriminated union. This flexible approach is intentional to allow for various CTA configurations.

Applied to files:

  • src/features/home/components/Banner/TalentBanner.tsx
📚 Learning: 2025-07-22T13:07:34.937Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Use `withAuth` HOC for basic user authentication in protected API routes

Applied to files:

  • src/features/home/components/Banner/TalentBanner.tsx
📚 Learning: 2025-07-22T13:07:34.937Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/pages-api-guidelines.mdc:0-0
Timestamp: 2025-07-22T13:07:34.937Z
Learning: Applies to src/pages/api/**/* : Use `withSponsorAuth` HOC for sponsor-specific API routes

Applied to files:

  • src/features/home/components/Banner/TalentBanner.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
📚 Learning: 2025-07-22T13:09:19.488Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/readonly-properties.mdc:0-0
Timestamp: 2025-07-22T13:09:19.488Z
Learning: Applies to **/*.{ts,tsx} : Use `readonly` properties for object types by default. This will prevent accidental mutation at runtime.

Applied to files:

  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
📚 Learning: 2025-07-22T13:09:19.488Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/readonly-properties.mdc:0-0
Timestamp: 2025-07-22T13:09:19.488Z
Learning: Applies to **/*.{ts,tsx} : Omit `readonly` only when the property is genuinely mutable.

Applied to files:

  • src/features/home/components/SponsorStage/PaymentPendingBanner.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : Place new icon components in src/components/icons/

Applied to files:

  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : File header must start with: /* eslint-disable prettier/prettier */ and import { GenIcon, type IconBaseProps } from '@/components/icons/helpers/GenIcon';

Applied to files:

  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
🧬 Code graph analysis (6)
src/features/home/components/SponsorStage/ReviewAiBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/features/home/components/Banner/TalentBanner.tsx (1)
src/features/auth/components/AuthWrapper.tsx (1)
  • AuthWrapper (32-141)
src/features/home/components/SponsorStage/PaymentPendingBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/features/home/components/SponsorStage/UnderVerificationBanner.tsx (2)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/store/user.ts (1)
  • useUser (23-101)
src/features/home/components/SponsorStage/BoostBanner.tsx (5)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/store/user.ts (1)
  • useUser (23-101)
src/features/listing-builder/components/Form/Boost/constants.ts (2)
  • BOOST_STEPS (1-1)
  • BOOST_STEP_TO_AMOUNT_USD (4-9)
src/features/listing-builder/components/Form/Boost/queries.ts (2)
  • featuredAvailabilityQuery (26-43)
  • emailEstimateQuery (45-62)
src/features/listing-builder/components/Form/Boost/utils.ts (3)
  • resolveEmailImpressions (101-108)
  • amountToStep (28-45)
  • getTotalImpressionsForValue (141-153)
src/features/home/components/SponsorStage/ReviewBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
🔇 Additional comments (1)
src/features/home/components/Banner/TalentBanner.tsx (1)

55-103: LGTM!

The refactored structure correctly delegates authentication handling to the outer AuthWrapper while tracking user intent via PostHog. The inner button serves as a visual CTA without its own handler, which is the expected pattern.

Comment on lines +13 to +97
export function PaymentPendingBanner({ listing }: PaymentPendingBannerProps) {
const handleClearPaymentsClick = () => {
posthog.capture('clear payments_sponsor stage banner', {
stage: 'PAYMENT_PENDING',
listing_type: listing.type,
listing_slug: listing.slug,
});
posthog.capture('click_sponsor stage banner', {
stage: 'PAYMENT_PENDING',
listing_type: listing.type,
listing_slug: listing.slug,
});
};

const handleGetHelpClick = () => {
posthog.capture('get help_sponsor stage banner', {
stage: 'PAYMENT_PENDING',
listing_type: listing.type,
listing_slug: listing.slug,
});
};

return (
<Link
href={`/dashboard/listings/${listing.slug}/submissions?tab=payments`}
className="relative flex items-center justify-between overflow-hidden rounded-xl bg-slate-100 px-10 py-8"
onClick={handleClearPaymentsClick}
prefetch={false}
>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
<div className="flex h-6 w-6 items-center justify-center rounded-lg">
<TriangleAlert className="h-6 w-6 text-slate-600" strokeWidth={2} />
</div>

<div className="flex flex-col gap-2">
<h2 className="text-xl font-semibold text-slate-700">
Please clear your payment
</h2>
<p className="max-w-md text-base text-slate-500">
You need to pay or add your transaction links for the payments
made to the winners of your latest {listing.type}
</p>
</div>
</div>

<div className="flex items-center gap-8">
<Button>Clear Payments</Button>
<Button
variant="ghost"
asChild
className="flex items-center gap-3 text-sm text-slate-400 underline underline-offset-4 hover:text-slate-700"
>
<Link
href="https://t.me/pratikdholani/"
onClick={(e) => {
e.stopPropagation();
handleGetHelpClick();
}}
>
<img
src="/assets/sponsor/pratik.webp"
alt="Get Help"
width={28}
height={28}
/>
<span>Get Help</span>
</Link>
</Button>
</div>
</div>

<div className="absolute -right-26 -bottom-32 hidden xl:block">
<span className="block size-100 rounded-full bg-indigo-100/80" />
</div>
<div className="absolute top-2/4 right-10 hidden w-49 -translate-y-1/2 xl:block">
<img
src="https://res.cloudinary.com/dgvnuwspr/image/upload/v1761684250/assets/home/sponsor-stages/logos.webp"
alt="Sponsor logos"
className="object-contain object-right"
/>
</div>
</Link>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null checks for optional Listing properties.

The component accesses listing.type and listing.slug without null/undefined checks, but these are optional properties in the Listing interface. This could cause:

  • Runtime errors when constructing the href (line 37)
  • Invalid analytics data (lines 17-18, 22-23, 30-31)
  • Displaying "undefined" in the UI (line 54)

Consider adding a guard at the component level or defensive checks:

 export function PaymentPendingBanner({ listing }: PaymentPendingBannerProps) {
+  if (!listing.slug || !listing.type) {
+    return null;
+  }
+
   const handleClearPaymentsClick = () => {

Alternatively, ensure that the parent component only renders this banner when listing.slug and listing.type are defined.

🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/PaymentPendingBanner.tsx around
lines 13 to 97, the component reads listing.type and listing.slug (used in the
href, analytics events, and UI) without guarding for undefined; add a defensive
guard so the banner only renders when listing?.slug and listing?.type are
present (or return null/placeholder), and use safe fallback values when
capturing analytics or building URLs (e.g., skip capture or use 'unknown' for
missing fields) to avoid runtime errors, invalid analytics, and rendering
"undefined" in the UI.

Comment on lines +1 to +7
import { Sparkles } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';

import { Button } from '@/components/ui/button';

import { type Listing } from '@/features/listings/types';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add 'use client' directive.

This component relies on PostHog for analytics tracking, which requires client-side JavaScript. Add the directive to prevent build failures.

Apply this diff:

+'use client';
+
 import { Sparkles } from 'lucide-react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Sparkles } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';
import { Button } from '@/components/ui/button';
import { type Listing } from '@/features/listings/types';
'use client';
import { Sparkles } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';
import { Button } from '@/components/ui/button';
import { type Listing } from '@/features/listings/types';
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/ReviewAiBanner.tsx lines 1-7, this
component uses PostHog (client-only) but is missing the React Server Components
client directive; add the single line "use client" at the very top of the file
(before any imports) to force client-side rendering so PostHog and other
browser-only APIs work without build failures.

Comment on lines +58 to +61
<p className="max-w-sm text-base text-slate-500">
Use AI to quickly review your{' '}
{label[listing.type as keyof typeof label]} and save tens of hours
in review time
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against undefined listing.type.

listing.type is optional, so accessing label[listing.type as keyof typeof label] without a null check can render an incomplete sentence when type is missing. Provide a fallback to ensure the copy remains coherent.

Apply this diff:

             <p className="max-w-sm text-base text-slate-500">
               Use AI to quickly review your{' '}
-              {label[listing.type as keyof typeof label]} and save tens of hours
-              in review time
+              {listing.type ? label[listing.type as keyof typeof label] : 'submissions'} and save tens of hours in review time
             </p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p className="max-w-sm text-base text-slate-500">
Use AI to quickly review your{' '}
{label[listing.type as keyof typeof label]} and save tens of hours
in review time
<p className="max-w-sm text-base text-slate-500">
Use AI to quickly review your{' '}
{listing.type ? label[listing.type as keyof typeof label] : 'submissions'} and save tens of hours in review time
</p>
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/ReviewAiBanner.tsx around lines 58
to 61, the code accesses label[listing.type as keyof typeof label] without
guarding for a missing listing.type which can produce an incomplete sentence;
update the rendering to compute a safe display value by checking listing?.type
(or typeof listing.type === 'string') and using it to index label when present,
otherwise fall back to a default string (e.g., a generic noun like "listing" or
"item") and render that fallback so the sentence remains coherent when type is
undefined.

Comment on lines +1 to +8
import { PartyPopper } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';

import { Button } from '@/components/ui/button';
import { dayjs } from '@/utils/dayjs';

import { type Listing } from '@/features/listings/types';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add 'use client' directive.

PostHog analytics require client-side execution. Without this directive, event tracking will fail.

Apply this diff:

+'use client';
+
 import { PartyPopper } from 'lucide-react';
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/ReviewBanner.tsx lines 1-8, the
file uses posthog (client-side analytics) but lacks the "use client" directive;
add a top-level "use client" directive as the very first line of the file so the
module runs on the client and PostHog event tracking works correctly.

Comment on lines +1 to +8
import { ClockFading, ScanSearch } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';

import { Button } from '@/components/ui/button';
import { useUser } from '@/store/user';

import { type Listing } from '@/features/listings/types';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add 'use client' directive.

This component uses both the useUser React hook and PostHog analytics, both of which require client-side execution. The missing directive will cause failures.

Apply this diff:

+'use client';
+
 import { ClockFading, ScanSearch } from 'lucide-react';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { ClockFading, ScanSearch } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';
import { Button } from '@/components/ui/button';
import { useUser } from '@/store/user';
import { type Listing } from '@/features/listings/types';
'use client';
import { ClockFading, ScanSearch } from 'lucide-react';
import Link from 'next/link';
import posthog from 'posthog-js';
import { Button } from '@/components/ui/button';
import { useUser } from '@/store/user';
import { type Listing } from '@/features/listings/types';
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/UnderVerificationBanner.tsx lines
1-8, this component uses the useUser hook and posthog (client-only APIs) but
lacks the "use client" directive; add the line "use client" as the very first
line of the file (before any imports) so React treats this file as a client
component and the hook/analytics calls run on the client.

Copy link
Copy Markdown
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: 2

🧹 Nitpick comments (1)
src/features/listings/utils/query-builder.ts (1)

241-279: Consider extracting the region capitalization logic to reduce duplication.

The pattern region.charAt(0).toUpperCase() + region.slice(1) appears in both line 246 and line 274. Consider extracting this to a helper function for better maintainability.

Example refactor:

function capitalizeRegion(region: string): string {
  return region.charAt(0).toUpperCase() + region.slice(1);
}

Then use it in both locations:

           region: {
-            in: [
-              region.charAt(0).toUpperCase() + region.slice(1),
+            in: [
+              capitalizeRegion(region),
               ...(st?.country || []),
             ],
           },
         region: {
-          in: [region.charAt(0).toUpperCase() + region.slice(1), 'Global'],
+          in: [capitalizeRegion(region), 'Global'],
         },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a45c47 and 3838bd6.

📒 Files selected for processing (9)
  • src/app/api/dynamic-og/region/route.tsx (1 hunks)
  • src/app/sitemap.ts (9 hunks)
  • src/features/home/components/CountryBanner.tsx (1 hunks)
  • src/features/home/utils/regions.ts (1 hunks)
  • src/features/listings/utils/query-builder.ts (2 hunks)
  • src/layouts/Home.tsx (4 hunks)
  • src/pages/regions/[slug]/all.tsx (1 hunks)
  • src/pages/regions/[slug]/index.tsx (1 hunks)
  • src/utils/json-ld.ts (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, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/app/api/dynamic-og/region/route.tsx
  • src/features/home/components/CountryBanner.tsx
  • src/layouts/Home.tsx
  • src/utils/json-ld.ts
  • src/features/listings/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/app/sitemap.ts
  • src/features/home/utils/regions.ts
src/app/api/**/*.{ts,tsx}

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

Use type-safe request and response handling in API endpoints.

Files:

  • src/app/api/dynamic-og/region/route.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/app/api/dynamic-og/region/route.tsx
  • src/features/home/components/CountryBanner.tsx
  • src/layouts/Home.tsx
  • src/utils/json-ld.ts
  • src/features/listings/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/app/sitemap.ts
  • src/features/home/utils/regions.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/app/api/dynamic-og/region/route.tsx
  • src/features/home/components/CountryBanner.tsx
  • src/layouts/Home.tsx
  • src/utils/json-ld.ts
  • src/features/listings/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/app/sitemap.ts
  • src/features/home/utils/regions.ts
**/*.*

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

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

Files:

  • src/app/api/dynamic-og/region/route.tsx
  • src/features/home/components/CountryBanner.tsx
  • src/layouts/Home.tsx
  • src/utils/json-ld.ts
  • src/features/listings/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/app/sitemap.ts
  • src/features/home/utils/regions.ts
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/app/api/dynamic-og/region/route.tsx
  • src/features/home/components/CountryBanner.tsx
  • src/layouts/Home.tsx
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/app/api/dynamic-og/region/route.tsx
  • src/features/home/components/CountryBanner.tsx
  • src/layouts/Home.tsx
  • src/utils/json-ld.ts
  • src/features/listings/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/app/sitemap.ts
  • src/features/home/utils/regions.ts
🧠 Learnings (2)
📚 Learning: 2025-07-19T18:14:01.505Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/app-api-guidelines.mdc:0-0
Timestamp: 2025-07-19T18:14:01.505Z
Learning: Applies to src/app/api/**/*.{js,ts} : Use getUserSession or getSponsorSession from '@/features/auth/utils' for session management in API endpoints, and check for session errors before proceeding.

Applied to files:

  • src/utils/json-ld.ts
📚 Learning: 2025-06-24T03:45:32.567Z
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: Use Zod schemas for validating incoming request data in Next.js API routes to ensure type safety and provide clear error responses.

Applied to files:

  • src/features/listings/utils/query-builder.ts
🧬 Code graph analysis (7)
src/features/home/components/CountryBanner.tsx (1)
src/components/shared/UserFlag.tsx (1)
  • UserFlag (36-87)
src/layouts/Home.tsx (2)
src/constants/Superteam.ts (1)
  • Superteam (41-53)
src/features/home/components/CountryBanner.tsx (1)
  • CountryBanner (11-39)
src/features/listings/utils/query-builder.ts (1)
src/constants/country.ts (1)
  • countries (3-2486)
src/pages/regions/[slug]/all.tsx (6)
src/constants/Superteam.ts (2)
  • Superteam (41-53)
  • Superteams (57-440)
src/utils/validUrl.ts (1)
  • getURL (1-11)
src/utils/json-ld.ts (2)
  • generateRegionalOrganizationSchema (188-203)
  • generateBreadcrumbListSchema (391-408)
src/layouts/Home.tsx (1)
  • Home (54-140)
src/components/shared/JsonLd.tsx (1)
  • JsonLd (11-27)
src/features/home/utils/regions.ts (1)
  • findCountryBySlug (32-49)
src/pages/regions/[slug]/index.tsx (5)
src/constants/Superteam.ts (2)
  • Superteam (41-53)
  • Superteams (57-440)
src/utils/validUrl.ts (1)
  • getURL (1-11)
src/utils/json-ld.ts (2)
  • generateRegionalOrganizationSchema (188-203)
  • generateBreadcrumbListSchema (391-408)
src/layouts/Home.tsx (1)
  • Home (54-140)
src/features/home/utils/regions.ts (1)
  • findCountryBySlug (32-49)
src/app/sitemap.ts (1)
src/features/home/utils/regions.ts (1)
  • getAllRegionSlugs (51-62)
src/features/home/utils/regions.ts (2)
src/constants/Superteam.ts (1)
  • Superteams (57-440)
src/constants/country.ts (1)
  • countries (3-2486)
🔇 Additional comments (2)
src/features/listings/utils/query-builder.ts (2)

3-3: LGTM!

The import of the countries constant is appropriate for the new regional fallback logic.


241-279: The sponsor filter asymmetry is intentional and aligns with the data model.

The logic correctly differentiates because Superteam regions have associated sponsor names (the name field from Superteams constant), while regular countries do not. The region filter differences are therefore justified:

  • Superteam case: Filters by both region (capitalized input + country array) and sponsor name—this is correct since Superteams have explicit sponsor associations
  • Country case: Filters by region only (country name + 'Global')—correct since countries have no sponsor mappings

The fallback case, however, passes an unverified capitalized region string to the database. If region is invalid or doesn't match any database entry, this silently falls back to searching by a non-existent region paired with 'Global'. Consider validating the region or logging when it doesn't match known Superteams or countries.

Comment thread src/features/home/utils/regions.ts Outdated
Comment on lines +11 to +49
function getSuperteamCodes(): readonly string[] {
return Superteams.map((st) => st.code.toUpperCase());
}

export function getEligibleCountries() {
const superteamCodes = getSuperteamCodes();

return countries.filter((country) => {
if (country.iso === true) {
const countryCodeUpper = country.code.toUpperCase();
return !superteamCodes.includes(countryCodeUpper);
}

if (country.region === true) {
return true;
}

return false;
});
}

export function findCountryBySlug(slug: string) {
const normalizedSlug = slug.toLowerCase();

const superteam = Superteams.find(
(st) => st.slug?.toLowerCase() === normalizedSlug,
);
if (superteam) {
return null;
}

const eligibleCountries = getEligibleCountries();
return (
eligibleCountries.find((country) => {
const countrySlug = generateSlug(country.name);
return countrySlug === normalizedSlug;
}) || null
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit return types to the region helpers

Our guidelines require top-level functions to declare their return types. getSuperteamCodes, getEligibleCountries, and findCountryBySlug currently lean on inference, which makes the API surface fuzzier and risks unintended type widening later on. Please lock these down explicitly. Here's one way to do it:

+type Country = (typeof countries)[number];
+
-function getSuperteamCodes() {
+function getSuperteamCodes(): readonly string[] {
   return Superteams.map((st) => st.code.toUpperCase());
 }
 
-export function getEligibleCountries() {
+export function getEligibleCountries(): ReadonlyArray<Country> {
   const superteamCodes = getSuperteamCodes();
 
   return countries.filter((country) => {
@@
   });
 }
 
-export function findCountryBySlug(slug: string) {
+export function findCountryBySlug(slug: string): Country | null {
   const normalizedSlug = slug.toLowerCase();
@@
   return (
     eligibleCountries.find((country) => {
       const countrySlug = generateSlug(country.name);
       return countrySlug === normalizedSlug;
     }) || null
   );
 }

As per coding guidelines

🤖 Prompt for AI Agents
In src/features/home/utils/regions.ts around lines 11 to 49, the three top-level
helpers lack explicit return types; add precise return annotations to lock the
API: annotate getSuperteamCodes() as returning readonly string[], annotate
getEligibleCountries() to return the array type used by countries (e.g.
Country[]), and annotate findCountryBySlug(slug: string) to return Country |
null; update the function signatures accordingly (exported ones included) so the
compiler enforces the intended types.

Comment on lines +102 to +104
<div className="w-full">
<ListingsSection type="region-all" region={countryName} />
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the region filter keyed by slug

ListingsSection still filters listings by the region slug (see the st.region branch just above). Passing the human-readable countryName here will send the wrong key downstream and the region-all feed will come back empty for country-derived pages. Please pass the normalized slug instead of the display name so the data fetch continues to work.

Apply this diff:

-          <ListingsSection type="region-all" region={countryName} />
+          <ListingsSection
+            type="region-all"
+            region={slug.toLowerCase()}
+          />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="w-full">
<ListingsSection type="region-all" region={countryName} />
</div>
<div className="w-full">
<ListingsSection
type="region-all"
region={slug.toLowerCase()}
/>
</div>
🤖 Prompt for AI Agents
In src/pages/regions/[slug]/all.tsx around lines 102 to 104, ListingsSection is
being passed the human-readable countryName which causes the region filter to
use the wrong key; replace countryName with the normalized region slug (e.g.
slug) so the downstream st.region branch receives the correct key; if a local
slug variable doesn't exist in this scope, read the route param (params.slug or
router.query.slug) and pass that normalized slug into ListingsSection.

Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
src/app/sitemap.ts (1)

55-57: Optional: Remove unnecessary async keyword.

Since getAllRegionSlugs() returns synchronously, the async keyword is technically unnecessary. However, keeping it allows uniform usage with Promise.all() in generateSitemaps() and getSitemapBoundaries().

If you prefer strict correctness, you could change this to:

-async function getRegionsCount(): Promise<number> {
-  return getAllRegionSlugs().length;
-}
+function getRegionsCount(): number {
+  return getAllRegionSlugs().length;
+}

Then update the Promise.all() calls to handle the synchronous return value.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3838bd6 and d4b7164.

📒 Files selected for processing (2)
  • src/app/sitemap.ts (8 hunks)
  • src/features/navbar/components/Footer.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/app/sitemap.ts
  • src/features/navbar/components/Footer.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/app/sitemap.ts
  • src/features/navbar/components/Footer.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/app/sitemap.ts
  • src/features/navbar/components/Footer.tsx
**/*.*

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

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

Files:

  • src/app/sitemap.ts
  • src/features/navbar/components/Footer.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/app/sitemap.ts
  • src/features/navbar/components/Footer.tsx
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/navbar/components/Footer.tsx
🧠 Learnings (3)
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : File header must start with: /* eslint-disable prettier/prettier */ and import { GenIcon, type IconBaseProps } from '@/components/icons/helpers/GenIcon';

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-06-25T12:03:40.223Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1123
File: src/pages/_app.tsx:58-58
Timestamp: 2025-06-25T12:03:40.223Z
Learning: The Next.js documentation officially recommends using key={router.asPath} on page components to force remounting and reset state on navigation. This is documented in the Next.js useRouter API reference as one of two official approaches (the other being useEffect with route dependencies). This pattern ensures clean state transitions between routes and is a legitimate architectural choice despite the performance implications of forced remounting.

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-06-25T12:03:40.223Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1123
File: src/pages/_app.tsx:58-58
Timestamp: 2025-06-25T12:03:40.223Z
Learning: Next.js recommends using key={router.asPath} on components to reset state after navigation when you need fresh state on route changes. This is documented in the Next.js useRouter API reference. While this pattern has performance implications (forced remounting), it's a legitimate approach for ensuring clean state transitions.

Applied to files:

  • src/features/navbar/components/Footer.tsx
🧬 Code graph analysis (2)
src/app/sitemap.ts (1)
src/features/home/utils/regions.ts (1)
  • getAllRegionSlugs (51-62)
src/features/navbar/components/Footer.tsx (2)
src/features/home/utils/regions.ts (2)
  • findCountryBySlug (32-49)
  • generateSlug (4-9)
src/components/shared/RegionCombobox.tsx (1)
  • RegionCombobox (102-333)
🔇 Additional comments (5)
src/app/sitemap.ts (1)

341-364: LGTM! Regions sitemap pagination implemented correctly.

The implementation follows the established pattern for other sitemap sections with proper offset calculation and boundary checking. The defensive empty-array check on line 349 is good practice.

Note: The priority was increased from 0.7 to 0.8, aligning regions closer to grants (0.8) rather than sponsors (0.7).

src/features/navbar/components/Footer.tsx (4)

6-6: LGTM!

The imports are correctly structured and align with the refactoring to region-based selection.

Also applies to: 12-12


55-55: LGTM!

State initialization is clean and the default value of 'Global' is appropriate.


57-82: Region detection logic is correct.

The useEffect appropriately synchronizes with the router (external system), handles all three cases (Superteam regions, country slugs, and default Global), and has the correct dependency array.


188-196: LGTM!

RegionCombobox usage is correct with appropriate props and conditional styling. The global, superteams, and regions flags properly configure the available options.

Comment on lines +84 to 102
const handleRegionChange = (value: string): void => {
if (value === 'Global') {
setSelectedRegion('Global');
router.push('/');
return;
}
setSelectedSuperteam(value);
const team = Superteams.find((t) => t.name === value);
if (team?.slug) {
router.push(`/regions/${team.slug.toLowerCase()}`);

setSelectedRegion(value);

// Check if it's a Superteam region
const superteam = Superteams.find((t) => t.region === value);
if (superteam?.slug) {
router.push(`/regions/${superteam.slug.toLowerCase()}`);
} else {
router.push('/');
// It's a country or region, generate slug from the name
const slug = generateSlug(value);
router.push(`/regions/${slug}`);
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix type mismatch with RegionCombobox onChange.

The function signature expects value: string, but RegionCombobox's onChange prop can pass string | undefined (see relevant code snippets). This type mismatch will cause TypeScript errors and potential runtime issues if undefined is passed.

Apply this diff to handle the undefined case:

-  const handleRegionChange = (value: string): void => {
+  const handleRegionChange = (value: string | undefined): void => {
+    if (!value) {
+      return;
+    }
+
     if (value === 'Global') {
       setSelectedRegion('Global');
       router.push('/');
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleRegionChange = (value: string): void => {
if (value === 'Global') {
setSelectedRegion('Global');
router.push('/');
return;
}
setSelectedSuperteam(value);
const team = Superteams.find((t) => t.name === value);
if (team?.slug) {
router.push(`/regions/${team.slug.toLowerCase()}`);
setSelectedRegion(value);
// Check if it's a Superteam region
const superteam = Superteams.find((t) => t.region === value);
if (superteam?.slug) {
router.push(`/regions/${superteam.slug.toLowerCase()}`);
} else {
router.push('/');
// It's a country or region, generate slug from the name
const slug = generateSlug(value);
router.push(`/regions/${slug}`);
}
};
const handleRegionChange = (value: string | undefined): void => {
if (!value) {
return;
}
if (value === 'Global') {
setSelectedRegion('Global');
router.push('/');
return;
}
setSelectedRegion(value);
// Check if it's a Superteam region
const superteam = Superteams.find((t) => t.region === value);
if (superteam?.slug) {
router.push(`/regions/${superteam.slug.toLowerCase()}`);
} else {
// It's a country or region, generate slug from the name
const slug = generateSlug(value);
router.push(`/regions/${slug}`);
}
};
🤖 Prompt for AI Agents
In src/features/navbar/components/Footer.tsx around lines 84 to 102, the handler
currently accepts value: string but RegionCombobox may pass string | undefined;
update the function signature to accept value: string | undefined and guard for
undefined at the top (e.g., if value === undefined) returning early (or setting
a sensible default) before using it, then keep the existing logic for 'Global'
and region/superteam routing; this ensures TypeScript correctness and avoids
runtime errors when undefined is passed.

Copy link
Copy Markdown
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: 3

♻️ Duplicate comments (2)
src/features/navbar/components/Footer.tsx (1)

87-105: Update handleRegionChange to accept string | undefined to match RegionCombobox and guard falsy values.

Per the RegionCombobox props, onChange may receive string | undefined. handleRegionChange currently assumes string, which can lead to a type error and, at runtime, to selectedRegion being set to undefined if RegionCombobox ever calls onChange with an unset value.

Adjusting the signature and guarding for a falsy value keeps this handler safe and consistent with the combobox:

-  const handleRegionChange = (value: string): void => {
-    if (value === 'Global') {
+  const handleRegionChange = (value: string | undefined): void => {
+    if (!value) {
+      return;
+    }
+
+    if (value === 'Global') {
       setSelectedRegion('Global');
       router.push('/');
       return;
     }
@@
-    setSelectedRegion(value);
+    setSelectedRegion(value);

The rest of the logic (superteam vs. country/region routing) can remain as-is.

Also applies to: 191-199

src/pages/regions/[slug]/all.tsx (1)

66-107: ListingsSection in country path should receive slug, not country name

This branch still passes the human‑readable countryName into ListingsSection as the region key, while the Superteam branch above passes st.region (slug-like). Previous feedback noted that the listings filter uses the region slug; using the display name here is likely to produce empty feeds on country-derived /all pages. Please pass a normalized slug instead (e.g. slug.toLowerCase()).

-        <div className="w-full">
-          <ListingsSection type="region-all" region={countryName} />
-        </div>
+        <div className="w-full">
+          <ListingsSection
+            type="region-all"
+            region={slug.toLowerCase()}
+          />
+        </div>
🧹 Nitpick comments (7)
src/features/listings/utils/region.ts (2)

2-2: Slugging and eligible-region discovery are sound; consider deduping slugs and tightening typing.

generateSlug, getEligibleCountries, findCountryBySlug, and getAllRegionSlugs fit together cleanly and match how the footer and sitemap use region slugs. Two small improvements you might want to make:

  • getAllRegionSlugs can emit duplicate slugs if a superteam and a region/country share the same slug; deduping would avoid duplicate URLs in consumers like the sitemap.
  • For getEligibleCountries, an explicit return type (e.g., an array of the same element type as countries) would make the API clearer and align with the top‑level function typing guideline.

A minimal change for deduplication in getAllRegionSlugs:

 export function getAllRegionSlugs(): readonly string[] {
   const superteamSlugs = Superteams.map((st) => st.slug).filter(
     (slug): slug is string => typeof slug === 'string',
   );

   const eligibleCountries = getEligibleCountries();
   const countrySlugs = eligibleCountries.map((country) =>
     generateSlug(country.name),
   );

-  return [...superteamSlugs, ...countrySlugs];
+  return Array.from(new Set([...superteamSlugs, ...countrySlugs]));
 }

Also applies to: 137-195


197-349: Region set helpers are coherent; add minor guards and simplify redundant branches.

The region-set helpers (getMultiCountryRegionsContainingCountry, getSuperteamRegionsContainingCountries, getRegionsFor*Page, getRegionsForUserLocation) are consistent with the countries/Superteams structures and give you nicely normalized region lists with Global included.

A few low-risk robustness/cleanup tweaks you might consider:

  • Guard .some calls on st.country so future Superteam entries without a country array can’t cause runtime errors.
  • In getRegionsForUserLocation, the if/else around userRegion.country currently pushes the same userRegion.name in both branches and can be simplified.

Example adjustments:

 function getSuperteamRegionsContainingCountries(
   countryNames: string[],
 ): string[] {
-  return Superteams.filter((st) =>
-    st.country.some((countryName) => countryNames.includes(countryName)),
-  ).map((st) => st.region);
+  return Superteams.filter(
+    (st) =>
+      Array.isArray(st.country) &&
+      st.country.some((countryName) => countryNames.includes(countryName)),
+  ).map((st) => st.region);
 }

 export function getRegionsForUserLocation(
   userLocation: string | null,
 ): string[] {
@@
   const regions: string[] = ['Global'];

   regions.push(userLocation);

-  if (userRegion.country && Array.isArray(userRegion.country)) {
-    regions.push(userRegion.name);
-  } else {
-    regions.push(userRegion.name);
-  }
+  regions.push(userRegion.name);
@@
-  const multiCountryRegionsFromSuperteams = Superteams.filter((st) =>
-    st.country.some(
-      (countryName) => countryName.toLowerCase() === userLocation.toLowerCase(),
-    ),
-  ).map((st) => st.region);
+  const multiCountryRegionsFromSuperteams = Superteams.filter(
+    (st) =>
+      Array.isArray(st.country) &&
+      st.country.some(
+        (countryName) =>
+          countryName.toLowerCase() === userLocation.toLowerCase(),
+      ),
+  ).map((st) => st.region);

These keep behavior the same given current data while making the helpers safer and slightly easier to read.

src/features/grants/utils/query-builder.ts (1)

3-4: Region-aware grants queries are correctly wired to the new utilities.

The grants query builder now cleanly delegates user-location handling to getRegionsForUserLocation and, for context === 'region', correctly distinguishes:

  • Superteam regions (via Superteams.find + getRegionsForSuperteamPage),
  • Multi-country regions (country.region && country.regionsgetRegionsForMultiCountryRegionPage),
  • Regular countries (getRegionsForCountryPage),
  • And a reasonable fallback for unknown regions.

This keeps grants-region behavior aligned with the listings side and the new region helpers. If you see this logic evolving further, you might eventually extract a shared “resolveRegionFilter(region: string)” helper to avoid the grants/listings branches drifting, but it’s not urgent.

Also applies to: 13-17, 66-67, 95-136

src/features/listings/utils/query-builder.ts (1)

3-3: Listing queries now use shared region helpers; region context logic is consistent and expressive.

The listings query builder’s region handling is now in line with the new region utilities:

  • User-based filtering delegates to getRegionsForUserLocation, keeping “For You” and home/all views aligned with the same region sets used elsewhere.
  • For context === 'region' | 'region-all', you correctly:
    • Treat Superteam regions specially (OR on region in getRegionsForSuperteamPage(...) or sponsor.name === st.name),
    • Distinguish multi-country regions from regular countries using country.region && country.regions,
    • And provide a sensible fallback [CapitalizedRegion, 'Global'] when no match is found.

The AND/OR nesting matches your existing Prisma-style query structure. As with the grants builder, this is a good candidate for a shared “resolve region filter” helper in the future to avoid keeping two nearly identical branches in sync, but it’s fine to defer that.

Also applies to: 19-23, 150-152, 227-299

src/pages/regions/[slug]/all.tsx (2)

3-23: Consolidate region page props and consider a discriminated union

AllRegionsPageProps mirrors RegionsPageProps in index.tsx and uses two optional props (st?, countryData?). This allows (type-wise) both or neither to be present, even though GSSP guarantees exactly one at runtime. Consider extracting a shared CountryData type and modelling the props as a discriminated union (e.g. st branch vs countryData branch) to encode that invariant in the type system and avoid the “bag of optionals” pattern.


30-63: Region Superteam branch looks sound; minor copy tweak for consistency

The Superteam (st) branch wiring (OG image, JSON-LD, and ListingsSection with region={st.region}) is consistent with the main region page and should behave correctly. The only nit is the description string using Welcome to Superteam ${displayName} while the index page uses Superteam Earn ${displayName}; consider aligning the copy for consistent branding across region pages.

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

16-27: Avoid duplicated props shapes and optional‑bag pattern

RegionsPageProps is effectively the same as AllRegionsPageProps in all.tsx, again using st? and countryData?. To reduce duplication and make the invariant “exactly one of st or countryData” explicit, consider extracting a shared CountryData type and using a discriminated union for the region page props in both files. That will make it harder to accidentally construct invalid props in future refactors.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4b7164 and 0addfce.

📒 Files selected for processing (8)
  • src/app/api/grants/route.ts (1 hunks)
  • src/app/sitemap.ts (8 hunks)
  • src/features/grants/utils/query-builder.ts (4 hunks)
  • src/features/listings/utils/query-builder.ts (4 hunks)
  • src/features/listings/utils/region.ts (2 hunks)
  • src/features/navbar/components/Footer.tsx (3 hunks)
  • src/pages/regions/[slug]/all.tsx (1 hunks)
  • src/pages/regions/[slug]/index.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/app/api/grants/route.ts
  • src/features/navbar/components/Footer.tsx
  • src/features/listings/utils/query-builder.ts
  • src/features/grants/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/features/listings/utils/region.ts
  • src/app/sitemap.ts
src/app/api/**/*.{js,ts}

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

src/app/api/**/*.{js,ts}: API endpoints in src/app/api must use the standard Next.js App Router route handler structure, exporting async functions for HTTP methods (e.g., GET, POST) with correct signatures.
When route handlers require route parameters, define the handler function to accept a props object with a params property (e.g., props: { params: Promise<{ id: string }> }).
Use getUserSession or getSponsorSession from '@/features/auth/utils' for session management in API endpoints, and check for session errors before proceeding.
Perform authorization checks (e.g., using checkGrantSponsorAuth) before executing protected operations in API endpoints, and return appropriate error responses if authorization fails.
Extract URL parameters using the URL API (e.g., new URL(request.url).searchParams) in API route handlers.
Parse request bodies using await request.json() and log the request body using logger.debug with safeStringify.
Return successful responses using NextResponse.json with a consistent structure (e.g., { data, message: 'Success' }) and status 200.
Return error responses using NextResponse.json with an error object and appropriate HTTP status code.
Always use the logger from '@/lib/logger' for logging debug, info, warning, and error messages in API endpoints.
Wrap main logic in API route handlers with try-catch blocks, log errors using logger.error, and return a 500 error response if an exception occurs.
Use waitUntil from '@vercel/functions' for non-blocking background tasks in API endpoints, and handle errors within the background task.
Validate incoming request data using Zod schemas and return a 400 error response with validation details if validation fails.
Implement response caching in API endpoints using Next.js built-in cache control headers (e.g., 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30').
Set route segment configuration such as maxDuration (e.g., export const maxDuration = 300) in API endpoint files as needed.
Organize A...

Files:

  • src/app/api/grants/route.ts
src/app/api/**/*.{ts,tsx}

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

Use type-safe request and response handling in API endpoints.

Files:

  • src/app/api/grants/route.ts
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/app/api/grants/route.ts
  • src/features/navbar/components/Footer.tsx
  • src/features/listings/utils/query-builder.ts
  • src/features/grants/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/features/listings/utils/region.ts
  • src/app/sitemap.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/app/api/grants/route.ts
  • src/features/navbar/components/Footer.tsx
  • src/features/listings/utils/query-builder.ts
  • src/features/grants/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/features/listings/utils/region.ts
  • src/app/sitemap.ts
**/*.*

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

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

Files:

  • src/app/api/grants/route.ts
  • src/features/navbar/components/Footer.tsx
  • src/features/listings/utils/query-builder.ts
  • src/features/grants/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/features/listings/utils/region.ts
  • src/app/sitemap.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/app/api/grants/route.ts
  • src/features/navbar/components/Footer.tsx
  • src/features/listings/utils/query-builder.ts
  • src/features/grants/utils/query-builder.ts
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
  • src/features/listings/utils/region.ts
  • src/app/sitemap.ts
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/navbar/components/Footer.tsx
  • src/pages/regions/[slug]/all.tsx
  • src/pages/regions/[slug]/index.tsx
🧠 Learnings (7)
📚 Learning: 2025-06-24T03:45:18.658Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/optional-properties.mdc:0-0
Timestamp: 2025-06-24T03:45:18.658Z
Learning: In TypeScript, use optional properties only when the property is truly optional. Overusing optional properties (e.g., 'userId?: string') can lead to bugs if a required value is accidentally omitted. Instead, prefer using a required property with an explicit 'undefined' type (e.g., 'userId: string | undefined') when you always want the property present but its value may be undefined. This approach makes it clear when a property is intentionally unset and helps prevent accidental omissions.

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-07-22T13:07:13.765Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/optional-properties.mdc:0-0
Timestamp: 2025-07-22T13:07:13.765Z
Learning: Applies to **/*.{ts,tsx} : Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer `userId: string | undefined` over `userId?: string` when the property should always be present but may be undefined.

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-07-19T18:14:18.278Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/discriminated-unions.mdc:0-0
Timestamp: 2025-07-19T18:14:18.278Z
Learning: Applies to **/*.{ts,tsx} : Use switch statements to handle the results of discriminated unions.

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : File header must start with: /* eslint-disable prettier/prettier */ and import { GenIcon, type IconBaseProps } from '@/components/icons/helpers/GenIcon';

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-06-25T12:03:40.223Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1123
File: src/pages/_app.tsx:58-58
Timestamp: 2025-06-25T12:03:40.223Z
Learning: The Next.js documentation officially recommends using key={router.asPath} on page components to force remounting and reset state on navigation. This is documented in the Next.js useRouter API reference as one of two official approaches (the other being useEffect with route dependencies). This pattern ensures clean state transitions between routes and is a legitimate architectural choice despite the performance implications of forced remounting.

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-06-25T12:03:40.223Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1123
File: src/pages/_app.tsx:58-58
Timestamp: 2025-06-25T12:03:40.223Z
Learning: Next.js recommends using key={router.asPath} on components to reset state after navigation when you need fresh state on route changes. This is documented in the Next.js useRouter API reference. While this pattern has performance implications (forced remounting), it's a legitimate approach for ensuring clean state transitions.

Applied to files:

  • src/features/navbar/components/Footer.tsx
📚 Learning: 2025-06-24T03:45:32.567Z
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: Use Zod schemas for validating incoming request data in Next.js API routes to ensure type safety and provide clear error responses.

Applied to files:

  • src/features/listings/utils/query-builder.ts
  • src/features/grants/utils/query-builder.ts
🧬 Code graph analysis (7)
src/features/navbar/components/Footer.tsx (3)
src/constants/Superteam.ts (1)
  • Superteams (57-440)
src/features/listings/utils/region.ts (2)
  • findCountryBySlug (165-182)
  • generateSlug (137-142)
src/components/shared/RegionCombobox.tsx (1)
  • RegionCombobox (102-333)
src/features/listings/utils/query-builder.ts (3)
src/features/listings/utils/region.ts (4)
  • getRegionsForUserLocation (291-349)
  • getRegionsForSuperteamPage (232-258)
  • getRegionsForMultiCountryRegionPage (260-279)
  • getRegionsForCountryPage (281-289)
src/constants/Superteam.ts (1)
  • Superteams (57-440)
src/constants/country.ts (1)
  • countries (3-2486)
src/features/grants/utils/query-builder.ts (3)
src/features/listings/utils/region.ts (4)
  • getRegionsForUserLocation (291-349)
  • getRegionsForSuperteamPage (232-258)
  • getRegionsForMultiCountryRegionPage (260-279)
  • getRegionsForCountryPage (281-289)
src/constants/Superteam.ts (1)
  • Superteams (57-440)
src/constants/country.ts (1)
  • countries (3-2486)
src/pages/regions/[slug]/all.tsx (6)
src/constants/Superteam.ts (2)
  • Superteam (41-53)
  • Superteams (57-440)
src/utils/validUrl.ts (1)
  • getURL (1-11)
src/utils/json-ld.ts (2)
  • generateRegionalOrganizationSchema (188-203)
  • generateBreadcrumbListSchema (391-408)
src/layouts/Home.tsx (1)
  • Home (54-140)
src/components/shared/JsonLd.tsx (1)
  • JsonLd (11-27)
src/features/listings/utils/region.ts (1)
  • findCountryBySlug (165-182)
src/pages/regions/[slug]/index.tsx (5)
src/constants/Superteam.ts (2)
  • Superteam (41-53)
  • Superteams (57-440)
src/utils/validUrl.ts (1)
  • getURL (1-11)
src/utils/json-ld.ts (2)
  • generateRegionalOrganizationSchema (188-203)
  • generateBreadcrumbListSchema (391-408)
src/layouts/Home.tsx (1)
  • Home (54-140)
src/features/listings/utils/region.ts (1)
  • findCountryBySlug (165-182)
src/features/listings/utils/region.ts (2)
src/constants/Superteam.ts (1)
  • Superteams (57-440)
src/constants/country.ts (1)
  • countries (3-2486)
src/app/sitemap.ts (1)
src/features/listings/utils/region.ts (1)
  • getAllRegionSlugs (184-195)
🔇 Additional comments (4)
src/app/sitemap.ts (1)

5-6: Region sitemap segmentation and boundaries look consistent.

The new region count helper, updated boundaries, and region sitemap branch are wired correctly: IDs are contiguous with other sitemap sections, slicing uses the same MAX_URLS_PER_SITEMAP logic as listings/sponsors/grants, and getAllRegionSlugs() keeps the sitemap source aligned with your region utilities. No functional issues stand out here.

Also applies to: 55-57, 82-89, 120-124, 138-139, 143-150, 155-155, 178-182, 190-191, 341-363

src/features/navbar/components/Footer.tsx (1)

58-85: URL-based region detection and routing are aligned with the new region utilities.

The footer’s region state now correctly:

  • Derives selectedRegion from router.asPath, preferring Superteam slugs and falling back to findCountryBySlug for country/region slugs, and
  • Routes selections through generateSlug or Superteam slugs depending on whether the chosen value corresponds to a Superteam.

This keeps the combobox, the URL schema (/regions/{slug}), and the region helpers in sync and should behave as expected across superteam, country, and multi-country region pages.

Also applies to: 96-105, 191-199

src/pages/regions/[slug]/all.tsx (1)

112-138: GSSP slug normalization and country resolution look correct

getServerSideProps now normalizes the slug to lowercase, resolves a Superteam first, then falls back to findCountryBySlug, and returns notFound otherwise. This cleanly guarantees that the page always gets either st or countryData (or 404), and avoids accidental region/country collisions at runtime.

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

115-142: GSSP country-aware resolution is consistent with /all page

The server-side logic correctly normalizes the slug, prefers an exact Superteam match, falls back to findCountryBySlug, and returns notFound when neither is found. This keeps the region index and /all page in sync in terms of routing behavior and avoids ambiguity between Superteam slugs and country slugs.

});
}

console.log('queryData', queryData);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace console.log with the logger utility.

This line violates the coding guidelines for API endpoints, which require using the logger from @/lib/logger for all logging operations instead of console.log.

As per coding guidelines.

Apply this diff to use the proper logger:

+import { logger } from '@/lib/logger';
+
 import { type NextRequest, NextResponse } from 'next/server';
 import { z } from 'zod';
-    console.log('queryData', queryData);
+    logger.debug('queryData', queryData);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/api/grants/route.ts around line 51, replace the
console.log('queryData', queryData) call with the project logger from
'@/lib/logger' (import it if not already imported) and log the same data using
an appropriate level (e.g., logger.debug or logger.info) preserving the message
and variable; remove the console.log and ensure the file compiles after adding
the import.

Comment on lines +28 to +65
if (st) {
const displayName = st.displayValue;

const ogImage = new URL(`${getURL()}api/dynamic-og/region/`);
ogImage.searchParams.set('region', st.displayValue);
ogImage.searchParams.set('code', st.code!);

const organizationSchema = generateRegionalOrganizationSchema(st);
const breadcrumbSchema = generateBreadcrumbListSchema([
{ name: 'Home', url: '/' },
{ name: displayName || st.region },
]);

return (
<Home
type="region"
st={st}
meta={
<>
<Meta
title={`Welcome to Superteam Earn ${displayName} | Discover Bounties and Grants`}
description={`Welcome to Superteam Earn ${displayName}'s page — Discover bounties and grants and become a part of the global crypto community`}
canonical={`https://earn.superteam.fun/regions/${slug}/`}
og={ogImage.toString()}
/>
<JsonLd data={[organizationSchema, breadcrumbSchema]} />
</>
}
>
<div className="w-full">
<RegionPop st={st} />
<ListingsSection type="region" region={st.region} />

<GrantsSection type="region" region={st.region} />
</div>
</Home>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify OG image region param semantics across region pages

Here the OG image is configured with region: st.displayValue, whereas /regions/[slug]/all uses st.region for the same api/dynamic-og/region/ endpoint. If the API expects a slug, both should use st.region; if it expects a display label, both should use st.displayValue. Please double‑check the API’s expectations and align both pages on a single convention to avoid divergent OG images.


🏁 Script executed:

#!/bin/bash
# Inspect how the dynamic OG endpoint uses the `region` param
rg -n "dynamic-og/region" -A5 -B5

Length of output: 3243


The web search doesn't return the specific API handler for this codebase. I need to search the repository for the API endpoint implementation.

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Find the dynamic-og region API handler
fd -e ts -e js -e tsx -e jsx | xargs rg -l "dynamic-og.*region|api.*dynamic.*og" | head -20

Length of output: 232


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Find the API handler for dynamic-og region - check pages/api or api directories
fd -path "*/api/*dynamic*og*" -o -path "*/pages/api/*"

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Also search for any region-related API implementations
rg -n "dynamic.og|dynamicOg" --type ts --type js -g "*/api/*" -A3 -B3

Length of output: 148


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Search for all files mentioning dynamic-og to find where it's implemented
rg -n "dynamic-og" --max-count=50

Length of output: 1224


🏁 Script executed:

#!/bin/bash
# Find api-related directories and files
fd -type d -name api | head -20

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Look for image generation or OG endpoint handlers
rg -n "region.*image|generateOg|dynamicOg|dynamic-og" -i | grep -i "function\|export\|const\|handler" | head -30

Length of output: 1224


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Search for pages/api directory more carefully
find . -type d -name "pages" -o -name "api" 2>/dev/null | grep -E "pages|api"

Length of output: 4844


🏁 Script executed:

#!/bin/bash
# Look for vercel.json, serverless config, or similar
ls -la | grep -E "vercel|serverless|api|next"

Length of output: 132


🏁 Script executed:

#!/bin/bash
# Check if api is in pages directory
find . -path "*/pages/api*" -type f 2>/dev/null | head -20

Length of output: 779


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Search for dynamic-og handlers in pages/api
find ./src/pages/api -name "*dynamic*" -o -name "*og*" 2>/dev/null

Length of output: 172


🏁 Script executed:

#!/bin/bash
# List all files in pages/api to see structure
ls -la ./src/pages/api/ | head -40

Length of output: 1153


🏁 Script executed:

#!/bin/bash
# Search for "dynamic" in all api files
rg -n "dynamic" ./src/pages/api/ -A5 -B2

Length of output: 0


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Search for dynamic-og in src/app/api
find ./src/app/api -type f 2>/dev/null | head -40

Length of output: 1845


🏁 Script executed:

#!/bin/bash
# Search for dynamic or og in app/api
rg -n "dynamic|og" ./src/app/api/ -A5 -B2 | head -80

Length of output: 5016


🏁 Script executed:

#!/bin/bash
# More direct search for region handler in app/api
find ./src/app/api -name "*region*" -o -path "*dynamic*og*"

Length of output: 472


[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Examine the region dynamic-og handler
cat ./src/app/api/dynamic-og/region/route.tsx

Length of output: 17913


Align OG image region parameter in all.tsx with index.tsx

The API handler at src/app/api/dynamic-og/region/route.tsx line 28 explicitly searches for regions by displayValue: Superteams.find((s) => s.displayValue === region). This means the endpoint expects the display label, not the slug.

Currently:

  • src/pages/regions/[slug]/index.tsx line 32 uses st.displayValue
  • src/pages/regions/[slug]/all.tsx line 34 uses st.region

Update src/pages/regions/[slug]/all.tsx line 34 to use st.displayValue instead of st.region to match the API expectations and the index page.

🤖 Prompt for AI Agents
In src/pages/regions/[slug]/all.tsx around line 34, the OG image region
parameter is using st.region but the dynamic OG API expects the region display
label (displayValue); change the usage to st.displayValue so the query parameter
sent to the API matches what route.tsx searches for (displayValue), ensuring
consistent behavior with src/pages/regions/[slug]/index.tsx.

Comment on lines +67 to +110
if (countryData) {
const countryName = countryData.name;
const countryCode = countryData.code.toUpperCase();

const ogImage = new URL(`${getURL()}api/dynamic-og/region/`);
ogImage.searchParams.set('region', countryName);
ogImage.searchParams.set('code', countryCode);

const organizationSchema = generateRegionalOrganizationSchema({
displayValue: countryName,
region: countryName,
slug,
code: countryCode,
});

const breadcrumbSchema = generateBreadcrumbListSchema([
{ name: 'Home', url: '/' },
{ name: countryName },
]);

return (
<Home
type="region"
countryData={countryData}
meta={
<>
<Meta
title={`Welcome to Superteam Earn ${countryName} | Discover Bounties and Grants`}
description={`Welcome to Superteam Earn ${countryName}'s page — Discover bounties and grants and become a part of the global crypto community`}
canonical={`https://earn.superteam.fun/regions/${slug}/`}
og={ogImage.toString()}
/>
<JsonLd data={[organizationSchema, breadcrumbSchema]} />
</>
}
>
<div className="w-full">
<ListingsSection type="region" region={countryName} />

<GrantsSection type="region" region={countryName} />
</div>
</Home>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use slug (not country name) for ListingsSection and GrantsSection region filters

In the country branch you pass region={countryName} into both ListingsSection and GrantsSection, while the Superteam branch passes st.region (slug-like). If these components filter by a region slug, using the human‑readable country name here will likely produce empty listings/grants for country-based region pages. Please pass a normalized slug instead:

-        <div className="w-full">
-          <ListingsSection type="region" region={countryName} />
-
-          <GrantsSection type="region" region={countryName} />
-        </div>
+        <div className="w-full">
+          <ListingsSection
+            type="region"
+            region={slug.toLowerCase()}
+          />
+
+          <GrantsSection
+            type="region"
+            region={slug.toLowerCase()}
+          />
+        </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (countryData) {
const countryName = countryData.name;
const countryCode = countryData.code.toUpperCase();
const ogImage = new URL(`${getURL()}api/dynamic-og/region/`);
ogImage.searchParams.set('region', countryName);
ogImage.searchParams.set('code', countryCode);
const organizationSchema = generateRegionalOrganizationSchema({
displayValue: countryName,
region: countryName,
slug,
code: countryCode,
});
const breadcrumbSchema = generateBreadcrumbListSchema([
{ name: 'Home', url: '/' },
{ name: countryName },
]);
return (
<Home
type="region"
countryData={countryData}
meta={
<>
<Meta
title={`Welcome to Superteam Earn ${countryName} | Discover Bounties and Grants`}
description={`Welcome to Superteam Earn ${countryName}'s page — Discover bounties and grants and become a part of the global crypto community`}
canonical={`https://earn.superteam.fun/regions/${slug}/`}
og={ogImage.toString()}
/>
<JsonLd data={[organizationSchema, breadcrumbSchema]} />
</>
}
>
<div className="w-full">
<ListingsSection type="region" region={countryName} />
<GrantsSection type="region" region={countryName} />
</div>
</Home>
);
}
if (countryData) {
const countryName = countryData.name;
const countryCode = countryData.code.toUpperCase();
const ogImage = new URL(`${getURL()}api/dynamic-og/region/`);
ogImage.searchParams.set('region', countryName);
ogImage.searchParams.set('code', countryCode);
const organizationSchema = generateRegionalOrganizationSchema({
displayValue: countryName,
region: countryName,
slug,
code: countryCode,
});
const breadcrumbSchema = generateBreadcrumbListSchema([
{ name: 'Home', url: '/' },
{ name: countryName },
]);
return (
<Home
type="region"
countryData={countryData}
meta={
<>
<Meta
title={`Welcome to Superteam Earn ${countryName} | Discover Bounties and Grants`}
description={`Welcome to Superteam Earn ${countryName}'s page — Discover bounties and grants and become a part of the global crypto community`}
canonical={`https://earn.superteam.fun/regions/${slug}/`}
og={ogImage.toString()}
/>
<JsonLd data={[organizationSchema, breadcrumbSchema]} />
</>
}
>
<div className="w-full">
<ListingsSection
type="region"
region={slug.toLowerCase()}
/>
<GrantsSection
type="region"
region={slug.toLowerCase()}
/>
</div>
</Home>
);
}
🤖 Prompt for AI Agents
In src/pages/regions/[slug]/index.tsx around lines 67 to 110, the
ListingsSection and GrantsSection are passed region={countryName} which uses the
human‑readable country name; replace those with the URL slug (region={slug}) so
the components receive the normalized slug used for filtering (optionally
normalize it to lowercase or run the same slugify used elsewhere before passing
if needed).

Copy link
Copy Markdown
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

🧹 Nitpick comments (3)
src/features/listings/utils/region.ts (3)

165-182: Consider adding JSDoc to clarify null return for Superteam matches.

The function returns null when a slug matches a Superteam, which appears intentional to distinguish Superteam pages from country pages. A JSDoc comment would make this behavior more explicit for future maintainers.

Example:

/**
 * Finds a country by its slug.
 * @param slug - The URL slug to search for
 * @returns The country object if found, or null if the slug belongs to a Superteam or no match exists
 */
export function findCountryBySlug(slug: string) {

353-372: Consider reusing existing helper function.

The code for finding multi-country regions containing a country (lines 353-372) duplicates logic from getMultiCountryRegionsContainingCountry. Consider reusing that helper to reduce duplication.

Example refactor:

-  const multiCountryRegionsFromCountries = countries
-    .filter(
-      (c) =>
-        c.region &&
-        c.regions &&
-        Array.isArray(c.regions) &&
-        c.regions.some(
-          (countryName) =>
-            countryName.toLowerCase() === userLocation.toLowerCase(),
-        ),
-    )
-    .map((c) => c.name);
-  regions.push(...multiCountryRegionsFromCountries);
-
-  const multiCountryRegionsFromSuperteams = Superteams.filter((st) =>
-    st.country.some(
-      (countryName) => countryName.toLowerCase() === userLocation.toLowerCase(),
-    ),
-  ).map((st) => st.region);
-  regions.push(...multiCountryRegionsFromSuperteams);
+  const multiCountryRegions = getMultiCountryRegionsContainingCountry(userLocation);
+  regions.push(...multiCountryRegions);

334-351: Simplify redundant conditional branches.

Both branches of the conditional (lines 334-351) push userRegion.name to regions. The logic can be simplified to avoid duplication.

Apply this diff:

+  regions.push(userRegion.name);
+
   if (
-    'country' in userRegion &&
-    userRegion.country &&
-    Array.isArray(userRegion.country)
-  ) {
-    regions.push(userRegion.name);
-  } else {
-    regions.push(userRegion.name);
-  }
-
-  if (
     !('country' in userRegion) ||
     !userRegion.country ||
     !Array.isArray(userRegion.country)
   ) {
     const parentRegions = getParentRegions(userRegion) || [];
     regions.push(...parentRegions);
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0addfce and 8e9b06a.

📒 Files selected for processing (1)
  • src/features/listings/utils/region.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/features/listings/utils/region.ts
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/features/listings/utils/region.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/features/listings/utils/region.ts
**/*.*

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

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

Files:

  • src/features/listings/utils/region.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/features/listings/utils/region.ts
🧬 Code graph analysis (1)
src/features/listings/utils/region.ts (2)
src/constants/Superteam.ts (1)
  • Superteams (57-440)
src/constants/country.ts (1)
  • countries (3-2486)
🔇 Additional comments (1)
src/features/listings/utils/region.ts (1)

197-245: LGTM! Well-structured helper functions.

The internal helper functions have clear logic and excellent inline comments, particularly the explanation of single vs multi-country Superteam handling in getSuperteamRegionsContainingCountries.

Comment on lines +148 to +163
export function getEligibleCountries() {
const superteamCodes = getSuperteamCodes();

return countries.filter((country) => {
if (country.iso === true) {
const countryCodeUpper = country.code.toUpperCase();
return !superteamCodes.includes(countryCodeUpper);
}

if (country.region === true) {
return true;
}

return false;
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit return type annotation.

Per coding guidelines, top-level module functions should declare their return types. The function currently lacks an explicit return type.

Apply this diff:

-export function getEligibleCountries() {
+export function getEligibleCountries(): typeof countries {
   const superteamCodes = getSuperteamCodes();

As per coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getEligibleCountries() {
const superteamCodes = getSuperteamCodes();
return countries.filter((country) => {
if (country.iso === true) {
const countryCodeUpper = country.code.toUpperCase();
return !superteamCodes.includes(countryCodeUpper);
}
if (country.region === true) {
return true;
}
return false;
});
}
export function getEligibleCountries(): typeof countries {
const superteamCodes = getSuperteamCodes();
return countries.filter((country) => {
if (country.iso === true) {
const countryCodeUpper = country.code.toUpperCase();
return !superteamCodes.includes(countryCodeUpper);
}
if (country.region === true) {
return true;
}
return false;
});
}
🤖 Prompt for AI Agents
In src/features/listings/utils/region.ts around lines 148 to 163, the
getEligibleCountries function is missing an explicit return type; add an
explicit return type annotation (e.g., : Country[] or Array<Country>) to the
function signature to satisfy the module-level typing rule, and import or
reference the appropriate Country type if it isn’t already in scope.

Copy link
Copy Markdown
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: 5

♻️ Duplicate comments (2)
src/features/home/components/SponsorStage/BoostBanner.tsx (1)

166-169: Avoid empty sponsor-logo src (already flagged).

If the sponsor hasn't set a logo, this <img> resolves to src="", producing a broken image plus an unnecessary request for the current page. Conditionally render the logo only when a valid logo URL exists.

Apply this diff to fix:

-        <img
-          src={user?.currentSponsor?.logo ?? ''}
-          alt={user?.currentSponsor?.name ?? ''}
-          className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
-        />
+        {user?.currentSponsor?.logo && (
+          <img
+            src={user.currentSponsor.logo}
+            alt={user.currentSponsor.name ?? 'Sponsor logo'}
+            className="absolute top-5/9 right-9 size-28 -translate-y-1/2 rounded-full"
+          />
+        )}
src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (1)

54-57: Handle missing commitment dates.

listing.commitmentDate is optional and will render "Invalid Date" when absent or invalid. This issue was already flagged in a previous review.

Apply this diff to guard the formatting:

-            <p className="max-w-sm text-base text-slate-500">
-              You are past your commitment date of{' '}
-              {dayjs(listing.commitmentDate).format('Do MMM')}. Announce winners
-              now to avoid losing credibility.
-            </p>
+            <p className="max-w-sm text-base text-slate-500">
+              {listing.commitmentDate && dayjs(listing.commitmentDate).isValid()
+                ? `You are past your commitment date of ${dayjs(listing.commitmentDate).format('Do MMM')}. Announce winners now to avoid losing credibility.`
+                : 'You are past your commitment date. Announce winners now to avoid losing credibility.'}
+            </p>
🧹 Nitpick comments (8)
src/features/home/components/SponsorStage/BoostBanner.tsx (5)

41-43: Consider removing unnecessary type assertion.

The emailEstimateQuery function signature (from relevant snippets) accepts string | undefined | null for the region parameter, so the explicit as string | undefined cast on line 42 may be redundant if listing.region is already typed as string | undefined | null.

Apply this diff if the type assertion is unnecessary:

-  const { data: emailEstimate, isLoading: isEmailLoading } = useQuery(
-    emailEstimateQuery(skills, region as string | undefined),
-  );
+  const { data: emailEstimate, isLoading: isEmailLoading } = useQuery(
+    emailEstimateQuery(skills, region ?? undefined),
+  );

50-58: Remove unnecessary array spread.

The spread operator [...BOOST_STEPS] creates a new array on every render. Since BOOST_STEPS is already a readonly constant array, you can use it directly with .indexOf().

Apply this diff:

-  const allSteps = [...BOOST_STEPS];
-  const currentStepIndex = allSteps.indexOf(currentStep);
-  const nextStep = allSteps[currentStepIndex + 1];
+  const currentStepIndex = BOOST_STEPS.indexOf(currentStep);
+  const nextStep = BOOST_STEPS[currentStepIndex + 1];

80-85: Consider extracting the pure function outside the component.

The formatImpressions function is a pure utility that gets recreated on every render. Extract it to module scope or to a shared utility file for better performance and reusability.

Apply this diff:

+const formatImpressions = (num: number): string => {
+  if (num >= 1000) {
+    return `${(num / 1000).toFixed(0)}k`;
+  }
+  return num.toString();
+};
+
 export function BoostBanner({ listing }: BoostBannerProps) {
   const { usdValue, skills, region } = listing;
   
   // ... rest of component
   
-  const formatImpressions = (num: number): string => {
-    if (num >= 1000) {
-      return `${(num / 1000).toFixed(0)}k`;
-    }
-    return num.toString();
-  };

87-98: Consider consolidating redundant PostHog events.

The handleBoostClick function captures two events with identical data:

  • 'boost listing_sponsor stage banner'
  • 'click_sponsor stage banner'

If both events serve the same tracking purpose, consider consolidating them into a single event. If they serve different purposes, consider adding a comment explaining why both are needed.


109-156: Review nested Link structure.

The outer <Link> (line 109) wraps the entire banner, while an inner <Link> (line 140) exists for the "Get Help" button. The stopPropagation call prevents the outer link from firing, but this nested structure can be fragile. Consider restructuring to avoid nesting interactive elements, or document why this pattern is necessary.

For example, you could make the outer wrapper a <div> and only wrap specific interactive elements in <Link> components, or use a different approach to handle the dual navigation targets.

src/features/listing-builder/components/Form/Boost/utils.ts (2)

108-108: Extract the magic number to a named constant.

The hardcoded 1000 threshold should be defined as a named constant for maintainability and clarity, consistent with other constants like DEFAULT_EMAIL_IMPRESSIONS.

As per coding guidelines.

Add to constants.ts:

export const MIN_EMAIL_ESTIMATE_THRESHOLD = 1000;

Then update the condition:

  if (
    isSkillsSelected(skills) &&
    typeof estimate === 'number' &&
-    estimate > 1000 // adding this so we dont show 0 impressions in boost panel
+    estimate > MIN_EMAIL_ESTIMATE_THRESHOLD
  ) {

108-108: Improve the inline comment clarity and fix typo.

The comment has a typo ("dont" → "don't") and could better explain the rationale. The threshold is 1000, not 0, so mentioning "0 impressions" is somewhat misleading.

Consider revising to:

-    estimate > 1000 // adding this so we dont show 0 impressions in boost panel
+    estimate > 1000 // Only show custom estimates when substantial; fall back to default for low-confidence values
src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (1)

75-80: Consider using Next.js Image component for optimization.

The plain <img> tag at line 75 (and line 91) misses automatic optimizations provided by Next.js Image, such as lazy loading and modern format serving. While the performance impact is minimal for small icons, consistency with Next.js best practices is recommended.

Apply this diff:

+import Image from 'next/image';
+
 // ... rest of imports

-              <img
+              <Image
                 src="/assets/sponsor/pratik.webp"
                 alt="Get Help"
                 width={28}
                 height={28}
               />

Similarly, update the decorative image at lines 91-95.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e9b06a and c601a70.

📒 Files selected for processing (6)
  • src/features/home/components/SponsorStage/BoostBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/NextListingBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (1 hunks)
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx (1 hunks)
  • src/features/listing-builder/components/Form/Boost/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/features/home/components/SponsorStage/NextListingBanner.tsx
  • src/features/home/components/SponsorStage/SponsorSidebarListing.tsx
  • src/features/home/components/SponsorStage/NewSponsorBanner.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/listing-builder/components/Form/Boost/utils.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/listing-builder/components/Form/Boost/utils.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/listing-builder/components/Form/Boost/utils.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
**/*.*

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

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

Files:

  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/listing-builder/components/Form/Boost/utils.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/features/home/components/SponsorStage/BoostBanner.tsx
  • src/features/listing-builder/components/Form/Boost/utils.ts
  • src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx
🧬 Code graph analysis (3)
src/features/home/components/SponsorStage/BoostBanner.tsx (5)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
src/store/user.ts (1)
  • useUser (23-101)
src/features/listing-builder/components/Form/Boost/constants.ts (2)
  • BOOST_STEPS (1-1)
  • BOOST_STEP_TO_AMOUNT_USD (4-9)
src/features/listing-builder/components/Form/Boost/queries.ts (2)
  • featuredAvailabilityQuery (26-43)
  • emailEstimateQuery (45-62)
src/features/listing-builder/components/Form/Boost/utils.ts (3)
  • resolveEmailImpressions (101-113)
  • amountToStep (28-45)
  • getTotalImpressionsForValue (146-158)
src/features/listing-builder/components/Form/Boost/utils.ts (1)
src/features/listing-builder/components/Form/Boost/constants.ts (2)
  • isSkillsSelected (16-17)
  • DEFAULT_EMAIL_IMPRESSIONS (14-14)
src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (1)
src/features/listings/types/index.ts (1)
  • Listing (12-61)
🔇 Additional comments (4)
src/features/home/components/SponsorStage/BoostBanner.tsx (2)

1-26: LGTM: Clean imports and interface definition.

The imports are well-organized and the interface is appropriately typed.


60-75: LGTM: Impression calculation logic is sound.

The impression difference calculation correctly determines whether to show the banner.

src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx (2)

1-12: LGTM!

The imports and props interface are well-structured, following TypeScript best practices with import type for the Listing type.


15-26: Verify the need for duplicate PostHog events and add null safety.

Two concerns:

  1. Two separate PostHog captures for the same click event may be redundant. Confirm whether both announce winners_sponsor stage banner and click_sponsor stage banner are intentionally tracked separately.
  2. listing.type and listing.slug are optional per the Listing interface but are accessed without null checks, which could pass undefined to analytics.

Consider consolidating events or adding guards:

  const handleAnnounceWinnersClick = () => {
    posthog.capture('announce winners_sponsor stage banner', {
      stage: 'REVIEW_URGENT',
-     listing_type: listing.type,
-     listing_slug: listing.slug,
+     listing_type: listing.type ?? 'unknown',
+     listing_slug: listing.slug ?? 'unknown',
    });
    posthog.capture('click_sponsor stage banner', {
      stage: 'REVIEW_URGENT',
-     listing_type: listing.type,
-     listing_slug: listing.slug,
+     listing_type: listing.type ?? 'unknown',
+     listing_slug: listing.slug ?? 'unknown',
    });
  };

Comment on lines +28 to +34
const handleGetHelpClick = () => {
posthog.capture('get help_sponsor stage banner', {
stage: 'REVIEW_URGENT',
listing_type: listing.type,
listing_slug: listing.slug,
});
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add null safety for optional properties.

listing.type and listing.slug are optional but accessed without guards, potentially passing undefined to PostHog.

Apply this diff:

  const handleGetHelpClick = () => {
    posthog.capture('get help_sponsor stage banner', {
      stage: 'REVIEW_URGENT',
-     listing_type: listing.type,
-     listing_slug: listing.slug,
+     listing_type: listing.type ?? 'unknown',
+     listing_slug: listing.slug ?? 'unknown',
    });
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleGetHelpClick = () => {
posthog.capture('get help_sponsor stage banner', {
stage: 'REVIEW_URGENT',
listing_type: listing.type,
listing_slug: listing.slug,
});
};
const handleGetHelpClick = () => {
posthog.capture('get help_sponsor stage banner', {
stage: 'REVIEW_URGENT',
listing_type: listing.type ?? 'unknown',
listing_slug: listing.slug ?? 'unknown',
});
};
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx around lines
28 to 34, listing.type and listing.slug are optional and currently accessed
directly when calling posthog.capture; change the call to pass safe values by
using optional chaining or fallback defaults (e.g., listing?.type ?? 'unknown'
and listing?.slug ?? 'unknown') so undefined is never sent to PostHog, and keep
the rest of the event payload the same.

Comment on lines +37 to +38
<Link
href={`/dashboard/listings/${listing.slug}/submissions`}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against undefined slug in href construction.

listing.slug is optional per the Listing interface. If it's undefined, the href becomes /dashboard/listings/undefined/submissions, breaking navigation.

Add a runtime guard or provide a fallback:

  return (
    <Link
-     href={`/dashboard/listings/${listing.slug}/submissions`}
+     href={listing.slug ? `/dashboard/listings/${listing.slug}/submissions` : '#'}
      className="relative flex items-center justify-between overflow-hidden rounded-xl bg-slate-100 px-10 py-8"
      onClick={handleAnnounceWinnersClick}
      prefetch={false}
    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Link
href={`/dashboard/listings/${listing.slug}/submissions`}
<Link
href={listing.slug ? `/dashboard/listings/${listing.slug}/submissions` : '#'}
className="relative flex items-center justify-between overflow-hidden rounded-xl bg-slate-100 px-10 py-8"
onClick={handleAnnounceWinnersClick}
prefetch={false}
>
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx around lines
37-38, the Link href uses listing.slug which is optional and can become
"undefined"; guard against this by computing a safeSlug (e.g., listing?.slug ??
fallback like '' or a sentinel path) and use that when building the URL or
conditionally render the Link only when slug exists so the href never becomes
/dashboard/listings/undefined/submissions.

Comment on lines +68 to +83
<Link
href="https://t.me/pratikdholani/"
onClick={(e) => {
e.stopPropagation();
handleGetHelpClick();
}}
>
<img
src="/assets/sponsor/pratik.webp"
alt="Get Help"
width={28}
height={28}
/>
<span>Get Help</span>
</Link>
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid nested anchor elements for accessibility.

The <Link> at line 68 is nested inside the parent <Link> at line 37, creating an anchor-inside-anchor structure that violates HTML semantics and degrades accessibility. While e.stopPropagation() prevents dual navigation, screen readers and keyboard navigation still encounter issues.

Refactor to use a <button> or restructure the layout to avoid nesting interactive elements:

-          <Button
-            variant="ghost"
-            asChild
-            className="flex items-center gap-3 text-sm text-slate-400 underline underline-offset-4 hover:text-slate-700"
-          >
-            <Link
-              href="https://t.me/pratikdholani/"
-              onClick={(e) => {
-                e.stopPropagation();
-                handleGetHelpClick();
-              }}
-            >
-              <img
-                src="/assets/sponsor/pratik.webp"
-                alt="Get Help"
-                width={28}
-                height={28}
-              />
-              <span>Get Help</span>
-            </Link>
-          </Button>
+          <Button
+            variant="ghost"
+            asChild
+            className="flex items-center gap-3 text-sm text-slate-400 underline underline-offset-4 hover:text-slate-700"
+          >
+            <a
+              href="https://t.me/pratikdholani/"
+              target="_blank"
+              rel="noopener noreferrer"
+              onClick={(e) => {
+                e.stopPropagation();
+                handleGetHelpClick();
+              }}
+            >
+              <img
+                src="/assets/sponsor/pratik.webp"
+                alt="Get Help"
+                width={28}
+                height={28}
+              />
+              <span>Get Help</span>
+            </a>
+          </Button>

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +87 to +90
<div className="absolute -right-26 -bottom-32 hidden xl:block">
<span className="block size-100 rounded-full bg-indigo-100/80" />
</div>
<div className="absolute top-2/4 right-14 hidden w-49 -translate-y-1/2 xl:block">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify custom Tailwind classes exist in configuration.

The classes size-100 (line 88) and w-49 (line 90) appear to be custom Tailwind utilities not in the default configuration. Ensure these are defined in your Tailwind config; otherwise, they'll be ignored at build time.

Run this script to check if these classes are defined:


🏁 Script executed:

#!/bin/bash
# Description: Check Tailwind config for custom size-100 and w-49 classes

# Search for Tailwind config files
fd -e js -e ts "tailwind.config" --exec cat {} \; | grep -E "(size.*100|width.*49|spacing.*100)"

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Locate and examine Tailwind config and verify custom classes

# Find Tailwind config files (check common patterns)
echo "=== Searching for Tailwind config files ==="
fd -e "js" -e "ts" "tailwind" . --max-depth 3

echo -e "\n=== Searching for theme extension with custom spacing/width ==="
rg -n "size.*100|w-49|spacing.*100" --type ts --type js

echo -e "\n=== Checking package.json for Tailwind version ==="
if [ -f "package.json" ]; then
  jq '.devDependencies.tailwindcss, .dependencies.tailwindcss' package.json 2>/dev/null || cat package.json | grep -i tailwind
fi

echo -e "\n=== Looking for tailwind.config in root ==="
ls -la tailwind.config.* 2>/dev/null || echo "No tailwind.config in root"

Length of output: 2286



🏁 Script executed:

#!/bin/bash
# Description: Find Tailwind configuration in various locations

echo "=== Checking for next.config files ==="
fd -e "js" -e "ts" "next.config" .

echo -e "\n=== Checking for postcss.config files ==="
fd -e "js" -e "ts" "postcss.config" .

echo -e "\n=== Searching for tailwind config in config directories ==="
fd "tailwind" . --type f

echo -e "\n=== Checking for CSS files with @layer utilities ==="
fd -e "css" . | head -20

echo -e "\n=== Searching CSS files for size-100 or w-49 definitions ==="
fd -e "css" -e "scss" . --exec grep -l "size-100\|w-49" {} \;

Length of output: 516



🏁 Script executed:

#!/bin/bash
# Description: Examine Tailwind and PostCSS configuration files

echo "=== Content of next.config.ts ==="
cat ./next.config.ts

echo -e "\n=== Content of postcss.config.js ==="
cat ./postcss.config.js

echo -e "\n=== Searching for Tailwind theme configuration in next.config ==="
rg -A 20 "tailwind|theme.*extend" ./next.config.ts

Length of output: 5555



🏁 Script executed:

#!/bin/bash
# Description: Check globals.css for Tailwind v4 configuration and verify class definitions

echo "=== Content of src/styles/globals.css ==="
cat ./src/styles/globals.css

echo -e "\n=== Searching for any tailwind configuration in CSS or config files ==="
rg -n "@theme|theme\s*\{|size.*100|w-49" ./src/styles/

echo -e "\n=== Checking for Tailwind plugins that might add these utilities ==="
rg -n "size-100|w-49|spacing.*100" ./src/

Length of output: 15199



🏁 Script executed:

#!/bin/bash
# Description: Check for build artifacts, Tailwind cache, or plugin-generated utilities

echo "=== Checking for .next/static or build artifacts ==="
ls -la .next/static 2>/dev/null | head -20 || echo "No .next build found"

echo -e "\n=== Searching for any Tailwind plugins that might generate utilities ==="
rg -n "@layer utilities|addUtilities|plugin" --type ts --type js ./src --max-count=5

echo -e "\n=== Checking package.json for any Tailwind plugins ==="
cat ./package.json | grep -A 20 '"dependencies"\|"devDependencies"' | grep -i "tailwind\|tw-"

Length of output: 1557


Replace undefined Tailwind classes with valid utilities or add custom configuration.

The classes size-100 (line 88) and w-49 (line 90) are not defined in your Tailwind v4 configuration. In Tailwind v4 with @tailwindcss/postcss, undefined utilities produce no CSS and are silently ignored.

Options to fix:

  1. Use standard Tailwind values: Replace size-100 with size-96 (nearest standard) and w-49 with w-48
  2. Add to @theme inline: In src/styles/globals.css, extend spacing under @theme inline:
    --spacing-100: 25rem;
    --spacing-49: 12.25rem;
    Then use size-[--spacing-100] or add custom utilities via @layer utilities
🤖 Prompt for AI Agents
In src/features/home/components/SponsorStage/ReviewUrgentBanner.tsx around lines
87 to 90, the Tailwind classes `size-100` and `w-49` are undefined and produce
no CSS; either replace them with valid Tailwind utilities (e.g., change
`size-100` to `size-96` and `w-49` to `w-48`) or add custom spacing values to
your Tailwind theme (or via @theme inline in src/styles/globals.css) and then
reference those custom tokens with the appropriate utility syntax so the classes
generate real CSS.

Comment on lines +105 to +111
if (
isSkillsSelected(skills) &&
typeof estimate === 'number' &&
estimate > 1000 // adding this so we dont show 0 impressions in boost panel
) {
return Math.round(estimate / 1000) * 1000;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify the threshold discontinuity.

The logic creates a discontinuity where estimate = 1000 returns 30,000 (the default), while estimate = 1500 returns 2,000 (rounded). This creates a counter-intuitive UX where slightly higher estimates actually display lower impression counts.

Confirm whether:

  1. The 1000 threshold is the correct business requirement
  2. The discontinuity is acceptable from a product perspective
  3. A smoother transition or different threshold might be more appropriate

🏁 Script executed:

#!/bin/bash
# Verify how resolveEmailImpressions is used in the codebase to understand impact

rg -n --type ts --type tsx -A5 -B5 'resolveEmailImpressions'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Correct the file type flags - use valid rg type names or omit the filter

rg -n -A5 -B5 'resolveEmailImpressions' src/

Length of output: 7292


🏁 Script executed:

#!/bin/bash
# Show the full function definition to understand context

cat src/features/listing-builder/components/Form/Boost/utils.ts | head -120 | tail -30

Length of output: 821


🏁 Script executed:

#!/bin/bash
# Search for the constant or threshold value 1000 near resolveEmailImpressions

rg -n '1000' src/features/listing-builder/components/Form/Boost/utils.ts -B3 -A3

Length of output: 566


🏁 Script executed:

#!/bin/bash
# Look for tests related to this utility file

fd 'utils.*test\|utils.*spec' src/features/listing-builder/components/Form/Boost/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for DEFAULT_EMAIL_IMPRESSIONS constant definition

rg -n 'DEFAULT_EMAIL_IMPRESSIONS' src/features/listing-builder/components/Form/Boost/

Length of output: 305


Extract hardcoded threshold to named constant and clarify the business logic discontinuity.

The code violates coding guidelines in three ways:

  1. Magic number 1000 (line 108): Extract to a named constant in constants.ts (e.g., MIN_ESTIMATE_THRESHOLD = 1000). Per guidelines, hardcoded values should use ALL_CAPS naming and be stored as constants, not inlined.

  2. Typo and unclear comment (line 108): The inline comment contains "dont" (should be "don't") and obscures intent. Consider moving to JSDoc for the function explaining why this threshold exists and what it prevents.

  3. UX discontinuity requires verification: The logic creates a cliff where estimate = 1000 returns 30,000 impressions, but estimate = 1001 returns 1,000. Confirm with product that this threshold and discontinuity are intentional. No unit tests exist to validate this behavior.

The function is used in 3 components (BoostContent, BoostButton, BoostBanner) to display email impressions, so any threshold change impacts all three.

🤖 Prompt for AI Agents
In src/features/listing-builder/components/Form/Boost/utils.ts around lines 105
to 111, replace the hardcoded magic number 1000 with a named ALL_CAPS constant
exported from constants.ts (e.g., MIN_ESTIMATE_THRESHOLD = 1000) and import it
here; move and expand the inline comment into a JSDoc on the function that
explains why the threshold exists (fixing the "dont" typo to "don't") and what
UX discontinuity it creates; add or update unit tests covering edge cases (1000,
1001, 0, null/undefined) to lock behavior; and before merging, confirm with
product whether the discontinuous mapping (the cliff between 1000 and 1001) is
intentional and adjust logic/tests accordingly, updating BoostContent,
BoostButton, and BoostBanner imports/usages if the constant or behavior changes.

Copy link
Copy Markdown
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

🧹 Nitpick comments (3)
src/features/listing-builder/components/Form/Rewards/Footer.tsx (2)

124-145: Remove redundant dependency from useMemo.

The dependency array includes both prevRewardAmountTokens and rewardAmount, but since prevRewardAmountTokens is simply Number(rewardAmount) || 0 (line 124), including both is redundant.

Apply this diff to simplify the dependencies:

 const targetBoostTokens = useMemo(() => {
   if (panel !== 'boost') return prevRewardAmountTokens;
   const estimatedUsdValue = computeEstimatedUsdValue(
     rewardAmount,
     tokenUsdValue,
   );
   const targetUSD = resolveTargetUsdFromBoost(
     boostStep ?? 0,
     estimatedUsdValue,
     isFeatureAvailable,
   );
   const usd = tokenUsdValue || 1;
   return targetUSD / usd;
 }, [
   boostStep,
   isFeatureAvailable,
   panel,
-  prevRewardAmountTokens,
   rewardAmount,
   tokenUsdValue,
 ]);

146-150: Consider clarifying the non-boost case logic.

The memo returns true when panel !== 'boost', but this value is never actually used since the memo is only accessed within the panel === 'boost' block (line 174). While this is a safe default, it could be clearer with a comment or by restructuring the logic.

Optional: Add a comment explaining the intent:

 const boostIncreasesReward = useMemo(
   () =>
+    // Safe default when not in boost mode (value won't be used)
     panel !== 'boost' ? true : targetBoostTokens > prevRewardAmountTokens,
   [panel, prevRewardAmountTokens, targetBoostTokens],
 );
src/features/listing-builder/components/Form/Rewards/Sheet.tsx (1)

110-114: Consider logging the error instead of silent failure.

While the empty catch with /* noop */ ensures the finally block executes for cleanup, silently swallowing all errors makes debugging harder. URL manipulation or router operations could fail in unexpected ways.

Apply this diff to add minimal error logging:

-    } catch (err) {
-      /* noop */
+    } catch (err) {
+      console.error('Failed to clean up boost URL parameter:', err);
     } finally {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c601a70 and 52f1d53.

📒 Files selected for processing (2)
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx (6 hunks)
  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx (4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx
**/*.*

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

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

Files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx
**/*.tsx

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

No need to declare the return type of a component which returns JSX.

Files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
  • src/features/listing-builder/components/Form/Rewards/Footer.tsx
🧠 Learnings (8)
📚 Learning: 2025-09-03T12:38:13.594Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/icon-component-generation.mdc:0-0
Timestamp: 2025-09-03T12:38:13.594Z
Learning: Applies to src/components/icons/*.tsx : File header must start with: /* eslint-disable prettier/prettier */ and import { GenIcon, type IconBaseProps } from '@/components/icons/helpers/GenIcon';

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-06-24T03:45:48.987Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-06-24T03:45:48.987Z
Learning: Never suppress the React Hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps). Instead, refactor your code to avoid missing dependencies, often by using updater functions or restructuring logic.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-07-22T13:09:13.711Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/react-state-and-effects.mdc:0-0
Timestamp: 2025-07-22T13:09:13.711Z
Learning: Applies to **/use*.{js,jsx,ts,tsx} : Design custom Hooks to encapsulate reusable logic with clear intent, focusing on concrete use cases and minimal configuration.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : Consider using custom Hooks for data fetching and subscriptions in React.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-07-22T13:37:47.476Z
Learnt from: CR
Repo: SuperteamDAO/earn PR: 0
File: .cursor/rules/we-might-not-need-an-effect.mdc:0-0
Timestamp: 2025-07-22T13:37:47.476Z
Learning: Applies to **/*.{tsx,ts} : Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-06-25T12:03:40.223Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1123
File: src/pages/_app.tsx:58-58
Timestamp: 2025-06-25T12:03:40.223Z
Learning: The Next.js documentation officially recommends using key={router.asPath} on page components to force remounting and reset state on navigation. This is documented in the Next.js useRouter API reference as one of two official approaches (the other being useEffect with route dependencies). This pattern ensures clean state transitions between routes and is a legitimate architectural choice despite the performance implications of forced remounting.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
📚 Learning: 2025-06-25T12:03:40.223Z
Learnt from: a20hek
Repo: SuperteamDAO/earn PR: 1123
File: src/pages/_app.tsx:58-58
Timestamp: 2025-06-25T12:03:40.223Z
Learning: Next.js recommends using key={router.asPath} on components to reset state after navigation when you need fresh state on route changes. This is documented in the Next.js useRouter API reference. While this pattern has performance implications (forced remounting), it's a legitimate approach for ensuring clean state transitions.

Applied to files:

  • src/features/listing-builder/components/Form/Rewards/Sheet.tsx
🧬 Code graph analysis (2)
src/features/listing-builder/components/Form/Rewards/Sheet.tsx (4)
src/features/listing-builder/components/Form/Rewards/Tokens/TokenSelect.tsx (1)
  • TokenSelect (32-134)
src/features/listing-builder/components/Form/Rewards/PaymentType.tsx (1)
  • PaymentType (38-101)
src/features/listing-builder/components/Form/Rewards/BoostContent.tsx (1)
  • BoostContent (39-493)
src/components/ui/alert-dialog.tsx (8)
  • AlertDialog (151-151)
  • AlertDialogContent (154-154)
  • AlertDialogHeader (157-157)
  • AlertDialogTitle (160-160)
  • AlertDialogDescription (155-155)
  • AlertDialogFooter (156-156)
  • AlertDialogCancel (153-153)
  • AlertDialogAction (152-152)
src/features/listing-builder/components/Form/Rewards/Footer.tsx (1)
src/features/listing-builder/components/Form/Boost/utils.ts (2)
  • computeEstimatedUsdValue (115-125)
  • resolveTargetUsdFromBoost (127-144)
🪛 Biome (2.1.2)
src/features/listing-builder/components/Form/Rewards/Sheet.tsx

[error] 137-137: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

🔇 Additional comments (7)
src/features/listing-builder/components/Form/Rewards/Footer.tsx (2)

39-39: LGTM: Backward-compatible API extension.

The optional options parameter maintains backward compatibility while enabling the new bypass prompt flow.


179-185: LGTM: Clean integration of boost calculations and prompt control.

The changes effectively integrate the computed boost values:

  • Button disable logic correctly uses boostIncreasesReward
  • Pre-computed targetBoostTokens and prevRewardAmountTokens improve readability
  • The distinction between bypassing prompts in boost flow (lines 267, 295) vs. not bypassing in rewards flow (line 317) is intentional and correct

Also applies to: 192-192, 238-238, 267-267, 295-295, 317-317

src/features/listing-builder/components/Form/Rewards/Sheet.tsx (5)

52-54: LGTM: Appropriate use of ref for bypass tracking.

The combination of isBoostDismissPromptOpen state and bypassBoostCloseRef ref correctly separates UI state (dialog visibility) from imperative control flow (bypassing the prompt). Using a ref prevents unnecessary re-renders when toggling the bypass flag.


86-98: LGTM: Well-structured conditional logic for boost dismiss prompt.

The early return pattern correctly intercepts close attempts from the boost panel and shows the confirmation dialog instead. The opening logic properly resets state to ensure clean initialization.


117-128: LGTM: Clean API for controlled sheet closure.

The changeOpen function provides a well-designed interface for the Footer component to optionally bypass the dismiss prompt. The optional parameter pattern is appropriate here since bypassing is the exception, not the rule.


131-227: LGTM: Well-structured JSX refactoring for the new dismiss prompt flow.

The fragment wrapper properly accommodates the new AlertDialog sibling, and the Sheet structure is updated correctly. Setting showCloseIcon={false} is appropriate since closing is now handled through the custom prompt logic. Passing changeOpen to the Footer component provides the necessary control API.


228-262: LGTM: Clear and effective boost dismissal confirmation dialog.

The AlertDialog implementation provides excellent UX for the boost flow:

  • Clear messaging about the value of boosting
  • "Skip Boost" correctly triggers closure with bypass
  • "Get more visibility" keeps the user in the boost panel to continue
  • Proper use of controlled dialog pattern with state management

<FormField
control={form.control}
name="rewards"
render={({}) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the empty object destructuring pattern.

The empty object pattern ({}) is flagged by static analysis as incorrect. Since the render prop's parameter isn't used, replace it with an underscore to indicate an intentionally unused parameter.

Based on static analysis.

Apply this diff:

-            render={({}) => {
+            render={(_) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
render={({}) => {
render={(_) => {
🧰 Tools
🪛 Biome (2.1.2)

[error] 137-137: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

🤖 Prompt for AI Agents
In src/features/listing-builder/components/Form/Rewards/Sheet.tsx around line
137, the render prop uses an empty object destructuring pattern "({})" which
static analysis flags; replace that parameter with an underscore to indicate an
intentionally unused parameter (e.g., change "render={({}) => {" to "render={_
=> {" or "render={(_) => {"), keeping the rest of the function body unchanged.

Copy link
Copy Markdown
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

🧹 Nitpick comments (2)
src/app/sitemap.ts (2)

55-57: Remove unnecessary async keyword.

This function doesn't perform any asynchronous operations—it just returns the length of an array. While marking it async doesn't break functionality, it's unnecessary and creates a promise wrapper for a synchronous operation.

Apply this diff to remove the async keyword:

-async function getRegionsCount(): Promise<number> {
+function getRegionsCount(): number {
   return getAllRegionSlugs().length;
 }

If you prefer to keep it async for API consistency with other count functions (which make database calls), that's acceptable but not required since callers handle both sync and async properly in Promise.all.


340-363: LGTM with optional performance suggestion.

The regions sitemap generation logic is correct and follows the established pattern. The priority of 0.8 is appropriate for region pages.

One minor optimization: getAllRegionSlugs() is called multiple times (lines 56 and 346). While the function is lightweight, you could cache the result at the module level if this becomes a performance concern.

Optional optimization (only if needed):

// At module level, before getRegionsCount
let cachedRegionSlugs: readonly string[] | null = null;

function getRegionSlugs(): readonly string[] {
  if (!cachedRegionSlugs) {
    cachedRegionSlugs = getAllRegionSlugs();
  }
  return cachedRegionSlugs;
}

Then replace calls to getAllRegionSlugs() with getRegionSlugs(). However, this is only beneficial if the function is called frequently or becomes expensive.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52f1d53 and 7de0c6a.

📒 Files selected for processing (1)
  • src/app/sitemap.ts (8 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

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

**/*.{ts,tsx}: When building generic functions in TypeScript, you may need to use any inside the function body to satisfy type constraints that cannot be expressed concisely.
Outside of generic functions, use any extremely sparingly in TypeScript code.

Unless explicitly required by the framework, do not use default exports in TypeScript files.

**/*.{ts,tsx}: Proactively use discriminated unions to model data that can be in one of a few different shapes.
Use switch statements to handle the results of discriminated unions.
Use discriminated unions to prevent the 'bag of optionals' problem when modeling state (e.g., fetching state).

**/*.{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.
Remember that numeric enums behave differently to string enums. Numeric enums produce a reverse mapping, resulting in more keys than expected.

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

**/*.{ts,tsx}: ALWAYS prefer interfaces when modelling inheritance in TypeScript
Only use the & operator in TypeScript where interface extends is not possible, due to performance concerns

Inside generic types, functions or classes, prefix type parameters with T (e.g., TKey, TValue)

Use optional properties extremely sparingly in TypeScript. Only use them when the property is truly optional, and consider whether bugs may be caused by a failure to pass the property. For example, prefer userId: string | undefined over userId?: string when the property should always be present but may be undefined.

**/*.{ts,tsx}: Use readonly properties for object types by default. This will prevent accidental mutation at runtime.
Omit readonly only when the property is genuinely mutable.

When declaring functions on the top-level of a module, declare their return types.

Consider using a re...

Files:

  • src/app/sitemap.ts
!(.cursor/rules/*.mdc)

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • src/app/sitemap.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: 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.

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

**/*.{js,jsx,ts,tsx}: Use updater functions (functional updater pattern) when performing multiple sequential updates to the same state variable in React components.
Always use updater functions for state updates inside asynchronous operations (such as async functions, API calls, or timers) to avoid stale state in React components.
Do not suppress the React hooks dependency linter (e.g., eslint-disable-next-line react-hooks/exhaustive-deps); instead, restructure code to fix missing dependencies.
Always include all reactive values (props, state, variables declared in the component body) used inside a useEffect in its dependency array.
Use Effect Events (such as useEffectEvent) for reading values inside effects without making them reactive, and only call Effect Events from inside Effects.
Avoid using useEffect for computations or deriving values from state or props; instead, calculate such values during rendering.
Avoid using useEffect for handling events; handle side effects directly in the event handler that caused the state change.
Always handle cleanup in useEffect to prevent memory leaks (e.g., remove event listeners, cancel timers, or abort network requests).
Prefer small, focused state variables over large object state in React components.
Always treat state as immutable in React; never modify state objects or arrays directly.
Extract repeated Effect logic into custom Hooks for reuse.
When migrating to new...

Files:

  • src/app/sitemap.ts
**/*.*

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

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

Files:

  • src/app/sitemap.ts
**/*.{tsx,ts}

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

**/*.{tsx,ts}: Avoid using Effects in React components for data transformations that can be calculated during rendering; compute values directly in the render body instead of using useEffect and state.
For expensive calculations in React components, use useMemo instead of useEffect and state to cache results.
To reset all component state when a prop changes, use the key prop on the component instead of resetting state in a useEffect.
When updating state based on props in React, calculate derived values during render instead of adjusting state in useEffect.
Place event-specific logic, such as form submission or notifications, inside event handlers rather than in useEffect.
Avoid chains of state updates across multiple Effects; instead, calculate all derived state in event handlers or during render.
Show notifications and perform side effects in response to user actions within event handlers, not in useEffect.
When fetching data in useEffect, always implement proper cleanup to prevent race conditions and memory leaks.
Consider using custom Hooks for encapsulating data fetching and subscriptions in React components.
If you can calculate something during render, you don't need an Effect in React.
For expensive calculations in React, prefer useMemo over useEffect.
To reset all component state in React, use the key prop rather than useEffect.
To update state based on props in React, calculate during render when possible instead of using useEffect.
Code that runs because of an event should be in event handlers, not in useEffect.
Avoid chains of state updates in multiple Effects in React components.
Consider using custom Hooks for data fetching and subscriptions in React.
Use Effects in React only when synchronizing with external systems, such as browser APIs, third-party libraries, WebSocket connections, or non-React UI widgets.

Files:

  • src/app/sitemap.ts
🧬 Code graph analysis (1)
src/app/sitemap.ts (1)
src/features/listings/utils/region.ts (1)
  • getAllRegionSlugs (184-195)
🔇 Additional comments (3)
src/app/sitemap.ts (3)

5-5: LGTM!

The import is correctly structured and the function is properly utilized throughout the file.


82-89: LGTM!

The regions count fetching and sitemap generation follows the same consistent pattern as listings, sponsors, and grants. The parallel fetching in Promise.all is efficient.

Also applies to: 120-124


138-139: LGTM!

The SitemapBoundaries interface update and boundary calculation logic are correct and follow the established pattern for other content types.

Also applies to: 178-192

@JayeshVP24 JayeshVP24 merged commit c5991d1 into main Nov 18, 2025
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Dec 1, 2025
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