Skip to content

feat(ui): redesign landing page sections - #1791

Merged
smakosh merged 2 commits into
mainfrom
feat/landing-page-redesign
Mar 4, 2026
Merged

smakosh merged 2 commits into
mainfrom
feat/landing-page-redesign

Conversation

@smakosh

@smakosh smakosh commented Mar 4, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Redesign all landing page sections below the hero with improved visual design: display font (Plus Jakarta Sans), atmospheric backgrounds, two-tier card grids, marquee testimonials, sticky code block, and scroll-triggered animations
  • Update stats and copy across landing, enterprise, and pricing pages to match actual codebase data (210+ models, 25 providers, 3 free models, BYOK is free)
  • Add discount form enhancements, public discounts API route, and global discount banner
  • Fix BYOK pricing copy in comparison page and changelog (was incorrectly showing "1% tracking fee" — code confirms BYOK is free)

Test plan

  • Verify all landing page sections render correctly in light and dark mode
  • Test responsive breakpoints: mobile (375px), tablet (768px), desktop (1280px+)
  • Verify marquee animation pauses on hover, edge fade masks work
  • Verify sticky code block behavior in code-example section
  • Check all links, accordion, code copy button still work
  • Verify provider icons display correctly in hero section (23 total)
  • Run pnpm build to confirm no build errors

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Discount expirations with countdown timers and "Expires" column in admin dashboards
    • New "Your Discounts" dashboard and responsive discount views (cards on mobile, table on desktop)
    • Global discount banners on model pages showing live expiry countdowns
    • Public API endpoints to fetch active discounts
  • Updates

    • Platform messaging updated to 210+ models across 25+ providers
    • Bring Your Own Keys is now free (no 1% tracking fee)
    • Landing, pricing, and marketing UI refreshed with new visuals and animations
  • Other

    • SOC 2 Type II indicator added in enterprise features

Elevate visual design across all landing page sections below the hero
with better layout variety, atmospheric backgrounds, improved typography
hierarchy, and scroll-triggered animations.

- Add Plus Jakarta Sans display font and CSS utilities (noise texture,
  surface-elevated token, font-display utility)
- Features: two-tier card grid with blur-slide animations
- Graph: left-aligned heading, stats row, gradient background
- Code example: split layout with sticky code block, macOS-style dots
- Testimonials: dual marquee rows with edge fade masks
- FAQ: two-column layout with sticky heading
- CTA: atmospheric background with ShimmerButton
- Footer: gradient separator, reorganized bottom bar
- Hero: add 6 missing provider icons (23 total)
- Enterprise: update stats, add SOC2 Type II card
- Pricing: accurate model counts (210+), free models (3)
- Fix BYOK pricing across comparison and changelog (free, not 1% fee)
- Add discount form, public discounts API, and global discount banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 4, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds discount expiry support end-to-end (form, server actions, API routes, UI lists/banners, countdown timers), new organization discounts pages/components, and multiple landing UI/design updates (fonts, tokens, marquee, testimonials, hero, footer, features, and copy).

Changes

Cohort / File(s) Summary
Admin discount UI & form
apps/admin/src/app/discounts/page.tsx, apps/admin/src/app/organizations/[orgId]/discounts/page.tsx, apps/admin/src/components/discount-form.tsx
Adds optional expiresAt to discount creation flow and form (datetime-local input), propagates it through server actions, adds an "Expires" table column, and adjusts table colspan and row rendering (formatted/expired/“Never”).
API routes (public/org)
apps/api/src/routes/public-discounts.ts, apps/api/src/routes/organization.ts, apps/api/src/index.ts
New GET endpoints to list global and org discounts with notExpired filtering (expiresAt null or >= now); org route enforces org access. Registers /public/discounts and /orgs/{id}/discounts.
Org discounts UI & client components
apps/ui/src/app/dashboard/[orgId]/org/discounts/page.tsx, apps/ui/src/app/dashboard/[orgId]/org/discounts/discounts-client.tsx, apps/ui/src/components/models/global-discount-banner.tsx
New OrgDiscountsPage and DiscountsClient (cards on mobile, table on desktop), plus GlobalDiscountBanner that fetches model discounts and renders percentage + Countdown.
Countdown & icon/nav changes
apps/ui/src/components/countdown.tsx, apps/ui/src/components/dashboard/animated-nav-icons.tsx, apps/ui/src/components/dashboard/dashboard-sidebar.tsx
Adds Countdown component (live timer with adaptive tick interval), introduces AnimatedPercent icon (duplicated export present), and adds "Your Discounts" nav item using the new icon.
Landing/marketing UI and components
apps/ui/src/components/landing/code-example.tsx, apps/ui/src/components/landing/features.tsx, apps/ui/src/components/landing/graph.tsx, apps/ui/src/components/landing/cta.tsx, apps/ui/src/components/landing/faq.tsx, apps/ui/src/components/landing/footer.tsx, apps/ui/src/components/landing/hero.tsx, apps/ui/src/components/landing/testimonials.tsx, apps/ui/src/components/landing/marquee-container.tsx
Extensive landing page redesigns: two-column code example with copy feedback, tiered features layout, revamped graph/hero/CTA/footer/FAQ, new MarqueeContainer, and testimonials converted to dual marquees.
Design system & layout
apps/ui/src/app/globals.css, apps/ui/src/app/layout.tsx
Adds Plus Jakarta Sans font (--font-display), surface-elevation tokens, and utilities (font-display, bg-noise, bg-surface-elevated); injects font variable into RootLayout.
Copy/metrics/pricing tweaks
apps/ui/src/components/landing/comparison.tsx, apps/ui/src/components/pricing/pricing-table.tsx, apps/ui/src/components/enterprise/features.tsx, apps/ui/src/components/enterprise/hero.tsx, apps/ui/src/content/changelog/2026-01-21-pro-features-now-free.md, apps/ui/src/app/models/[name]/page.tsx
Updates BYOK/pricing copy (1% → free), model counts (180+ → 210+), enterprise metrics/text, adds SOC2 feature badge, and embeds GlobalDiscountBanner on model pages.
API surface / infra changes
apps/api/src/index.ts
Registers new public discounts route (note: appears registered in two places in diff).

