Skip to content

#324 [landing-page][Page] Compose landing page and states FIXED#328

Open
felladaniel36-hash wants to merge 9 commits into
boundlessfi:mainfrom
felladaniel36-hash:#324-landing-page-PAGE-Compose-landing-page-and-states-FIX
Open

#324 [landing-page][Page] Compose landing page and states FIXED#328
felladaniel36-hash wants to merge 9 commits into
boundlessfi:mainfrom
felladaniel36-hash:#324-landing-page-PAGE-Compose-landing-page-and-states-FIX

Conversation

@felladaniel36-hash

@felladaniel36-hash felladaniel36-hash commented Jul 26, 2026

Copy link
Copy Markdown

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 main branch. 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.ts has content?: never for the GET /leaderboard and GET /projects/featured responses. This means the backend endpoints exist and are documented in ENDPOINTS.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 once npm run codegen:snapshot is re-run against an updated OpenAPI document.

3. The existing CtaSection serves a different purpose.
components/marketing/cta-section.tsx renders "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 new WantToBuild component 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 in package.json. No new tests were introduced because the testing toolchain does not exist in the active app.

5. BuilderCard needs a view-model adapter.
The BuilderCard component consumes BuilderCardView (defined in components/cards/types.ts), which expects fields like displayName, avatarSrc, detailUrl. The leaderboard API returns different field names (avatarUrl, followerCount, projectCount). The toCardView() function in top-builders.tsx bridges this gap. The same applies to FeaturedProjects mapping to OpportunityCardView.


Fix Features

Section composition in order:

  • app/page.tsx renders: SiteHeader → HeroBackground (with fadeBottom) → StatsStrip → TopBuilders → FeaturedProjects → WantToBuild → SiteFooter

Self-contained data fetching:

  • StatsStrip fetches from GET /discover/landing via TanStack Query
  • TopBuilders fetches from GET /leaderboard?limit=6&timeframe=ALL_TIME via TanStack Query
  • FeaturedProjects fetches from GET /projects/featured?limit=6 via TanStack Query
  • WantToBuild is a static server component with no data dependencies

Loading skeletons prevent layout shift:

  • StatsStrip renders 4 skeleton stat columns
  • TopBuilders renders a 3-column grid of BuilderCardSkeleton
  • FeaturedProjects renders a 3-column grid of OpportunityCardSkeleton

Empty and error states:

  • Each data section shows a centered muted message when the fetch fails or returns zero results

Scroll-in animations:

  • Every section after the hero is wrapped in <Reveal> from components/marketing/reveal.tsx
  • Respects prefers-reduced-motion automatically

Gradient continuity:

  • StatsStrip uses 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 section
  • WantToBuild uses bg-[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 footer

Consistent vertical rhythm:

  • All sections use the shared <Section> component providing px-5 py-16 lg:px-[100px] lg:py-20 on the 8pt grid

Design system compliance:

  • Zero new styles or one-off UI in the route
  • All buttons, cards, skeletons, sections, and marketing components are reused from existing primitives
  • Single quotes, JSX single quotes, no em dashes, 80-col width

Barrel export:

  • components/landing/index.ts exports all four sections, following the design system's feature-first convention where consumers import from the index, not internal paths

CLOSE #324

Summary by CodeRabbit

  • New Features
    • Added new landing-page sections: ecosystem statistics, top builders, featured projects, and a “Want to Be a Builder?” call-to-action.
    • Top builders and featured projects now load asynchronously with skeleton placeholders, plus empty/error messaging when no results are available.
    • Featured projects show key details (including deadlines when present) and link to view all projects; top builders link to view all builders.
    • New sections are wrapped with reveal transitions for animated entry.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@felladaniel36-hash is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 26, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@felladaniel36-hash, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d6dd6550-1e53-4431-931b-bb4014fcc89a

📥 Commits

Reviewing files that changed from the base of the PR and between aa1d816 and 0ab4629.

📒 Files selected for processing (3)
  • components/landing/to-builder-card.ts
  • components/landing/top-builders.tsx
  • components/landing/want-to-build.tsx
📝 Walkthrough

Walkthrough

The 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.

Changes

Landing page sections

Layer / File(s) Summary
Landing statistics section
components/landing/stats-strip.tsx
Fetches /discover/landing and renders formatted totals with loading and fallback states.
Top builders section
components/landing/top-builders.tsx
Fetches six all-time leaderboard entries, maps them to builder cards, and links to /builders.
Featured projects section
components/landing/featured-projects.tsx
Fetches six featured projects, maps them to opportunity cards, formats deadlines, and links to /projects.
Landing page composition and CTA
app/page.tsx, components/landing/index.ts, components/landing/want-to-build.tsx
Adds the sections in order with Reveal wrappers and provides CTA links to /about and /opportunities.

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
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: benjtalkshow

Poem

I’m a rabbit hopping through the page,
Past builder cards on a lively stage.
Stats bloom, projects gleam,
CTAs invite the dream—
Four bright sections take center stage!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects composing the landing page and related state changes.
Linked Issues check ✅ Passed The PR composes app/page.tsx in the requested order and adds the section components and Reveal wrappers.
Out of Scope Changes check ✅ Passed The added landing section components and barrel exports are all within the linked landing-page scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between af446e4 and 3da6fc6.

📒 Files selected for processing (6)
  • app/page.tsx
  • components/landing/featured-projects.tsx
  • components/landing/index.ts
  • components/landing/stats-strip.tsx
  • components/landing/top-builders.tsx
  • components/landing/want-to-build.tsx

Comment on lines +62 to +68
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`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
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.

Comment on lines +31 to +45
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>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

@Benjtalkshow

Copy link
Copy Markdown
Contributor

@felladaniel36-hash
Your issue depends on every other landing page issues.
You need to read the issue description carefully to undertsand what you are to do.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Remove the duplicate leaderboard implementation.

Both the previous and refactored useTopBuilders/TopBuilders implementations 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 win

Fetch the required six all-time builders.

The current query uses limit=8 and omits timeframe=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 win

Preserve the optional className prop.

The final TopBuilders export removes the earlier { className?: string } API, so callers that provide section styling will fail type-checking. Restore the prop and pass it to Section.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3da6fc6 and aa1d816.

📒 Files selected for processing (1)
  • components/landing/top-builders.tsx

@felladaniel36-hash

Copy link
Copy Markdown
Author

PLEASE REVIEW

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.

[landing-page][Page] Compose landing page and states

2 participants