Skip to content

fix: onboarding and dashboard UX improvements - #1676

Merged
smakosh merged 1 commit into
mainfrom
fix/onboarding-improvements
Feb 16, 2026
Merged

smakosh merged 1 commit into
mainfrom
fix/onboarding-improvements

Conversation

@smakosh

@smakosh smakosh commented Feb 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Quick Start snippet model mismatch: Changed cURL and TypeScript snippets from gemini-3-flash-preview (paid) to model: "auto" with free_models_only: true, matching the "Try it now" section so new users' first self-initiated API call succeeds without credits
  • Dashboard empty state: When user has < 5 API calls, replaces the empty Usage Overview chart with a "Get Started" card containing the Quick Start snippet, contextual message acknowledging the onboarding test call, and links to Docs/Playground/Models
  • Invite banner delay: The "Invite your friends" sidebar CTA now only appears after the user has been active 7+ days, made 50+ API calls, or purchased credits — no longer shown immediately on first dashboard visit

Test plan

  • Create a new test account with email signup
  • Verify the onboarding "Try it now" works with the free model
  • Copy the Quick Start cURL snippet and run it — should succeed with $0 credits
  • Verify the dashboard shows "Get Started" card instead of empty chart
  • Verify the "Get Started" card acknowledges the onboarding test call
  • Verify the invite banner does NOT appear for a brand-new user
  • Verify the invite banner appears for users with 7+ day old accounts
  • Verify after 5+ API calls, the normal Usage Overview chart replaces the Get Started card

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Get Started card for new dashboard users with quick links to documentation, playground, and available models.
    • Dashboard now tracks user activity and intelligently manages upgrade prompts based on usage metrics and organization eligibility.
  • Improvements

    • Updated code snippet examples to use automatic model selection for improved compatibility.

- Use free model (auto + free_models_only) in Quick Start
  snippets to match "Try it now" section
- Replace empty dashboard chart with Get Started card
  when user has < 5 API calls
- Delay invite banner until 7+ days, 50+ calls, or
  credits purchased
- Acknowledge onboarding test call in dashboard state

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

coderabbitai Bot commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Modifies dashboard UI to display a Get Started card for new users with totalRequests below 5, introduces activity tracking via localStorage, restructures Quick Actions component rendering with Button/Link composition, updates sidebar CTA eligibility logic based on organization age and activity flags, and changes Quick Start code snippets to use "auto" model with free_models_only configuration.

Changes

Cohort / File(s) Summary
Dashboard Bootstrap & Metrics
apps/ui/src/components/dashboard/dashboard-client.tsx
Adds conditional bootstrap UI pathway: renders Get Started card with QuickStartSection and external links when totalRequests < 5. Introduces side-effect tracking totalRequests and writing user_has_50_plus_calls to localStorage at threshold >= 50. Restructures Quick Actions with Button + Link wrapper pattern, replacing previous inline rendering. Reorganizes layout between loaded and bootstrap states.
Dashboard Sidebar Eligibility
apps/ui/src/components/dashboard/dashboard-sidebar.tsx
Adds useInviteBannerEligible hook that computes eligibility based on organization age (>= 7 days), credits > 0, or localStorage flag user_has_50_plus_calls. Updates UpgradeCTA to render conditionally when hook returns true alongside existing show and selectedOrganization checks.
Quick Start Code Examples
apps/ui/src/components/shared/quick-start-snippet.tsx
Updates curl and TypeScript code snippets: changes model parameter from "gemini-3-flash-preview" to "auto", adds free_models_only: true field in both examples, and adds TypeScript expect-error comment.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • steebchen
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: onboarding and dashboard UX improvements' is directly related to the main changes: dashboard empty state handling with a Get Started card for new users, Quick Start snippet model updates, and invite banner visibility logic based on user activity.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ 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 fix/onboarding-improvements

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.