Sequence Diagram(s)

sequenceDiagram
  participant AdminUI as Admin Form (browser)
  participant ServerAction as Next.js Server Action
  participant API as API Route (/orgs/{id}/discounts / public)
  participant DB as Database

  AdminUI->>ServerAction: submit create discount (provider, model, percent, reason, expiresAt)
  ServerAction->>API: call createOrganizationDiscount (payload incl. expiresAt)
  API->>DB: INSERT discount row (expiresAt)
  DB-->>API: created discount
  API-->>ServerAction: success
  ServerAction-->>AdminUI: render/redirect to discounts list

  Note over AdminUI,API: Listing discounts (org or public)
  AdminUI->>API: GET /orgs/{id}/discounts or /public/discounts?modelId=...
  API->>DB: SELECT discounts WHERE organizationId = ? OR NULL AND (expiresAt IS NULL OR expiresAt >= now)
  DB-->>API: discounts[]
  API-->>AdminUI: discounts[] (used by DiscountsClient / GlobalDiscountBanner)
  AdminUI->>AdminUI: Countdown component renders live remaining time for each expiresAt
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • steebchen
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(ui): redesign landing page sections' accurately reflects the primary change: a comprehensive redesign of landing page sections with new visual design, animations, fonts, and component layouts.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/landing-page-redesign

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.

@smakosh smakosh self-assigned this Mar 4, 2026

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 16

Caution

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

⚠️ Outside diff range comments (1)
apps/ui/src/components/pricing/pricing-table.tsx (1)

216-219: 🛠️ Refactor suggestion | 🟠 Major

Remove unnecessary as any cast on Link href.

Line 218 weakens type safety unnecessarily. The conditional guard at line 216 (feature.learnMoreLink &&) already narrows the type to string, which is a valid type for the Next.js Link component's href prop. Pass it directly without the cast.

Proposed fix
-											<Link
-												href={feature.learnMoreLink as any}
-												className="text-xs text-blue-600 hover:underline"
-											>
+											<Link
+												href={feature.learnMoreLink}
+												className="text-xs text-blue-600 hover:underline"
+											>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/pricing/pricing-table.tsx` around lines 216 - 219,
Remove the unnecessary type cast on the Next.js Link href: the conditional guard
around feature.learnMoreLink already narrows it to a valid string, so change the
Link usage in the pricing table (the JSX using feature.learnMoreLink and Link)
to pass href={feature.learnMoreLink} directly instead of
href={feature.learnMoreLink as any}; this restores type safety without other
logic changes.
🧹 Nitpick comments (6)
apps/ui/src/components/landing/faq.tsx (2)

94-101: Extract duplicated trigger className to a constant.

The same ~280-character className string is repeated across all 4 AccordionPrimitive.Trigger elements (lines 94, 132, 157, 177). Extract to a constant for maintainability.

♻️ Proposed refactor

Add a constant before the component:

const triggerClassName =
	"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-center justify-between gap-4 rounded-md py-2 text-left font-display text-lg md:text-xl font-medium leading-7 transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&>svg>path:last-child]:origin-center [&>svg>path:last-child]:transition-all [&>svg>path:last-child]:duration-200 [&[data-state=open]>svg]:rotate-180 [&[data-state=open]>svg>path:last-child]:rotate-90 [&[data-state=open]>svg>path:last-child]:opacity-0 text-foreground";

Then use it in each trigger:

-<AccordionPrimitive.Trigger className="focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-center justify-between gap-4 rounded-md py-2 text-left font-display text-lg md:text-xl font-medium leading-7 transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&>svg>path:last-child]:origin-center [&>svg>path:last-child]:transition-all [&>svg>path:last-child]:duration-200 [&[data-state=open]>svg]:rotate-180 [&[data-state=open]>svg>path:last-child]:rotate-90 [&[data-state=open]>svg>path:last-child]:opacity-0 text-foreground">
+<AccordionPrimitive.Trigger className={triggerClassName}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/landing/faq.tsx` around lines 94 - 101, Extract the
long duplicated className string into a single constant (e.g., triggerClassName)
and replace each AccordionPrimitive.Trigger's inline className with that
constant; locate all uses of AccordionPrimitive.Trigger in this file and update
their className prop to use triggerClassName to remove duplication and improve
maintainability.

60-61: Consider removing structural comments.

Comments like {/* Left column: sticky heading */}, {/* Item 1 */}, etc. describe structure that's already evident from the code (grid classes, value="item-1"). As per coding guidelines, avoid unnecessary code comments.

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

In `@apps/ui/src/components/landing/faq.tsx` around lines 60 - 61, Remove
unnecessary structural comments in the FAQ component (e.g., the JSX comments
like {/* Left column: sticky heading */} and {/* Item 1 */}) that duplicate
information already expressed by markup and classNames (see the div with
className "lg:col-span-2 lg:sticky lg:top-24 lg:self-start" and the elements
using value="item-1"); delete these redundant comments and keep only comments
that add non-obvious, actionable context.
apps/ui/src/components/landing/cta.tsx (1)

