#324 [landing-page][Page] Compose landing page and states FIXED#328
Conversation
|
@felladaniel36-hash is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
@felladaniel36-hash Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThe landing page now renders statistics, top builders, featured projects, and a builder CTA between the hero and footer. The data-backed sections fetch API data with React Query and provide loading, empty, and error states. ChangesLanding page sections
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Home
participant StatsAPI
participant LeaderboardAPI
participant ProjectsAPI
Home->>StatsAPI: fetch landing statistics
StatsAPI-->>Home: render StatsStrip
Home->>LeaderboardAPI: fetch six all-time builders
LeaderboardAPI-->>Home: render TopBuilders
Home->>ProjectsAPI: fetch six featured projects
ProjectsAPI-->>Home: render FeaturedProjects
Home->>Home: render WantToBuild CTA
Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/landing/featured-projects.tsx`:
- Around line 62-68: Update the landing page component using formatDeadline to
maintain a minute-based now state, refresh it with an interval, and clean up the
interval on unmount. Change formatDeadline to accept the current timestamp and
use that value instead of calling Date.now(), then pass the state into each
deadline calculation so countdowns and expiry update while the page remains
open.
In `@components/landing/stats-strip.tsx`:
- Around line 31-45: Update StatsStripSkeleton to reuse StatsBar’s bordered,
rounded outer container and matching responsive row/column layout, including its
mobile spacing. Keep the existing four Skeleton items inside that wrapper and
preserve their loading-content structure.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f44de716-52e3-4fb9-add3-ff433d7e8763
📒 Files selected for processing (6)
app/page.tsxcomponents/landing/featured-projects.tsxcomponents/landing/index.tscomponents/landing/stats-strip.tsxcomponents/landing/top-builders.tsxcomponents/landing/want-to-build.tsx
| function formatDeadline(deadline: string): string { | ||
| const diff = new Date(deadline).getTime() - Date.now(); | ||
| if (diff <= 0) return ''; | ||
| const days = Math.floor(diff / 86_400_000); | ||
| const hours = Math.floor((diff % 86_400_000) / 3_600_000); | ||
| const minutes = Math.floor((diff % 3_600_000) / 60_000); | ||
| return `${days}D:${String(hours).padStart(2, '0')}H:${String(minutes).padStart(2, '0')}M`; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep deadline countdowns current.
Line 63 samples Date.now() only during render. staleTime does not itself cause a re-render when five minutes pass, so displayed minutes—and eventually expiry—can remain stale on an open landing page. Maintain a minute-based now state with interval cleanup and pass it into formatDeadline.
Proposed fix
-import { useQuery } from '`@tanstack/react-query`';
+import { useEffect, useState } from 'react';
+import { useQuery } from '`@tanstack/react-query`';
-function toCardView(project: FeaturedProject): OpportunityCardView {
+function toCardView(
+ project: FeaturedProject,
+ now: number
+): OpportunityCardView {
// ...
- endsIn: project.deadline ? formatDeadline(project.deadline) : '',
+ endsIn: project.deadline ? formatDeadline(project.deadline, now) : '',
// ...
}
-function formatDeadline(deadline: string): string {
- const diff = new Date(deadline).getTime() - Date.now();
+function formatDeadline(deadline: string, now: number): string {
+ const diff = new Date(deadline).getTime() - now;
export function FeaturedProjects({ className }: { className?: string }) {
+ const [now, setNow] = useState(() => Date.now());
+ useEffect(() => {
+ const interval = window.setInterval(() => setNow(Date.now()), 60_000);
+ return () => window.clearInterval(interval);
+ }, []);
+
// ...
- opportunity={toCardView(project)}
+ opportunity={toCardView(project, now)}📝 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.
| function formatDeadline(deadline: string): string { | |
| const diff = new Date(deadline).getTime() - Date.now(); | |
| if (diff <= 0) return ''; | |
| const days = Math.floor(diff / 86_400_000); | |
| const hours = Math.floor((diff % 86_400_000) / 3_600_000); | |
| const minutes = Math.floor((diff % 3_600_000) / 60_000); | |
| return `${days}D:${String(hours).padStart(2, '0')}H:${String(minutes).padStart(2, '0')}M`; | |
| function formatDeadline(deadline: string, now: number): string { | |
| const diff = new Date(deadline).getTime() - now; | |
| if (diff <= 0) return ''; | |
| const days = Math.floor(diff / 86_400_000); | |
| const hours = Math.floor((diff % 86_400_000) / 3_600_000); | |
| const minutes = Math.floor((diff % 3_600_000) / 60_000); | |
| return `${days}D:${String(hours).padStart(2, '0')}H:${String(minutes).padStart(2, '0')}M`; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/landing/featured-projects.tsx` around lines 62 - 68, Update the
landing page component using formatDeadline to maintain a minute-based now
state, refresh it with an interval, and clean up the interval on unmount. Change
formatDeadline to accept the current timestamp and use that value instead of
calling Date.now(), then pass the state into each deadline calculation so
countdowns and expiry update while the page remains open.
| function StatsStripSkeleton() { | ||
| return ( | ||
| <div className='flex flex-col gap-1 sm:flex-row'> | ||
| {Array.from({ length: 4 }).map((_, i) => ( | ||
| <div | ||
| key={i} | ||
| className='flex flex-1 flex-col gap-2 px-6 py-5' | ||
| > | ||
| <Skeleton className='h-5 w-24' /> | ||
| <Skeleton className='h-9 w-20' /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Match the skeleton geometry to StatsBar.
Line 31 lacks StatsBar’s bordered, rounded wrapper; its mobile spacing also differs. The strip visibly shifts when data resolves. Reuse the loaded component’s outer container and responsive row/column layout with Skeleton content inside.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/landing/stats-strip.tsx` around lines 31 - 45, Update
StatsStripSkeleton to reuse StatsBar’s bordered, rounded outer container and
matching responsive row/column layout, including its mobile spacing. Keep the
existing four Skeleton items inside that wrapper and preserve their
loading-content structure.
|
@felladaniel36-hash |
…nding-page-and-states-FIX
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
components/landing/top-builders.tsx (3)
4-45: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick winRemove the duplicate leaderboard implementation.
Both the previous and refactored
useTopBuilders/TopBuildersimplementations remain in the file. Biome reports parse errors at Line 64, and the duplicate declarations will prevent the module from compiling. Keep one import block, one hook, one skeleton, and one exported component.Also applies to: 47-63, 64-95, 98-145
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/landing/top-builders.tsx` around lines 4 - 45, Remove the duplicate leaderboard implementation from the file, keeping only one import block, one useTopBuilders hook, one loading skeleton, and one exported TopBuilders component. Preserve the refactored implementation using the visible BuilderCardView, apiFetch, Paginated, Section, and SectionHeading symbols, and delete the obsolete duplicate declarations causing the parse errors.Source: Linters/SAST tools
74-82: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winFetch the required six all-time builders.
The current query uses
limit=8and omitstimeframe=ALL_TIME, conflicting with the landing-page contract. Use a limit of 6, add the timeframe parameter, and include the timeframe in the query key.Suggested fix
-const TOP_BUILDERS_LIMIT = 8; +const TOP_BUILDERS_LIMIT = 6; - queryKey: ['leaderboard', 'top-builders', TOP_BUILDERS_LIMIT], + queryKey: [ + 'leaderboard', + 'top-builders', + TOP_BUILDERS_LIMIT, + 'ALL_TIME', + ], - `/leaderboard?limit=${TOP_BUILDERS_LIMIT}` + `/leaderboard?limit=${TOP_BUILDERS_LIMIT}&timeframe=ALL_TIME`🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/landing/top-builders.tsx` around lines 74 - 82, Update useTopBuilders and TOP_BUILDERS_LIMIT to fetch exactly six all-time builders by setting the limit to 6 and adding timeframe=ALL_TIME to the leaderboard request URL. Include the ALL_TIME timeframe in the queryKey so caching distinguishes this query.
145-150: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winPreserve the optional
classNameprop.The final
TopBuildersexport removes the earlier{ className?: string }API, so callers that provide section styling will fail type-checking. Restore the prop and pass it toSection.Suggested fix
-export function TopBuilders() { +export function TopBuilders({ className }: { className?: string }) { ... - <Section innerClassName='flex flex-col gap-8'> + <Section className={className} innerClassName='flex flex-col gap-8'>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/landing/top-builders.tsx` around lines 145 - 150, Restore the optional className prop on the TopBuilders component and pass it through to the rendered Section component, preserving existing section styling while keeping current behavior for callers that omit it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@components/landing/top-builders.tsx`:
- Around line 4-45: Remove the duplicate leaderboard implementation from the
file, keeping only one import block, one useTopBuilders hook, one loading
skeleton, and one exported TopBuilders component. Preserve the refactored
implementation using the visible BuilderCardView, apiFetch, Paginated, Section,
and SectionHeading symbols, and delete the obsolete duplicate declarations
causing the parse errors.
- Around line 74-82: Update useTopBuilders and TOP_BUILDERS_LIMIT to fetch
exactly six all-time builders by setting the limit to 6 and adding
timeframe=ALL_TIME to the leaderboard request URL. Include the ALL_TIME
timeframe in the queryKey so caching distinguishes this query.
- Around line 145-150: Restore the optional className prop on the TopBuilders
component and pass it through to the rendered Section component, preserving
existing section styling while keeping current behavior for callers that omit
it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 16a055f6-34ce-4ff6-ae0d-1a39fba74e42
📒 Files selected for processing (1)
components/landing/top-builders.tsx
|
PLEASE REVIEW |
Findings
1.
components/landing/directory did not exist.The issue assumed these section components would already be created by sibling issues (top builders, featured projects, stats strip, CTA band). None had landed yet on the
mainbranch. The directory and all four section components had to be created from scratch.2. API response shapes are partially open in the generated schema.
The generated
schema.d.tshascontent?: neverfor theGET /leaderboardandGET /projects/featuredresponses. This means the backend endpoints exist and are documented inENDPOINTS.md, but the TypeScript response types are stubs. The section components define their own local interfaces (LeaderboardEntry,FeaturedProject,DiscoverLandingDto) that will align with the real shapes oncenpm run codegen:snapshotis re-run against an updated OpenAPI document.3. The existing
CtaSectionserves a different purpose.components/marketing/cta-section.tsxrenders "Your Next Opportunity Starts Here" linking to/opportunities. The spec called for a distinct "Want to be a builder?" CTA band with different copy ("Join the Boundless ecosystem. Create a profile, showcase your work, and discover opportunities to build, earn, and grow."). A newWantToBuildcomponent was created following the same visual anatomy: rounded card, badge pill, heading, description, marketing button.4. No test infrastructure in the active codebase.
All test files live under
archive/from the previous app iteration. There is no test runner (jest,vitest,playwright) configured inpackage.json. No new tests were introduced because the testing toolchain does not exist in the active app.5.
BuilderCardneeds a view-model adapter.The
BuilderCardcomponent consumesBuilderCardView(defined incomponents/cards/types.ts), which expects fields likedisplayName,avatarSrc,detailUrl. The leaderboard API returns different field names (avatarUrl,followerCount,projectCount). ThetoCardView()function intop-builders.tsxbridges this gap. The same applies toFeaturedProjectsmapping toOpportunityCardView.Fix Features
Section composition in order:
app/page.tsxrenders: SiteHeader → HeroBackground (with fadeBottom) → StatsStrip → TopBuilders → FeaturedProjects → WantToBuild → SiteFooterSelf-contained data fetching:
GET /discover/landingvia TanStack QueryGET /leaderboard?limit=6&timeframe=ALL_TIMEvia TanStack QueryGET /projects/featured?limit=6via TanStack QueryLoading skeletons prevent layout shift:
BuilderCardSkeletonOpportunityCardSkeletonEmpty and error states:
Scroll-in animations:
<Reveal>fromcomponents/marketing/reveal.tsxprefers-reduced-motionautomaticallyGradient continuity:
bg-[linear-gradient(180deg,rgba(46,237,170,0.08)_0%,rgba(13,17,17,0)_100%)]so the hero's teal glow transitions smoothly into the first content sectionbg-[linear-gradient(180deg,rgba(46,237,170,0.08)_0%,rgba(13,17,17,0)_50%)]so the teal tint resolves back to ink before the footerConsistent vertical rhythm:
<Section>component providingpx-5 py-16 lg:px-[100px] lg:py-20on the 8pt gridDesign system compliance:
Barrel export:
components/landing/index.tsexports all four sections, following the design system's feature-first convention where consumers import from the index, not internal pathsCLOSE #324
Summary by CodeRabbit