@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

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@apps/ui/src/components/dashboard/dashboard-client.tsx`:
- Line 441: The UI shows the "Get Started" card when totalRequests < 5 but
totalRequests is derived from activityData filtered by the selected from/to
range, so users with lifetime usage outside that range still see the card;
update the logic to use a lifetime request count or persisted flag instead of
the range-filtered totalRequests: fetch or read a lifetimeRequests (or reuse the
existing user_has_50_plus_calls-style flag) and change the conditional in the
dashboard rendering (the {!isLoading && totalRequests < 5 ? ...} branch) to
check lifetimeRequests or the persisted flag so the card is only shown for truly
new users.
- Around line 146-150: The effect currently sets localStorage key
"user_has_50_plus_calls" based on date-scoped totalRequests, which misses users
with 50+ lifetime calls when they view a narrow range; change the check inside
the useEffect to use the lifetime (unfiltered) request count instead of the
date-filtered totalRequests — e.g., use a prop/state like lifetimeRequests,
totalRequestsAllTime, or compute from the unfiltered requests array and fall
back to totalRequests if the lifetime value is not available; update the
useEffect (and its dependency list) to reference that lifetime identifier so the
flag reflects 50+ API calls across all time rather than only the selected date
window.
- Around line 490-519: The Quick Actions JSX is duplicated; extract it into a
small local React component or variable (e.g., QuickActionsCard) that renders
the Card with CardHeader/CardContent and maps quickActions to the Button+Link
elements, reusing helpers buildOrgUrl and buildUrl and action.icon/action.label;
then replace the repeated blocks in both branches with a single
{QuickActionsCard} reference to eliminate duplication.

In `@apps/ui/src/components/dashboard/dashboard-sidebar.tsx`:
- Around line 649-686: The hook useInviteBannerEligible currently only depends
on selectedOrganization so it never re-checks when the localStorage flag
"user_has_50_plus_calls" is set later; update the hook to listen for changes to
that flag (e.g., add a storage event listener or read the flag into React state
and include it in the effect dependency array) so the eligibility recalculates
in-session when dashboard-client.tsx writes the flag, and stabilize the
selectedOrganization dependency by depending only on the specific fields used
(e.g., selectedOrganization.id, selectedOrganization.createdAt,
selectedOrganization.credits) or derive a memoized object to avoid unnecessary
re-runs.
- Around line 674-680: The code reads
localStorage.getItem("user_has_50_plus_calls") inside the useEffect to
setEligible, which violates the guideline to use cookies for non-DB user
settings; update the logic to read the "user_has_50_plus_calls" cookie instead
of localStorage in the useEffect (and ensure any code that previously wrote to
localStorage now sets the same cookie), keeping the same boolean check and
calling setEligible(true) when the cookie equals "true"; reference the existing
symbols localStorage -> cookie, "user_has_50_plus_calls", useEffect,
setEligible, and eligible so you can locate and replace the storage reads/writes
consistently across the codebase.
🧹 Nitpick comments (3)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@apps/ui/src/components/dashboard/dashboard-client.tsx`:
- Around line 490-519: The Quick Actions JSX is duplicated; extract it into a
small local React component or variable (e.g., QuickActionsCard) that renders
the Card with CardHeader/CardContent and maps quickActions to the Button+Link
elements, reusing helpers buildOrgUrl and buildUrl and action.icon/action.label;
then replace the repeated blocks in both branches with a single
{QuickActionsCard} reference to eliminate duplication.

In `@apps/ui/src/components/dashboard/dashboard-sidebar.tsx`:
- Around line 649-686: The hook useInviteBannerEligible currently only depends
on selectedOrganization so it never re-checks when the localStorage flag
"user_has_50_plus_calls" is set later; update the hook to listen for changes to
that flag (e.g., add a storage event listener or read the flag into React state
and include it in the effect dependency array) so the eligibility recalculates
in-session when dashboard-client.tsx writes the flag, and stabilize the
selectedOrganization dependency by depending only on the specific fields used
(e.g., selectedOrganization.id, selectedOrganization.createdAt,
selectedOrganization.credits) or derive a memoized object to avoid unnecessary
re-runs.
- Around line 674-680: The code reads
localStorage.getItem("user_has_50_plus_calls") inside the useEffect to
setEligible, which violates the guideline to use cookies for non-DB user
settings; update the logic to read the "user_has_50_plus_calls" cookie instead
of localStorage in the useEffect (and ensure any code that previously wrote to
localStorage now sets the same cookie), keeping the same boolean check and
calling setEligible(true) when the cookie equals "true"; reference the existing
symbols localStorage -> cookie, "user_has_50_plus_calls", useEffect,
setEligible, and eligible so you can locate and replace the storage reads/writes
consistently across the codebase.
apps/ui/src/components/dashboard/dashboard-sidebar.tsx (2)

649-686: useInviteBannerEligible won't react to the user_has_50_plus_calls flag being set later in the session.

The useEffect depends only on [selectedOrganization]. If the user reaches 50 calls during the same session (dashboard-client.tsx writes to localStorage), this hook won't re-evaluate because selectedOrganization hasn't changed. The banner will only appear on the next page load / organization switch.

This may be acceptable (banner appears on next visit), but if the intent is real-time eligibility, you'd need a cross-component signal (e.g., a shared context, a custom event, or polling).