13-20: Remove decorative inline comments in JSX.

Lines 13, 16, and 19 add comments that restate obvious markup intent and create noise in this component.

As per coding guidelines, "No unnecessary code comments".

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

In `@apps/ui/src/components/landing/cta.tsx` around lines 13 - 20, In the CTA
component remove the three decorative inline JSX comments that restate obvious
markup (the comment nodes immediately preceding the three absolute divs for the
gradient separator, atmospheric background, and soft radial glow) — delete the
comment lines like {/* Gradient separator at top */}, {/* Atmospheric background
*/}, and {/* Soft radial glow */} so only the div elements remain; do not alter
the div attributes or layout logic in this file.
apps/ui/src/components/landing/features.tsx (1)

382-385: Use stable keys instead of index keys in both card maps.

key={i} is fragile if ordering changes; feature.slug is a stable, unique key here. All slugs in the file are unique, making this refactor safe.

Proposed refactor
-					{tier1Features.map((feature, i) => (
+					{tier1Features.map((feature) => (
 						<FeaturedCard
-							key={i}
+							key={feature.slug}
 							icon={feature.icon}
 							title={feature.title}
 							description={feature.description}
-					{tier2Features.map((feature, i) => (
+					{tier2Features.map((feature) => (
 						<CompactCard
-							key={i}
+							key={feature.slug}
 							icon={feature.icon}
 							title={feature.title}
 							description={feature.description}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/landing/features.tsx` around lines 382 - 385, Replace
unstable index keys used in the FeaturedCard maps with the stable unique slug:
in the tier1Features.map where FeaturedCard currently uses key={i}, change it to
use key={feature.slug}; do the same in the other feature card map (the second
map rendering FeaturedCard) so both use feature.slug as the key to ensure stable
identity when ordering changes.
apps/ui/src/components/landing/testimonials.tsx (1)

22-23: Remove purely descriptive JSX comments in this section.

These comments restate obvious structure and can be dropped to match repo style.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: No unnecessary code comments".

Also applies to: 36-37, 48-49

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

In `@apps/ui/src/components/landing/testimonials.tsx` around lines 22 - 23, Remove
the purely descriptive JSX comment nodes that restate structure: delete the {/*
Noise texture */} comment immediately preceding the div with className "absolute
inset-0 bg-noise" and the similar descriptive comments around the other elements
referenced in the review (the comment blocks around lines 36-37 and 48-49). Keep
the JSX elements (e.g., the div with className "absolute inset-0 bg-noise")
intact and only remove the unnecessary {/* ... */} comment nodes to comply with
the repo guideline about unnecessary comments.
apps/ui/src/components/landing/graph.tsx (1)

78-85: Drop section-label JSX comments for consistency with repo style.

These comments are structural labels and can be removed without losing clarity.

As per coding guidelines, "**/*.{ts,tsx,js,jsx}: No unnecessary code comments".

Also applies to: 115-118

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

In `@apps/ui/src/components/landing/graph.tsx` around lines 78 - 85, Remove the
unnecessary JSX comments that label sections in
apps/ui/src/components/landing/graph.tsx (e.g., the "Gradient background",
"Noise texture", "Header" comments adjacent to the <div className="absolute
inset-0 bg-gradient-to-b from-background via-surface-elevated to-background" />,
<div className="absolute inset-0 bg-noise" />, and the <div
className="relative"> container) and also drop the similar comments around the
elements at the 115–118 region; leave the JSX elements and className attributes
intact so only the comment nodes are deleted for consistency with the repo style
rule forbidding unnecessary comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/index.ts`:
- Around line 224-225: The /public/discounts route is currently mounted directly
with app.route("/public/discounts", publicDiscounts); ensure this is intentional
and not duplicated in the authenticated routes aggregator by confirming
publicDiscounts is not exported or included in apps/api/src/routes/index.ts; if
it should be public keep only this mount, otherwise move the mounting into the
routes index and remove this line. After making the change, run pnpm build to
verify the API routing and ensure no duplicate registrations or build-time route
errors occur.

In `@apps/api/src/routes/organization.ts`:
- Around line 801-811: Replace the direct SQL-style reads using
db.select().from(tables.discount) with the repository-standard
db.query.discount.findMany() calls: for the org-specific query (currently using
and(eq(tables.discount.organizationId, id), notExpired)) call
db.query.discount.findMany() with a where that composes the same conditions
(organizationId equals id plus the notExpired predicate) and orderBy createdAt
descending; for the global query (currently using
and(isNull(tables.discount.organizationId), notExpired)) call
db.query.discount.findMany() with a where that composes organizationId is null
plus the notExpired predicate and orderBy createdAt descending. Ensure you reuse
the existing notExpired expression and the same ordering (desc on createdAt) so
behavior stays identical.

In `@apps/api/src/routes/public-discounts.ts`:
- Around line 51-61: Replace the raw query using
db.select().from(tables.discount) with the canonical query API
db().query.discount.findMany(); pass the same filters by converting the where
clause into the findMany's where argument: require organizationId to be null,
model to be null or equal to modelId, and include the existing notExpired
condition, and apply the same ordering by createdAt descending (use the
findMany/orderBy option). Update the call site that currently references
tables.discount and notExpired so the conditions map to the findMany where
object and orderBy: { createdAt: 'desc' }.
- Around line 57-61: The query currently only orders by createdAt, causing newer
global discounts to appear before older model-specific ones; change the .orderBy
call to first sort by specificity (model-specific discounts matching modelId
before all-model/null ones) and then by createdAt DESC, e.g. add a primary order
key like desc(eq(tables.discount.model, modelId)) (or
desc(isNotNull(tables.discount.model) && eq(...))) ahead of
desc(tables.discount.createdAt) so model-specific discounts are prioritized.

In `@apps/ui/src/app/dashboard/`[orgId]/org/discounts/page.tsx:
- Line 25: Remove the unnecessary "as any" cast on the "/orgs/{id}/discounts"
endpoint string: locate the code using the literal "/orgs/{id}/discounts" (same
pattern used for "/orgs/{id}/transactions" and "/orgs/{id}/projects") and delete
the "as any" so the call uses the generated api/v1 types directly; ensure the
call signature compiles against the existing GET type for that path and adjust
any surrounding generics if TypeScript still needs explicit typing.

In `@apps/ui/src/app/globals.css`:
- Line 11: Remove the self-referential CSS custom property assignment for
--font-display (the line "--font-display: var(--font-display);"); it's invalid
and unnecessary because the display font variable is already injected elsewhere,
so delete that declaration and leave the other aliasing lines (e.g.,
--font-geist-mono → --font-mono, --font-inter → --font-sans) unchanged.

In `@apps/ui/src/components/countdown.tsx`:
- Around line 9-16: The getTimeRemaining function and the countdown component
must immediately react to changes and guard against invalid dates: update
getTimeRemaining (used by the Countdown component) to check new
Date(expiresAt).getTime() for NaN and return expired: true with zeroed fields
when invalid or when end <= now; compute diff and integer
days/hours/minutes/seconds only for valid numbers. In the Countdown component
(useEffect that sets the interval), call getTimeRemaining(expiresAt) and set
state immediately when expiresAt changes (before creating the interval) and then
start the interval for subsequent ticks; also clear the timer on unmount or when
expiresAt changes to avoid stale timers.

In `@apps/ui/src/components/landing/code-example.tsx`:
- Around line 246-247: Remove the non-essential JSX inline comments inside the
component markup (e.g., the comment before the AnimatedGroup JSX and other
similar comments around lines referenced like the ones near AnimatedGroup) so
the UI component contains only necessary markup and developer-facing comments
are eliminated; locate occurrences within the component (search for
AnimatedGroup and surrounding JSX blocks and any JSX comments like {/* ... */})
and delete those comment nodes (also remove the other noted JSX comments around
the other column/tabs/bullets sections) leaving the JSX elements and their props
intact.
- Around line 370-375: Replace the inline explicit types (className: string;
style: CSSProperties; tokens: any[]; getLineProps: (props: any) => any;
getTokenProps: (props: any) => any) used in the Highlight render-prop callback
with the exported RenderProps type from prism-react-renderer: import RenderProps
from 'prism-react-renderer' (or named import if applicable) and change the
callback signature to accept a single parameter typed as RenderProps; do the
same replacement for the other occurrence referenced (lines ~388–394) so both
render-prop callbacks use RenderProps instead of any.

In `@apps/ui/src/components/landing/cta.tsx`:
- Line 53: The anchor currently uses href={config.githubUrl ?? ""} with
target="_blank" which can cause same-page navigation when config.githubUrl is
missing and lacks rel attributes; update the CTA anchor (the <a href={...}
target="_blank"> in cta.tsx) to only render when config.githubUrl is a non-empty
string (or disable the link) and add rel="noopener noreferrer" to the anchor
when using target="_blank"; e.g., guard the link by conditionally rendering the
<a> around the clickable content using config.githubUrl truthiness or use a safe
fallback that does not open a new tab, and include rel="noopener noreferrer"
whenever target="_blank" is present.

In `@apps/ui/src/components/landing/features.tsx`:
- Line 354: In the Features component JSX
(apps/ui/src/components/landing/features.tsx) remove non-essential inline JSX
comments such as the section-label comments (e.g. {/* Dot grid background */}
and the other similar comments around the block rendered at the same area),
leaving the markup clean; locate the JSX inside the Features functional
component (or the component returned markup) and delete those comment nodes
(including the ones referenced near the other two occurrences) so only
meaningful comments or explanatory code remain.

In `@apps/ui/src/components/landing/footer.tsx`:
- Around line 176-180: The anchor elements in footer.tsx that use
target="_blank" (the <a> tags inside the Footer component around config.docsUrl
and the other anchor at lines ~220-223) lack rel attributes; update those
anchors to include rel="noopener noreferrer" to restore proper tab isolation and
security when opening external links in a new tab—locate the <a
href={config.docsUrl ?? ""} target="_blank"> and the corresponding anchor
further down and add rel="noopener noreferrer" to each.
- Around line 185-188: In the Footer component replace all occurrences of Link
hrefs that use "as any" (e.g., href={"/integrations" as any}, href={"/guides" as
any}, href={"/brand" as any}, href={"/compare/litellm" as any}) by passing the
string literals directly (e.g., href="/integrations") so you remove the
unnecessary type casts; update each Link usage in this file to use plain string
href props and remove the "as any" casts to preserve Next.js route type safety.

In `@apps/ui/src/components/landing/graph.tsx`:
- Around line 136-149: The rightRefs array contains a dangling element at index
5 that is never attached to a Circle (you render logos for indices 0–4 and the
Help Circle at index 6), causing null-targets in AnimatedBeam; fix by removing
the unused slot from rightRefs initialization so the array only contains refs
for mounted targets (or alternately mount a hidden Circle for that ref), and
ensure any iteration in AnimatedBeam over rightRefs (beam generation loop) uses
the updated array length or guards against nulls so no null ref is processed.

In `@apps/ui/src/components/models/global-discount-banner.tsx`:
- Around line 35-44: The component's fetch logic leaves stale discounts set when
a fetch fails or returns no active discount; update the effect/fetch in
global-discount-banner (the code that uses setDiscount and computes withExpiry)
to explicitly clear the discount state: call setDiscount(undefined|null)
immediately when starting a new fetch for a different modelId, call
setDiscount(undefined|null) when res.ok is false, call
setDiscount(undefined|null) when no withExpiry is found, and also in the catch
block so errors clear the banner; keep the existing setDiscount(withExpiry) path
for the success case.

In `@apps/ui/src/content/changelog/2026-01-21-pro-features-now-free.md`:
- Line 22: The copy "Use your own API keys from OpenAI, Anthropic, Google, and
25+ other providers." is inconsistent with the PR's "25 providers" messaging;
update the sentence (the exact string containing "25+ other providers") to read
something like "Use your own API keys from OpenAI, Anthropic, Google — 25
providers total." to align counts across the changelog.

---

Outside diff comments:
In `@apps/ui/src/components/pricing/pricing-table.tsx`:
- Around line 216-219: Remove the unnecessary type cast on the Next.js Link
href: the conditional guard around feature.learnMoreLink already narrows it to a
valid string, so change the Link usage in the pricing table (the JSX using
feature.learnMoreLink and Link) to pass href={feature.learnMoreLink} directly
instead of href={feature.learnMoreLink as any}; this restores type safety
without other logic changes.

---

Nitpick comments:
In `@apps/ui/src/components/landing/cta.tsx`:
- Around line 13-20: In the CTA component remove the three decorative inline JSX
comments that restate obvious markup (the comment nodes immediately preceding
the three absolute divs for the gradient separator, atmospheric background, and
soft radial glow) — delete the comment lines like {/* Gradient separator at top
*/}, {/* Atmospheric background */}, and {/* Soft radial glow */} so only the
div elements remain; do not alter the div attributes or layout logic in this
file.

In `@apps/ui/src/components/landing/faq.tsx`:
- Around line 94-101: Extract the long duplicated className string into a single
constant (e.g., triggerClassName) and replace each AccordionPrimitive.Trigger's
inline className with that constant; locate all uses of
AccordionPrimitive.Trigger in this file and update their className prop to use
triggerClassName to remove duplication and improve maintainability.
- Around line 60-61: Remove unnecessary structural comments in the FAQ component
(e.g., the JSX comments like {/* Left column: sticky heading */} and {/* Item 1
*/}) that duplicate information already expressed by markup and classNames (see
the div with className "lg:col-span-2 lg:sticky lg:top-24 lg:self-start" and the
elements using value="item-1"); delete these redundant comments and keep only
comments that add non-obvious, actionable context.

In `@apps/ui/src/components/landing/features.tsx`:
- Around line 382-385: Replace unstable index keys used in the FeaturedCard maps
with the stable unique slug: in the tier1Features.map where FeaturedCard
currently uses key={i}, change it to use key={feature.slug}; do the same in the
other feature card map (the second map rendering FeaturedCard) so both use
feature.slug as the key to ensure stable identity when ordering changes.

In `@apps/ui/src/components/landing/graph.tsx`:
- Around line 78-85: Remove the unnecessary JSX comments that label sections in
apps/ui/src/components/landing/graph.tsx (e.g., the "Gradient background",
"Noise texture", "Header" comments adjacent to the <div className="absolute
inset-0 bg-gradient-to-b from-background via-surface-elevated to-background" />,
<div className="absolute inset-0 bg-noise" />, and the <div
className="relative"> container) and also drop the similar comments around the
elements at the 115–118 region; leave the JSX elements and className attributes
intact so only the comment nodes are deleted for consistency with the repo style
rule forbidding unnecessary comments.

In `@apps/ui/src/components/landing/testimonials.tsx`:
- Around line 22-23: Remove the purely descriptive JSX comment nodes that
restate structure: delete the {/* Noise texture */} comment immediately
preceding the div with className "absolute inset-0 bg-noise" and the similar
descriptive comments around the other elements referenced in the review (the
comment blocks around lines 36-37 and 48-49). Keep the JSX elements (e.g., the
div with className "absolute inset-0 bg-noise") intact and only remove the
unnecessary {/* ... */} comment nodes to comply with the repo guideline about
unnecessary comments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a51e82c2-a193-4a42-986b-e1ed9a44c686

📥 Commits

Reviewing files that changed from the base of the PR and between 97c7356 and 3291a69.

⛔ Files ignored due to path filters (4)
  • apps/admin/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/code/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/playground/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
  • apps/ui/src/lib/api/v1.d.ts is excluded by !**/v1.d.ts
📒 Files selected for processing (29)
  • apps/admin/src/app/discounts/page.tsx
  • apps/admin/src/app/organizations/[orgId]/discounts/page.tsx
  • apps/admin/src/components/discount-form.tsx
  • apps/api/src/index.ts
  • apps/api/src/routes/organization.ts
  • apps/api/src/routes/public-discounts.ts
  • apps/ui/src/app/dashboard/[orgId]/org/discounts/discounts-client.tsx
  • apps/ui/src/app/dashboard/[orgId]/org/discounts/page.tsx
  • apps/ui/src/app/globals.css
  • apps/ui/src/app/layout.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/src/components/countdown.tsx
  • apps/ui/src/components/dashboard/animated-nav-icons.tsx
  • apps/ui/src/components/dashboard/dashboard-sidebar.tsx
  • apps/ui/src/components/enterprise/features.tsx
  • apps/ui/src/components/enterprise/hero.tsx
  • apps/ui/src/components/landing/code-example.tsx
  • apps/ui/src/components/landing/comparison.tsx
  • apps/ui/src/components/landing/cta.tsx
  • apps/ui/src/components/landing/faq.tsx
  • apps/ui/src/components/landing/features.tsx
  • apps/ui/src/components/landing/footer.tsx
  • apps/ui/src/components/landing/graph.tsx
  • apps/ui/src/components/landing/hero.tsx
  • apps/ui/src/components/landing/marquee-container.tsx
  • apps/ui/src/components/landing/testimonials.tsx
  • apps/ui/src/components/models/global-discount-banner.tsx
  • apps/ui/src/components/pricing/pricing-table.tsx
  • apps/ui/src/content/changelog/2026-01-21-pro-features-now-free.md

Comment thread apps/api/src/index.ts
Comment thread apps/api/src/routes/organization.ts
Comment thread apps/api/src/routes/public-discounts.ts
Comment thread apps/api/src/routes/public-discounts.ts
Comment thread apps/ui/src/app/dashboard/[orgId]/org/discounts/page.tsx
Comment thread apps/ui/src/components/landing/footer.tsx
Comment thread apps/ui/src/components/landing/footer.tsx
Comment thread apps/ui/src/components/landing/graph.tsx
Comment thread apps/ui/src/components/models/global-discount-banner.tsx
Comment thread apps/ui/src/content/changelog/2026-01-21-pro-features-now-free.md
- Remove self-referential --font-display CSS variable assignment
- Add NaN guard and immediate state sync in Countdown component
- Remove inline type annotations from Highlight render prop callback
- Remove unused CSSProperties import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@smakosh
smakosh enabled auto-merge March 4, 2026 21:37

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/ui/src/components/landing/code-example.tsx (1)

278-289: Expose selected state on language toggle buttons.

Add pressed/selected semantics so assistive tech can announce which snippet is active.

♿ Suggested accessibility tweak
 								<button
 									key={key}
 									onClick={() => setActiveTab(key as keyof typeof codeExamples)}
+									aria-pressed={activeTab === key}
+									aria-label={`Show ${example.label} code example`}
 									className={cn(
@@
 									<button
 										key={key}
 										onClick={() =>
 											setActiveTab(key as keyof typeof codeExamples)
 										}
+										aria-pressed={activeTab === key}
+										aria-label={`Show ${example.label} code example`}
 										className={cn(

Also applies to: 303-316

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

In `@apps/ui/src/components/landing/code-example.tsx` around lines 278 - 289, The
language toggle buttons lack pressed/selected semantics; update the button
rendered inside the map (the one using key, example.label, setActiveTab and
comparing activeTab === key) to include an ARIA state such as
aria-pressed={activeTab === key} (or aria-current="true"/"false") so assistive
tech can announce the active snippet, and mirror the same change for the second
similar block (the other buttons around the 303-316 region) so both toggle sets
expose selection state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ui/src/app/globals.css`:
- Around line 213-232: Stylelint flags Tailwind v4 at-rules like the `@utility`
blocks (e.g., `@utility` font-display, `@utility` bg-noise, `@utility`
bg-surface-elevated) as unknown at-rules; fix by either updating your stylelint
config to ignore Tailwind directives (add ignoreAtRules:
["utility","theme","plugin","custom-variant"] to your .stylelintrc.json rules)
or by converting these `@utility` blocks into standard Tailwind v3+ compatible
syntax (use `@layer` utilities and regular CSS class selectors instead of
`@utility/`@theme) so the linter and IDE stop reporting errors.

In `@apps/ui/src/components/landing/code-example.tsx`:
- Around line 221-223: The copy button handler currently calls setCopied(true)
and starts a setTimeout without clearing prior timers, which stacks timers and
can update state after unmount; modify the component in code-example.tsx to
store the timeout id in a ref (e.g., copyTimeoutRef via useRef<number | null>),
clear any existing timeout with clearTimeout(copyTimeoutRef.current) before
creating a new setTimeout, assign the new id to copyTimeoutRef.current, and add
a useEffect cleanup that clears the timeout on unmount to avoid stale state
updates from setCopied.

---

Nitpick comments:
In `@apps/ui/src/components/landing/code-example.tsx`:
- Around line 278-289: The language toggle buttons lack pressed/selected
semantics; update the button rendered inside the map (the one using key,
example.label, setActiveTab and comparing activeTab === key) to include an ARIA
state such as aria-pressed={activeTab === key} (or aria-current="true"/"false")
so assistive tech can announce the active snippet, and mirror the same change
for the second similar block (the other buttons around the 303-316 region) so
both toggle sets expose selection state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8fcb8251-bb24-48d4-9173-9ae07f793d77

📥 Commits

Reviewing files that changed from the base of the PR and between 3291a69 and 7728a08.

📒 Files selected for processing (3)
  • apps/ui/src/app/globals.css
  • apps/ui/src/components/countdown.tsx
  • apps/ui/src/components/landing/code-example.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/src/components/countdown.tsx

Comment on lines +213 to +232
@utility font-display {
font-family: var(--font-display), var(--font-sans), system-ui, sans-serif;
}

@utility bg-noise {
&::before {
content: "";
position: absolute;
inset: 0;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: 256px 256px;
pointer-events: none;
}
}

@utility bg-surface-elevated {
background-color: var(--surface-elevated);
}

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

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Tailwind directives in globals.css =="
rg -n --fixed-strings '@utility' apps/ui/src/app/globals.css
rg -n --fixed-strings '@theme inline' apps/ui/src/app/globals.css

echo
echo "== Stylelint config support for Tailwind directives =="
fd -HI 'stylelint*' -E node_modules -E .git | while read -r f; do
	echo "--- $f"
	rg -n -C2 'at-rule-no-unknown|ignoreAtRules|tailwind|customSyntax|scss' "$f" || true
done

echo
echo "== Biome CSS parser/linter config =="
fd -HI 'biome*.json*' -E node_modules -E .git | while read -r f; do
	echo "--- $f"
	rg -n -C2 'css|parser|linter|tailwind' "$f" || true
done

echo
echo "== Relevant package versions =="
fd -HI 'package.json' -E node_modules -E .git | while read -r f; do
	echo "--- $f"
	jq -r '
		(.dependencies // {}) + (.devDependencies // {})
		| to_entries[]
		| select(.key | test("tailwind|stylelint|biome"; "i"))
		| "\(.key)=\(.value)"
	' "$f" || true
done

Repository: theopenco/llmgateway

Length of output: 2093


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Complete .stylelintrc.json =="
cat .stylelintrc.json

echo
echo "== PostCSS config for Tailwind v4 =="
fd -HI 'postcss.config.*' -E node_modules -E .git | while read -r f; do
	echo "--- $f"
	cat "$f"
done

echo
echo "== Check if `@tailwindcss/postcss` is a plugin =="
rg -n 'tailwindcss/postcss|@tailwindcss/postcss' postcss.config.* || true

echo
echo "== Stylelint at-rule-no-unknown rule =="
cat .stylelintrc.json | jq -r '.rules["at-rule-no-unknown"]' 2>/dev/null || echo "Rule not explicitly defined"

echo
echo "== Check tailwindcss v4 postcss plugin documentation =="
rg -n 'postcss\|@tailwindcss' apps/ui/package.json || true

Repository: theopenco/llmgateway

Length of output: 1931


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CI workflows checking for lint =="
fd -HI 'workflow\|\.yml\|\.yaml' .github/workflows 2>/dev/null | while read -r f; do
	echo "--- $f"
	rg -n 'stylelint|lint.*css|prettier' "$f" || echo "(no lint checks found)"
done

echo
echo "== Pre-commit or husky lint config =="
fd -HI '\.husky|pre-commit' -E node_modules -E .git | while read -r f; do
	echo "--- $f"
	head -20 "$f" 2>/dev/null || cat "$f" 2>/dev/null
done

echo
echo "== Package.json lint scripts =="
rg -n '"lint"|"style"' apps/ui/package.json -A1 -B1 || true

echo
echo "== Check if tailwind-language-server or official plugin =="
rg -n 'stylelint-tailwind|tailwindcss.*stylelint|postcss-tailwind' . -E node_modules -E .git || echo "No Tailwind-specific stylelint plugin found"

echo
echo "== Stylelint plugin or custom syntax for Tailwind =="
cat apps/ui/package.json | jq -r '.devDependencies | keys[] | select(. | test("stylelint|postcss"; "i"))' || true

Repository: theopenco/llmgateway

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Husky hooks =="
ls -la .husky/ 2>/dev/null || echo "No .husky directory"

echo
echo "== Hook contents (pre-commit, lint, etc) =="
find .husky -type f ! -name '.' -exec echo "--- {} ---" \; -exec cat {} \; 2>/dev/null || true

echo
echo "== Biome CSS linter rules =="
cat biome.json | jq '.css // empty'

echo
echo "== Try running stylelint on globals.css =="
cd apps/ui && npx stylelint src/app/globals.css 2>&1 | head -50 || echo "Stylelint command failed or not available"

Repository: theopenco/llmgateway

Length of output: 1693


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Lint-staged configuration =="
if [ -f .lintstagedrc.json ]; then
	cat .lintstagedrc.json
elif [ -f .lintstagedrc.mjs ]; then
	cat .lintstagedrc.mjs
elif [ -f .lintstagedrc.js ]; then
	cat .lintstagedrc.js
else
	echo "Checking package.json for lint-staged config..."
	cat package.json | jq '.["lint-staged"] // empty' || echo "No lint-staged in root package.json"
fi

echo
echo "== Check apps/ui package.json lint-staged =="
cat apps/ui/package.json | jq '.["lint-staged"] // empty' || echo "No lint-staged config in apps/ui"

Repository: theopenco/llmgateway

Length of output: 189


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Is lint-staged installed? =="
rg -n '"lint-staged"' package.json apps/*/package.json

echo
echo "== Full package.json lint-staged and related config =="
cat package.json | jq '.. | select(type=="object") | select(has("lint-staged") or has("husky"))' 2>/dev/null || cat package.json | jq 'keys[]' | head -20

echo
echo "== Check for all lint-staged configs =="
fd -HI 'lintstagedrc|lint-staged' -E node_modules -E .git

echo
echo "== Default lint-staged behavior (check docs) =="
echo "lint-staged by default uses linter configs to determine which files to check."
echo "With stylelint configured for SCSS/CSS, it should check *.css, *.scss files."
echo "Since pre-commit runs 'pnpm lint-staged' without args, globals.css will be included."

Repository: theopenco/llmgateway

Length of output: 2120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== apps/ui/.lintstagedrc.json =="
cat apps/ui/.lintstagedrc.json

echo
echo "== Summary: Lint enforcement in commit flow =="
echo "1. Pre-commit hook runs: pnpm lint-staged"
echo "2. apps/ui/.lintstagedrc.json defines which files are checked"
echo "3. stylelint errors will block commit if CSS files are included"

Repository: theopenco/llmgateway

Length of output: 490


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Check for CI workflows (GitHub Actions, etc) =="
find .github -type f -name '*.yml' -o -name '*.yaml' 2>/dev/null | head -20

echo
echo "== Search for any stylelint or CSS linting in CI =="
find .github -type f \( -name '*.yml' -o -name '*.yaml' \) -exec grep -l 'stylelint\|css.*lint\|lint.*css' {} \; 2>/dev/null || echo "No CSS linting found in CI"

echo
echo "== Check turbo tasks for lint =="
cat turbo.json 2>/dev/null | jq '.tasks[] | select(.cache == false or .outputs == [])' || echo "No turbo.json or no relevant tasks"

echo
echo "== Summary of findings =="
echo "- stylelint errors: 11 errors confirmed"
echo "- Tailwind v4 `@utility/`@theme directives: NOT recognized by stylelint-config-standard-scss"
echo "- Pre-commit lint-staged: DOES NOT check *.css files in apps/ui"
echo "- Immediate merge blocker: NO (won't fail pre-commit)"
echo "- CI blocker risk: UNKNOWN (need to check CI config)"

Repository: theopenco/llmgateway

Length of output: 844


Stylelint reports errors on Tailwind v4 @utility and @theme directives, but they're not currently enforced on CSS files.

Lines 213, 217, and 230 contain @utility blocks that stylelint flags as unknown at-rules (11 errors total across the file). The repo uses Tailwind v4 with proper PostCSS configuration, but stylelint-config-standard-scss doesn't recognize Tailwind's v4 directives. This won't block commits since lint-staged only checks JS/TS/JSON/Markdown files and CI doesn't run stylelint. However, these errors will appear in IDEs and should be resolved to avoid technical debt. Either update .stylelintrc.json to ignore Tailwind directives (add ignoreAtRules: ["utility", "theme", "plugin", "custom-variant"] to rules) or rewrite as @layer utilities with standard CSS class selectors.

🧰 Tools
🪛 Biome (2.4.4)

[error] 213-215: Tailwind-specific syntax is disabled.

(parse)


[error] 217-228: Tailwind-specific syntax is disabled.

(parse)


[error] 230-232: Tailwind-specific syntax is disabled.

(parse)

🪛 Stylelint (17.3.0)

[error] 218-218: Unexpected missing scoping root (nesting-selector-no-missing-scoping-root)

(nesting-selector-no-missing-scoping-root)


[error] 213-213: Unexpected unknown at-rule "@Utility" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 217-217: Unexpected unknown at-rule "@Utility" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)


[error] 230-230: Unexpected unknown at-rule "@Utility" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

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

In `@apps/ui/src/app/globals.css` around lines 213 - 232, Stylelint flags Tailwind
v4 at-rules like the `@utility` blocks (e.g., `@utility` font-display, `@utility`
bg-noise, `@utility` bg-surface-elevated) as unknown at-rules; fix by either
updating your stylelint config to ignore Tailwind directives (add ignoreAtRules:
["utility","theme","plugin","custom-variant"] to your .stylelintrc.json rules)
or by converting these `@utility` blocks into standard Tailwind v3+ compatible
syntax (use `@layer` utilities and regular CSS class selectors instead of
`@utility/`@theme) so the linter and IDE stop reporting errors.

Comment on lines +221 to 223
setCopied(true);
setTimeout(() => setCopied(false), 2000);
toast({

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

Clear/reset the copy timeout to avoid stale state updates after unmount.

Repeated clicks stack timers, and unmount can leave a pending timeout that still tries to update state.

🛠️ Proposed fix
-import { useState, useEffect } from "react";
+import { useState, useEffect, useRef } from "react";
@@
 	const { resolvedTheme } = useTheme();
 	const [mounted, setMounted] = useState(false);
 	const [copied, setCopied] = useState(false);
+	const copyResetTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
@@
 	useEffect(() => {
 		setMounted(true);
+		return () => {
+			if (copyResetTimeout.current) {
+				clearTimeout(copyResetTimeout.current);
+			}
+		};
 	}, []);
@@
 			await navigator.clipboard.writeText(text);
 			setCopied(true);
-			setTimeout(() => setCopied(false), 2000);
+			if (copyResetTimeout.current) {
+				clearTimeout(copyResetTimeout.current);
+			}
+			copyResetTimeout.current = setTimeout(() => setCopied(false), 2000);
📝 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
setCopied(true);
setTimeout(() => setCopied(false), 2000);
toast({
import { useState, useEffect, useRef } from "react";
// ... other imports ...
export default function CodeExample() {
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
const [copied, setCopied] = useState(false);
const copyResetTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
setMounted(true);
return () => {
if (copyResetTimeout.current) {
clearTimeout(copyResetTimeout.current);
}
};
}, []);
// ... other code ...
// Around lines 221-223:
const handleCopy = async (text: string) => {
await navigator.clipboard.writeText(text);
setCopied(true);
if (copyResetTimeout.current) {
clearTimeout(copyResetTimeout.current);
}
copyResetTimeout.current = setTimeout(() => setCopied(false), 2000);
toast({
// ... rest of toast code ...
});
};
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/landing/code-example.tsx` around lines 221 - 223, The
copy button handler currently calls setCopied(true) and starts a setTimeout
without clearing prior timers, which stacks timers and can update state after
unmount; modify the component in code-example.tsx to store the timeout id in a
ref (e.g., copyTimeoutRef via useRef<number | null>), clear any existing timeout
with clearTimeout(copyTimeoutRef.current) before creating a new setTimeout,
assign the new id to copyTimeoutRef.current, and add a useEffect cleanup that
clears the timeout on unmount to avoid stale state updates from setCopied.

@smakosh
smakosh added this pull request to the merge queue Mar 4, 2026
Merged via the queue into main with commit 4d2f7de Mar 4, 2026
8 checks passed
@smakosh
smakosh deleted the feat/landing-page-redesign branch March 4, 2026 21:51
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.

1 participant