Also minor: selectedOrganization is an object prop — if the parent re-creates it on each render the effect will re-fire unnecessarily. Consider comparing only the fields you actually read (createdAt, credits, and the org id) via a stabilized dependency or a ref-based check.

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

In `@apps/ui/src/components/dashboard/dashboard-sidebar.tsx` around lines 649 -
686, The hook useInviteBannerEligible currently only depends on
selectedOrganization so it never re-checks when the localStorage flag
"user_has_50_plus_calls" is set later; update the hook to listen for changes to
that flag (e.g., add a storage event listener or read the flag into React state
and include it in the effect dependency array) so the eligibility recalculates
in-session when dashboard-client.tsx writes the flag, and stabilize the
selectedOrganization dependency by depending only on the specific fields used
(e.g., selectedOrganization.id, selectedOrganization.createdAt,
selectedOrganization.credits) or derive a memoized object to avoid unnecessary
re-runs.

674-680: localStorage usage instead of cookies for cross-component state.

The coding guidelines state: "Use cookies for user settings which are not saved in the database to ensure SSR works." Here localStorage is used for the user_has_50_plus_calls flag. Since the initial eligible state is false and the check happens inside useEffect, this won't cause an SSR hydration mismatch — the CTA simply never renders server-side and appears after hydration when eligible. This is fine for a progressive-enhancement pattern, but be aware that if you ever need SSR-aware eligibility (e.g., to avoid layout shift), you'd need to move this to a cookie.

As per coding guidelines: apps/{ui,playground}/**/*.{ts,tsx}: "Use cookies for user-settings which are not saved in the database to ensure SSR works"

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

In `@apps/ui/src/components/dashboard/dashboard-sidebar.tsx` around lines 674 -
680, The code reads localStorage.getItem("user_has_50_plus_calls") inside the
useEffect to setEligible, which violates the guideline to use cookies for non-DB
user settings; update the logic to read the "user_has_50_plus_calls" cookie
instead of localStorage in the useEffect (and ensure any code that previously
wrote to localStorage now sets the same cookie), keeping the same boolean check
and calling setEligible(true) when the cookie equals "true"; reference the
existing symbols localStorage -> cookie, "user_has_50_plus_calls", useEffect,
setEligible, and eligible so you can locate and replace the storage reads/writes
consistently across the codebase.
apps/ui/src/components/dashboard/dashboard-client.tsx (1)

490-519: Quick Actions card is duplicated across both branches.

The Quick Actions rendering (Button + Link composition for each action) is identical in the "Get Started" branch (lines 490–519) and the "Usage Overview" branch (lines 558–587). Extract it to a local component or variable to reduce duplication.

♻️ Suggested extraction
+	const QuickActionsCard = (
+		<Card className="col-span-3">
+			<CardHeader>
+				<CardTitle>Quick Actions</CardTitle>
+				<CardDescription>
+					Common tasks you might want to perform
+				</CardDescription>
+			</CardHeader>
+			<CardContent className="space-y-2">
+				{quickActions.map((action) => (
+					<Button
+						key={action.href}
+						asChild
+						variant="outline"
+						className="w-full justify-start"
+					>
+						<Link
+							href={
+								action.href === "provider-keys"
+									? buildOrgUrl("org/provider-keys")
+									: buildUrl(action.href)
+							}
+							prefetch={true}
+						>
+							<action.icon className="mr-2 h-4 w-4" />
+							{action.label}
+						</Link>
+					</Button>
+				))}
+			</CardContent>
+		</Card>
+	);

Then use {QuickActionsCard} in both branches instead of repeating the markup.

Also applies to: 558-587

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

In `@apps/ui/src/components/dashboard/dashboard-client.tsx` around lines 490 -
519, The Quick Actions JSX is duplicated; extract it into a small local React
component or variable (e.g., QuickActionsCard) that renders the Card with
CardHeader/CardContent and maps quickActions to the Button+Link elements,
reusing helpers buildOrgUrl and buildUrl and action.icon/action.label; then
replace the repeated blocks in both branches with a single {QuickActionsCard}
reference to eliminate duplication.

Comment thread apps/ui/src/components/dashboard/dashboard-client.tsx
Comment thread apps/ui/src/components/dashboard/dashboard-client.tsx
@smakosh
smakosh added this pull request to the merge queue Feb 16, 2026
Merged via the queue into main with commit 8f17d49 Feb 16, 2026
8 checks passed
@smakosh
smakosh deleted the fix/onboarding-improvements branch February 16, 2026 20:54
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