Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Jan 20, 2026

User description

This PR integrates the billing payment popup, usage tracking on the camping icon, and credit preview toggle as requested.


PR Type

Enhancement


Description

  • Integrate billing payment popup that opens on app startup

  • Replace Stripe link with usage sidebar showing credits and billing history

  • Add collapsible credits preview toggle in chat history sidebar

  • Update tent icon to trigger usage sidebar instead of external link


Diagram Walkthrough

flowchart LR
  A["Header Component"] -->|Opens on startup| B["Purchase Credits Popup"]
  A -->|Tent icon click| C["Usage Sidebar"]
  D["Chat History Sidebar"] -->|Toggle| E["Credits Preview"]
  C -->|Upgrade button| B
  B -->|Pay Now| F["Stripe Payment"]
Loading

File Walkthrough

Relevant files
Enhancement
header.tsx
Integrate billing popup and usage sidebar                               

components/header.tsx

  • Import PurchaseCreditsPopup and UsageSidebar components with state
    management
  • Add useEffect hook to open purchase popup automatically on app startup
  • Replace external Stripe link on tent icon with onClick handler to open
    usage sidebar
  • Update both desktop and mobile menu tent buttons to trigger usage
    sidebar
  • Wrap header content in fragment to accommodate new popup and sidebar
    components
+21/-10 
purchase-credits-popup.tsx
New billing payment popup component                                           

components/purchase-credits-popup.tsx

  • Create new dialog component for credit purchase with Zap icon header
  • Display upgrade message and Standard Tier pricing information
  • Implement "Pay Now" button that opens Stripe payment link in new
    window
  • Add "Later" button to dismiss the popup
  • Use Dialog component from UI library for consistent styling
+57/-0   
usage-sidebar.tsx
New usage tracking sidebar component                                         

components/usage-sidebar.tsx

  • Create new Sheet component displaying user credits and usage history
  • Show plan tier (Free), available credits, and daily refresh credits
    (300)
  • Display mock usage data table with details, date, and credit changes
  • Include Upgrade button linking to Stripe payment page
  • Add close button and responsive layout for mobile and desktop
+113/-0 
chat-history-client.tsx
Add collapsible credits preview toggle                                     

components/sidebar/chat-history-client.tsx

  • Import Zap, ChevronDown, and ChevronUp icons for credits preview UI
  • Add isCreditsVisible state to toggle credits preview section
  • Create collapsible credits preview button with expand/collapse chevron
  • Display available credits count and progress bar visualization
  • Show upgrade prompt text when credits preview is expanded
+30/-0   

Summary by CodeRabbit

  • New Features
    • Added in-app purchase credits popup with integrated checkout for buying credits
    • Introduced usage sidebar displaying billing information and usage history with credit tracking
    • Added credits preview section showing available credits with expandable details

✏️ Tip: You can customize this high-level summary in your review settings.

@charliecreates charliecreates bot requested a review from CharlieHelps January 20, 2026 06:21
@vercel
Copy link

vercel bot commented Jan 20, 2026

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

Project Deployment Review Updated (UTC)
qcx Ready Ready Preview, Comment Jan 20, 2026 6:23am

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 20, 2026

Walkthrough

Introduces new in-app billing and credit management components by adding a PurchaseCreditsPopup modal and UsageSidebar sheet. Integrates these components into the Header with state-controlled visibility, replacing external Stripe navigation. Adds a Credits Preview toggle to the chat history sidebar with mock usage data.

Changes

Cohort / File(s) Summary
Header Integration
components/header.tsx
Added state management (isPurchaseOpen, isUsageOpen) with useEffect for auto-opening purchase popup. Replaced external Stripe link buttons with in-component toggles. Renders PurchaseCreditsPopup and UsageSidebar with onClose handlers.
Purchase Dialog
components/purchase-credits-popup.tsx
New component exporting PurchaseCreditsPopup. Renders modal with plan card, description, and "Pay Now"/"Later" actions. Handles purchase by opening Stripe checkout in new tab before closing dialog.
Usage Sidebar
components/usage-sidebar.tsx
New component exporting UsageSidebar. Renders slide-out sheet with billing summary card, usage table with mock data, and close button. Populates mock usage on open, maintains internal state for usage array and credits.
Credits Preview
components/sidebar/chat-history-client.tsx
Added Credits Preview toggle section with Zap icon and collapsible UI. Displays mock "Available Credits" (0) and progress bar. Uses new state (isCreditsVisible) to control visibility.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hop hop, new features bloom—
Popups and sidebars light the room!
Credits and payments, state managed with care,
In-app dialogs floating through air! 💳✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 title directly matches the main changes: introduces billing popup, usage sidebar, and credit preview toggle across multiple components.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Reverse tabnabbing

Description: window.open(..., "_blank") is used without noopener/noreferrer, which can allow
reverse-tabnabbing (the opened Stripe page can access window.opener and potentially
redirect the original app tab); use window.open(url, "_blank", "noopener,noreferrer") (or
ensure opener is null) for both purchase/upgrade flows.
purchase-credits-popup.tsx [20-24]

Referred Code
export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) {
  const handlePurchase = () => {
    window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
    onClose();
  };
Reverse tabnabbing

Description: The Upgrade button uses window.open(..., "_blank") without noopener/noreferrer, which can
permit reverse-tabnabbing via window.opener; prefer window.open(url, "_blank",
"noopener,noreferrer") (or explicitly null opener) when opening external pages.
usage-sidebar.tsx [46-50]

Referred Code
<div className="p-4 border rounded-xl space-y-4">
  <div className="flex justify-between items-center">
    <span className="italic font-medium text-lg">Free</span>
    <Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
  </div>
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Non-descriptive typing: The new state usage is typed as any[], reducing readability and intent compared to a
descriptive UsageItem type/interface.

Referred Code
export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) {
  const [usage, setUsage] = useState<any[]>([]);
  const [credits, setCredits] = useState(0);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
No failure handling: The new Stripe launch flow uses window.open(...) without handling failure cases (e.g.,
popup blocked or null return) and provides no fallback UX or error reporting.

Referred Code
const handlePurchase = () => {
  window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
  onClose();
};

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unsafe window.open usage: The new external navigation uses window.open(..., '_blank') without
noopener/noreferrer protections, which can enable reverse-tabnabbing via window.opener.

Referred Code
  <Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
</div>

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logging: The new billing-related user actions (e.g., clicking Pay Now / Upgrade which initiates a
Stripe purchase flow) are not instrumented with any audit/event logging in the added code.

Referred Code
export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) {
  const handlePurchase = () => {
    window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
    onClose();
  };

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Replace hardcoded logic with dynamic data

The new billing and usage components use hardcoded data, including a popup that
appears on every app startup. These should be updated to use dynamic data from a
backend or state management system, with popups triggered conditionally based on
the user's actual credit status.

Examples:

components/header.tsx [27-30]
  useEffect(() => {
    // Open payment popup as soon as application opens
    setIsPurchaseOpen(true)
  }, [])
components/usage-sidebar.tsx [23-33]
  useEffect(() => {
    if (isOpen) {
      // Mock data for now as per the screenshot
      setUsage([
        { details: 'Efficiently Fix Pull Request ...', date: '2026-01-17 08:05', change: -418 },
        { details: 'Fix Build and Add Parallel S...', date: '2026-01-16 06:10', change: -482 },
        { details: 'How to Add a Feature to a ...', date: '2026-01-14 10:42', change: -300 },
      ]);
      setCredits(0);
    }

 ... (clipped 1 lines)

Solution Walkthrough:

Before:

// components/header.tsx
function Header() {
  const [isPurchaseOpen, setIsPurchaseOpen] = useState(false);

  useEffect(() => {
    // Always opens popup on app start
    setIsPurchaseOpen(true);
  }, []);

  return <PurchaseCreditsPopup isOpen={isPurchaseOpen} ... />;
}

// components/usage-sidebar.tsx
function UsageSidebar({ isOpen }) {
  useEffect(() => {
    // Mock data is always used
    setCredits(0);
    setUsage([...]); // hardcoded usage data
  }, [isOpen]);
  // ...
}

After:

// components/header.tsx
function Header() {
  const { user } = useUserData(); // Assume a hook to get user data
  const [isPurchaseOpen, setIsPurchaseOpen] = useState(false);

  useEffect(() => {
    // Conditionally open popup based on user's credit status
    if (user && user.credits <= 0) {
      setIsPurchaseOpen(true);
    }
  }, [user]);

  return (
    <>
      <PurchaseCreditsPopup isOpen={isPurchaseOpen} ... />
      <UsageSidebar user={user} ... />
    </>
  );
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical flaw across all new components, where hardcoded data and logic make the features non-functional and create a poor user experience.

High
Possible issue
Remove automatic popup on app load

Remove the useEffect hook that automatically opens the purchase popup on
application startup to avoid disrupting the user experience.

components/header.tsx [27-30]

-useEffect(() => {
-  // Open payment popup as soon as application opens
-  setIsPurchaseOpen(true)
-}, [])
 
+
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a major user experience flaw where a purchase popup is shown on every application start, and rightly recommends its removal.

High
Handle dialog close correctly

Update the onOpenChange handler for the Dialog to conditionally call onClose
only when the dialog is being closed.

components/purchase-credits-popup.tsx [27]

-<Dialog open={isOpen} onOpenChange={onClose}>
+<Dialog open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that the onOpenChange handler should check its argument to prevent unintended behavior, improving the component's robustness.

Low
Fix sheet close handler

Update the onOpenChange handler for the Sheet to conditionally call onClose only
when the sheet is being closed.

components/usage-sidebar.tsx [36]

-<Sheet open={isOpen} onOpenChange={onClose}>
+<Sheet open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that the onOpenChange handler should check its argument to prevent unintended behavior, improving the component's robustness.

Low
General
Use an anchor tag for navigation

Replace the onClick handler that uses window.open with a proper anchor () tag
and use the asChild prop on the Button for correct styling and behavior.

components/usage-sidebar.tsx [49]

-<Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
+<Button asChild size="sm" className="rounded-full px-4">
+  <a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer">Upgrade</a>
+</Button>
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion improves code robustness and semantics by replacing a potentially unreliable window.open call with a standard anchor tag, which is better for accessibility and avoids popup blockers.

Low
Use a unique key for list items

Replace the array index i with a unique identifier like item.date for the key
prop in the mapped TableRow components to follow React best practices.

components/usage-sidebar.tsx [99-105]

-{usage.map((item, i) => (
-  <TableRow key={i}>
+{usage.map((item) => (
+  <TableRow key={item.date}>
     <TableCell className="text-xs font-medium">{item.details}</TableCell>
     <TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
     <TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
   </TableRow>
 ))}
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies the use of an array index as a React key, which is an anti-pattern, and proposes using a unique property from the data instead.

Low
  • More

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@components/header.tsx`:
- Around line 18-20: The imports at the top of components/header.tsx are
inconsistent: React hooks useState and useEffect are imported separately rather
than from the React import; update the import statements so hooks are
consolidated into the same import that brings in React (e.g., combine into a
single import that includes useState and useEffect alongside other React
imports) and remove the standalone import of useState/useEffect; ensure symbols
referenced (PurchaseCreditsPopup, UsageSidebar, useState, useEffect) remain
correctly imported.

In `@components/purchase-credits-popup.tsx`:
- Around line 20-24: The PurchaseCreditsPopup currently closes immediately in
handlePurchase; change handlePurchase to capture the return value of window.open
(e.g., const popup = window.open(...)) and only call onClose() if popup is
non-null (popup opened). If popup is null (blocked) or closed quickly, keep the
dialog open and set local state (e.g., popupBlocked or isLoading) to show an
inline error/notification or a "Open Stripe in new tab" link/button; also
consider adding a short timeout before auto-closing to allow the user to switch
to the new tab. Update the PurchaseCreditsPopup component to add that state and
conditional UI based on popupBlocked/isLoading.

In `@components/sidebar/chat-history-client.tsx`:
- Around line 119-143: The collapsible "Credits Preview" control lacks ARIA
attributes and a trigger for purchasing; update the Button component that
toggles isCreditsVisible (onClick => setIsCreditsVisible) to include
aria-expanded={isCreditsVisible} and aria-controls referencing the collapsible
region's id (e.g., credits-preview-panel) and give the collapsible div that id
and role="region". Also replace or wrap the "Upgrade to get more credits" text
with an interactive element that opens the existing PurchaseCreditsPopup (call
the same handler/prop used elsewhere in the PR) so keyboard and assistive users
can activate the purchase flow.
- Around line 138-140: The progress bar markup lacks ARIA attributes; update the
outer container (the div with "w-full bg-secondary h-1.5 rounded-full
overflow-hidden") to include role="progressbar" plus aria-valuemin="0" and
aria-valuemax="100", and set aria-valuenow to the current numeric progress value
(e.g., progressPercent) and/or aria-valuetext for a human-readable label; also
ensure the inner bar (the div with "bg-yellow-500 h-full w-[0%]") uses a
computed width from the same progress value (not a hardcoded w-[0%]) so the ARIA
value and visual width stay in sync inside the ChatHistoryClient progress
markup.

In `@components/usage-sidebar.tsx`:
- Line 49: The Stripe checkout URL is duplicated in the UsageSidebar Button
onClick and in purchase-credits-popup.tsx; extract the literal into a shared
exported constant (e.g., STRIPE_CHECKOUT_URL) and import it where needed. Create
a constants module (for example lib/constants.ts) exporting STRIPE_CHECKOUT_URL
= 'https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', then replace the hardcoded
string in the UsageSidebar Button onClick handler (the Button component) and in
purchase-credits-popup.tsx to reference the imported STRIPE_CHECKOUT_URL
constant.
- Around line 99-105: The map over usage currently uses the array index as the
React key which can break reconciliation; in the usage.map(...) replace key={i}
on the TableRow with a stable unique identifier from each item (e.g., item.id or
another invariant like a concatenation of item.date and item.details) so
TableRow (and its children) use a persistent key across reorders/filters; update
any type/interface for the items if needed to ensure the chosen unique field
exists.
- Line 20: The state declaration uses a loose any[] which loses type safety;
define a concrete interface (e.g., UsageItem) describing the properties used by
this component (fields like id, date, amount, description — matching how usage
is accessed) and change the state to use useState<UsageItem[]>([]) instead of
any[], then update any references to usage, setUsage, and props/handlers (e.g.,
where items are fetched or passed) to use the new UsageItem type so TypeScript
enforces correct shape across functions like setUsage and rendering logic in the
UsageSidebar component.
- Around line 84-87: The SVG used inside the Button lacks accessible labeling;
update the Button/SVG so screen readers get a meaningful name: add an explicit
aria-label (or aria-labelledby) on the Button component (e.g., "View more") and
mark the inline SVG as decorative with aria-hidden="true" (or alternatively add
a <title> inside the SVG and role="img" on the SVG if you want the SVG itself to
be announced). Locate the Button and its child <svg> in
components/usage-sidebar.tsx and apply one of these fixes (or replace the inline
SVG with the lucide-react ChevronRight icon for consistent, accessible icons).
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25e7bb4 and 6eeae28.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • components/header.tsx
  • components/purchase-credits-popup.tsx
  • components/sidebar/chat-history-client.tsx
  • components/usage-sidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
components/sidebar/chat-history-client.tsx (1)
components/ui/button.tsx (1)
  • Button (56-56)
components/purchase-credits-popup.tsx (2)
components/ui/dialog.tsx (4)
  • Dialog (112-112)
  • props (35-53)
  • props (20-29)
  • props (70-81)
components/ui/button.tsx (1)
  • Button (56-56)
components/header.tsx (2)
components/purchase-credits-popup.tsx (1)
  • PurchaseCreditsPopup (20-57)
components/usage-sidebar.tsx (1)
  • UsageSidebar (19-113)
🪛 Biome (2.1.2)
components/usage-sidebar.tsx

[error] 86-86: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🔇 Additional comments (8)
components/sidebar/chat-history-client.tsx (2)

21-21: LGTM!

Icon imports are appropriate and all are used in the new Credits Preview toggle.


35-35: LGTM!

State declaration follows standard React patterns for controlling collapsible UI sections.

components/usage-sidebar.tsx (1)

23-33: Mock data is acceptable for initial implementation.

The TODO comment documents that this is intentionally mock data. Consider adding a follow-up task to integrate with a real data source.

components/purchase-credits-popup.tsx (2)

15-18: LGTM!

Clean interface definition with appropriate props for controlled dialog state.


26-56: LGTM!

Dialog implementation follows standard patterns with proper use of Dialog primitives. The UI clearly communicates the upgrade prompt and pricing.

components/header.tsx (3)

33-35: LGTM!

Clean integration of the popup and sidebar components with proper state bindings.


70-72: LGTM!

Both desktop and mobile buttons correctly trigger the usage sidebar. The TentTree icon aligns with the PR objective ("camping icon").

Also applies to: 82-84


27-30: Verify whether the purchase popup auto-opens unconditionally or if guards exist elsewhere.

The concern about auto-opening popups harming UX is valid, but confirmation requires access to the full codebase context. Before addressing this, clarify:

  • Is this new behavior introduced in this PR, or pre-existing?
  • Are there feature flags, user preferences, or state conditions elsewhere that control when this popup displays?
  • Is this intentional design or unintended behavior?

If the popup truly opens unconditionally on every mount, it should trigger only when meaningful (e.g., when users exhaust credits, after specific engagement, or on user action).

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +18 to +20
import { PurchaseCreditsPopup } from './purchase-credits-popup'
import { UsageSidebar } from './usage-sidebar'
import { useState, useEffect } from 'react'
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor import inconsistency.

useState and useEffect are imported separately from the React import on line 2. Consider consolidating for consistency.

♻️ Suggested consolidation
 "use client"
-import React from 'react'
+import React, { useState, useEffect } from 'react'
 import Image from 'next/image'
 ...
-import { useState, useEffect } from 'react'
🤖 Prompt for AI Agents
In `@components/header.tsx` around lines 18 - 20, The imports at the top of
components/header.tsx are inconsistent: React hooks useState and useEffect are
imported separately rather than from the React import; update the import
statements so hooks are consolidated into the same import that brings in React
(e.g., combine into a single import that includes useState and useEffect
alongside other React imports) and remove the standalone import of
useState/useEffect; ensure symbols referenced (PurchaseCreditsPopup,
UsageSidebar, useState, useEffect) remain correctly imported.

Comment on lines +20 to +24
export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) {
const handlePurchase = () => {
window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
onClose();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider user feedback before closing the dialog.

The dialog closes immediately after opening the Stripe URL. If the popup is blocked or the user switches back quickly, they may be confused. Consider keeping the dialog open or providing visual feedback.

🤖 Prompt for AI Agents
In `@components/purchase-credits-popup.tsx` around lines 20 - 24, The
PurchaseCreditsPopup currently closes immediately in handlePurchase; change
handlePurchase to capture the return value of window.open (e.g., const popup =
window.open(...)) and only call onClose() if popup is non-null (popup opened).
If popup is null (blocked) or closed quickly, keep the dialog open and set local
state (e.g., popupBlocked or isLoading) to show an inline error/notification or
a "Open Stripe in new tab" link/button; also consider adding a short timeout
before auto-closing to allow the user to switch to the new tab. Update the
PurchaseCreditsPopup component to add that state and conditional UI based on
popupBlocked/isLoading.

Comment on lines +119 to +143
<Button
variant="ghost"
size="sm"
className="w-full flex items-center justify-between text-muted-foreground hover:text-foreground"
onClick={() => setIsCreditsVisible(!isCreditsVisible)}
>
<div className="flex items-center gap-2">
<Zap size={14} className="text-yellow-500" />
<span className="text-xs font-medium">Credits Preview</span>
</div>
{isCreditsVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</Button>

{isCreditsVisible && (
<div className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2">
<div className="flex justify-between items-center text-xs">
<span>Available Credits</span>
<span className="font-bold">0</span>
</div>
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
<p className="text-[10px] text-muted-foreground">Upgrade to get more credits</p>
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add accessibility attributes for the collapsible section.

The toggle button should include aria-expanded and aria-controls attributes to properly communicate state to assistive technologies.

Additionally, consider making "Upgrade to get more credits" an interactive element (link or button) that triggers the PurchaseCreditsPopup introduced elsewhere in this PR.

♻️ Proposed accessibility fix
 <Button
   variant="ghost"
   size="sm"
   className="w-full flex items-center justify-between text-muted-foreground hover:text-foreground"
   onClick={() => setIsCreditsVisible(!isCreditsVisible)}
+  aria-expanded={isCreditsVisible}
+  aria-controls="credits-preview-panel"
 >
   <div className="flex items-center gap-2">
     <Zap size={14} className="text-yellow-500" />
     <span className="text-xs font-medium">Credits Preview</span>
   </div>
   {isCreditsVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
 </Button>

 {isCreditsVisible && (
-  <div className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2">
+  <div 
+    id="credits-preview-panel"
+    className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2"
+  >
     <div className="flex justify-between items-center text-xs">
       <span>Available Credits</span>
       <span className="font-bold">0</span>
     </div>
     <div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
       <div className="bg-yellow-500 h-full w-[0%]" />
     </div>
-    <p className="text-[10px] text-muted-foreground">Upgrade to get more credits</p>
+    <Button 
+      variant="link" 
+      size="sm" 
+      className="text-[10px] text-muted-foreground p-0 h-auto"
+    >
+      Upgrade to get more credits
+    </Button>
   </div>
 )}
📝 Committable suggestion

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

Suggested change
<Button
variant="ghost"
size="sm"
className="w-full flex items-center justify-between text-muted-foreground hover:text-foreground"
onClick={() => setIsCreditsVisible(!isCreditsVisible)}
>
<div className="flex items-center gap-2">
<Zap size={14} className="text-yellow-500" />
<span className="text-xs font-medium">Credits Preview</span>
</div>
{isCreditsVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</Button>
{isCreditsVisible && (
<div className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2">
<div className="flex justify-between items-center text-xs">
<span>Available Credits</span>
<span className="font-bold">0</span>
</div>
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
<p className="text-[10px] text-muted-foreground">Upgrade to get more credits</p>
</div>
)}
<Button
variant="ghost"
size="sm"
className="w-full flex items-center justify-between text-muted-foreground hover:text-foreground"
onClick={() => setIsCreditsVisible(!isCreditsVisible)}
aria-expanded={isCreditsVisible}
aria-controls="credits-preview-panel"
>
<div className="flex items-center gap-2">
<Zap size={14} className="text-yellow-500" />
<span className="text-xs font-medium">Credits Preview</span>
</div>
{isCreditsVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</Button>
{isCreditsVisible && (
<div
id="credits-preview-panel"
className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2"
>
<div className="flex justify-between items-center text-xs">
<span>Available Credits</span>
<span className="font-bold">0</span>
</div>
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
<Button
variant="link"
size="sm"
className="text-[10px] text-muted-foreground p-0 h-auto"
>
Upgrade to get more credits
</Button>
</div>
)}
🤖 Prompt for AI Agents
In `@components/sidebar/chat-history-client.tsx` around lines 119 - 143, The
collapsible "Credits Preview" control lacks ARIA attributes and a trigger for
purchasing; update the Button component that toggles isCreditsVisible (onClick
=> setIsCreditsVisible) to include aria-expanded={isCreditsVisible} and
aria-controls referencing the collapsible region's id (e.g.,
credits-preview-panel) and give the collapsible div that id and role="region".
Also replace or wrap the "Upgrade to get more credits" text with an interactive
element that opens the existing PurchaseCreditsPopup (call the same handler/prop
used elsewhere in the PR) so keyboard and assistive users can activate the
purchase flow.

Comment on lines +138 to +140
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add ARIA attributes to the progress bar for accessibility.

The custom progress bar should include appropriate ARIA attributes to be accessible to screen readers.

♻️ Proposed fix
-<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
-  <div className="bg-yellow-500 h-full w-[0%]" />
-</div>
+<div 
+  role="progressbar" 
+  aria-valuenow={0} 
+  aria-valuemin={0} 
+  aria-valuemax={100}
+  aria-label="Credits usage"
+  className="w-full bg-secondary h-1.5 rounded-full overflow-hidden"
+>
+  <div className="bg-yellow-500 h-full w-[0%]" />
+</div>
📝 Committable suggestion

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

Suggested change
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
<div
role="progressbar"
aria-valuenow={0}
aria-valuemin={0}
aria-valuemax={100}
aria-label="Credits usage"
className="w-full bg-secondary h-1.5 rounded-full overflow-hidden"
>
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
🤖 Prompt for AI Agents
In `@components/sidebar/chat-history-client.tsx` around lines 138 - 140, The
progress bar markup lacks ARIA attributes; update the outer container (the div
with "w-full bg-secondary h-1.5 rounded-full overflow-hidden") to include
role="progressbar" plus aria-valuemin="0" and aria-valuemax="100", and set
aria-valuenow to the current numeric progress value (e.g., progressPercent)
and/or aria-valuetext for a human-readable label; also ensure the inner bar (the
div with "bg-yellow-500 h-full w-[0%]") uses a computed width from the same
progress value (not a hardcoded w-[0%]) so the ARIA value and visual width stay
in sync inside the ChatHistoryClient progress markup.

}

export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) {
const [usage, setUsage] = useState<any[]>([]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid any[] type for usage state.

Using any[] loses type safety. Define a proper interface for usage items.

♻️ Suggested type definition
+interface UsageItem {
+  details: string;
+  date: string;
+  change: number;
+}
+
 export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) {
-  const [usage, setUsage] = useState<any[]>([]);
+  const [usage, setUsage] = useState<UsageItem[]>([]);
📝 Committable suggestion

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

Suggested change
const [usage, setUsage] = useState<any[]>([]);
interface UsageItem {
details: string;
date: string;
change: number;
}
export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) {
const [usage, setUsage] = useState<UsageItem[]>([]);
🤖 Prompt for AI Agents
In `@components/usage-sidebar.tsx` at line 20, The state declaration uses a loose
any[] which loses type safety; define a concrete interface (e.g., UsageItem)
describing the properties used by this component (fields like id, date, amount,
description — matching how usage is accessed) and change the state to use
useState<UsageItem[]>([]) instead of any[], then update any references to usage,
setUsage, and props/handlers (e.g., where items are fetched or passed) to use
the new UsageItem type so TypeScript enforces correct shape across functions
like setUsage and rendering logic in the UsageSidebar component.

<div className="p-4 border rounded-xl space-y-4">
<div className="flex justify-between items-center">
<span className="italic font-medium text-lg">Free</span>
<Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Duplicate hardcoded Stripe URL.

This same Stripe URL appears in purchase-credits-popup.tsx. Extract it to a shared constant to ensure consistency and simplify future updates.

♻️ Suggested refactor

Create a constants file:

// lib/constants.ts
export const STRIPE_CHECKOUT_URL = 'https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00';

Then import and use it in both components.

🤖 Prompt for AI Agents
In `@components/usage-sidebar.tsx` at line 49, The Stripe checkout URL is
duplicated in the UsageSidebar Button onClick and in purchase-credits-popup.tsx;
extract the literal into a shared exported constant (e.g., STRIPE_CHECKOUT_URL)
and import it where needed. Create a constants module (for example
lib/constants.ts) exporting STRIPE_CHECKOUT_URL =
'https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', then replace the hardcoded
string in the UsageSidebar Button onClick handler (the Button component) and in
purchase-credits-popup.tsx to reference the imported STRIPE_CHECKOUT_URL
constant.

Comment on lines +84 to +87
<Button variant="ghost" size="icon" className="h-4 w-4">
<span className="sr-only">View more</span>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-4"><path d="M6.1584 3.1356C6.35366 2.94034 6.67024 2.94034 6.8655 3.1356L10.8655 7.13561C11.0608 7.33087 11.0608 7.64745 10.8655 7.84271L6.8655 11.8427C6.67024 12.038 6.35366 12.038 6.1584 11.8427C5.96314 11.6474 5.96314 11.3309 6.1584 11.1356L9.80485 7.48916L6.1584 3.84271C5.96314 3.64745 5.96314 3.33087 6.1584 3.1356Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SVG missing accessible title or aria-label.

The chevron icon SVG lacks accessible text. Screen readers cannot identify its purpose. Based on static analysis hint from Biome.

♿ Proposed accessibility fix
-              <Button variant="ghost" size="icon" className="h-4 w-4">
-                <span className="sr-only">View more</span>
-                <svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-4"><path d="M6.1584 3.1356C6.35366 2.94034 6.67024 2.94034 6.8655 3.1356L10.8655 7.13561C11.0608 7.33087 11.0608 7.64745 10.8655 7.84271L6.8655 11.8427C6.67024 12.038 6.35366 12.038 6.1584 11.8427C5.96314 11.6474 5.96314 11.3309 6.1584 11.1356L9.80485 7.48916L6.1584 3.84271C5.96314 3.64745 5.96314 3.33087 6.1584 3.1356Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
-              </Button>
+              <Button variant="ghost" size="icon" className="h-4 w-4" aria-label="View more">
+                <svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" aria-hidden="true"><path d="M6.1584 3.1356C6.35366 2.94034 6.67024 2.94034 6.8655 3.1356L10.8655 7.13561C11.0608 7.33087 11.0608 7.64745 10.8655 7.84271L6.8655 11.8427C6.67024 12.038 6.35366 12.038 6.1584 11.8427C5.96314 11.6474 5.96314 11.3309 6.1584 11.1356L9.80485 7.48916L6.1584 3.84271C5.96314 3.64745 5.96314 3.33087 6.1584 3.1356Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
+              </Button>

Alternatively, consider using a lucide-react icon (e.g., ChevronRight) for consistency with other icons in this file.

📝 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
<Button variant="ghost" size="icon" className="h-4 w-4">
<span className="sr-only">View more</span>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-4"><path d="M6.1584 3.1356C6.35366 2.94034 6.67024 2.94034 6.8655 3.1356L10.8655 7.13561C11.0608 7.33087 11.0608 7.64745 10.8655 7.84271L6.8655 11.8427C6.67024 12.038 6.35366 12.038 6.1584 11.8427C5.96314 11.6474 5.96314 11.3309 6.1584 11.1356L9.80485 7.48916L6.1584 3.84271C5.96314 3.64745 5.96314 3.33087 6.1584 3.1356Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
</Button>
<Button variant="ghost" size="icon" className="h-4 w-4" aria-label="View more">
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" aria-hidden="true"><path d="M6.1584 3.1356C6.35366 2.94034 6.67024 2.94034 6.8655 3.1356L10.8655 7.13561C11.0608 7.33087 11.0608 7.64745 10.8655 7.84271L6.8655 11.8427C6.67024 12.038 6.35366 12.038 6.1584 11.8427C5.96314 11.6474 5.96314 11.3309 6.1584 11.1356L9.80485 7.48916L6.1584 3.84271C5.96314 3.64745 5.96314 3.33087 6.1584 3.1356Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
</Button>
🧰 Tools
🪛 Biome (2.1.2)

[error] 86-86: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🤖 Prompt for AI Agents
In `@components/usage-sidebar.tsx` around lines 84 - 87, The SVG used inside the
Button lacks accessible labeling; update the Button/SVG so screen readers get a
meaningful name: add an explicit aria-label (or aria-labelledby) on the Button
component (e.g., "View more") and mark the inline SVG as decorative with
aria-hidden="true" (or alternatively add a <title> inside the SVG and role="img"
on the SVG if you want the SVG itself to be announced). Locate the Button and
its child <svg> in components/usage-sidebar.tsx and apply one of these fixes (or
replace the inline SVG with the lucide-react ChevronRight icon for consistent,
accessible icons).

Comment on lines +99 to +105
{usage.map((item, i) => (
<TableRow key={i}>
<TableCell className="text-xs font-medium">{item.details}</TableCell>
<TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
<TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
</TableRow>
))}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Using array index as React key.

Using i as the key can cause rendering issues if items are reordered or filtered. Once real data is integrated, use a unique identifier.

♻️ Suggested improvement
-                {usage.map((item, i) => (
-                  <TableRow key={i}>
+                {usage.map((item) => (
+                  <TableRow key={`${item.date}-${item.details}`}>
📝 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
{usage.map((item, i) => (
<TableRow key={i}>
<TableCell className="text-xs font-medium">{item.details}</TableCell>
<TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
<TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
</TableRow>
))}
{usage.map((item) => (
<TableRow key={`${item.date}-${item.details}`}>
<TableCell className="text-xs font-medium">{item.details}</TableCell>
<TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
<TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
</TableRow>
))}
🤖 Prompt for AI Agents
In `@components/usage-sidebar.tsx` around lines 99 - 105, The map over usage
currently uses the array index as the React key which can break reconciliation;
in the usage.map(...) replace key={i} on the TableRow with a stable unique
identifier from each item (e.g., item.id or another invariant like a
concatenation of item.date and item.details) so TableRow (and its children) use
a persistent key across reorders/filters; update any type/interface for the
items if needed to ensure the chosen unique field exists.

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

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

This change introduces several user-impacting and correctness issues: the purchase popup auto-opens on every load (severe UX regression), and both Dialog/Sheet onOpenChange handlers are wired in a way that can fire onClose at unintended times. There are also security concerns from window.open without noopener,noreferrer (reverse-tabnabbing). Finally, usage uses any[] and credits UI is hard-coded, which will impede maintainability and can appear broken to users.

Summary of changes

What this PR changes

  • Header billing/usage integration

    • Adds PurchaseCreditsPopup and UsageSidebar to components/header.tsx.
    • Replaces the Stripe checkout <a> link behind the TentTree icon with a button that opens the new usage sidebar.
    • Introduces useEffect to auto-open the purchase dialog on app load.
  • New UI components

    • Adds components/purchase-credits-popup.tsx: a Dialog prompting the user to upgrade and opening a Stripe checkout link in a new tab.
    • Adds components/usage-sidebar.tsx: a Sheet showing plan/credits and a table of usage items (currently mocked).
  • Sidebar credit preview toggle

    • Updates components/sidebar/chat-history-client.tsx to add a “Credits Preview” collapsible section with placeholder values.

Comment on lines +24 to +31
const [isPurchaseOpen, setIsPurchaseOpen] = useState(false)
const [isUsageOpen, setIsUsageOpen] = useState(false)

useEffect(() => {
// Open payment popup as soon as application opens
setIsPurchaseOpen(true)
}, [])

Choose a reason for hiding this comment

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

Auto-opening the purchase dialog on every app load is a major UX regression and will likely block onboarding/normal usage. The intent described in the PR context sounds like “integrate” rather than force-open. This should be gated behind a real condition (e.g., user is out of credits / feature flag / query param) and ideally persisted so it doesn’t show repeatedly once dismissed.

Suggestion

Gate the auto-open behind a condition and persist dismissal. For example:

const [isPurchaseOpen, setIsPurchaseOpen] = useState(false)

useEffect(() => {
  const dismissed = localStorage.getItem('purchasePopupDismissed') === '1'
  if (!dismissed && shouldPromptForUpgrade) setIsPurchaseOpen(true)
}, [shouldPromptForUpgrade])

const closePurchase = () => {
  localStorage.setItem('purchasePopupDismissed', '1')
  setIsPurchaseOpen(false)
}

Then pass closePurchase to onClose. Reply with "@CharlieHelps yes please" if you'd like me to add a commit implementing this gating/persistence pattern.

};

return (
<Dialog open={isOpen} onOpenChange={onClose}>

Choose a reason for hiding this comment

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

Dialog's onOpenChange typically receives the next open state ((open: boolean) => void). Passing onClose directly means it will be invoked for both open and close transitions, and it also ignores the intended next state. This can cause surprising behavior (e.g., clicking the trigger/open could immediately call onClose).

Suggestion

Handle the boolean argument and only close when open === false:

<Dialog
  open={isOpen}
  onOpenChange={(open) => {
    if (!open) onClose()
  }}
>

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this fix.

Comment on lines +21 to +24
const handlePurchase = () => {
window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
onClose();
};

Choose a reason for hiding this comment

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

Opening a new tab via window.open without noopener,noreferrer leaves you vulnerable to reverse-tabnabbing (the newly opened page can access window.opener). The previous implementation used rel="noopener noreferrer"; this should retain equivalent protection.

Suggestion

Use the third parameter to disable opener, and consider noreferrer as well:

window.open(url, '_blank', 'noopener,noreferrer')

Alternatively, render a normal <a target="_blank" rel="noopener noreferrer"> for navigation. Reply with "@CharlieHelps yes please" if you'd like me to add a commit applying this everywhere window.open is used in this PR.

}, [isOpen]);

return (
<Sheet open={isOpen} onOpenChange={onClose}>

Choose a reason for hiding this comment

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

onOpenChange={onClose} has the same issue here as in the dialog: the Sheet component’s open-change callback commonly receives the next open state. Calling onClose unconditionally can cause state thrash or unexpected close calls.

Suggestion

Mirror the boolean-handling pattern:

<Sheet open={isOpen} onOpenChange={(open) => {
  if (!open) onClose()
}}>

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this fix.

Comment on lines +19 to +22
export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) {
const [usage, setUsage] = useState<any[]>([]);
const [credits, setCredits] = useState(0);

Choose a reason for hiding this comment

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

This introduces useState<any[]> for usage. That’s an unsafe-but-type-valid escape hatch that makes future refactors harder and can hide real mistakes (e.g., typos in details/date/change). Prefer a small local type for the usage row shape.

Suggestion

Replace any[] with an explicit type:

type UsageItem = {
  details: string
  date: string
  change: number
}

const [usage, setUsage] = useState<UsageItem[]>([])

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.

Comment on lines +48 to +50
<span className="italic font-medium text-lg">Free</span>
<Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
</div>

Choose a reason for hiding this comment

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

The Upgrade button again uses window.open without noopener,noreferrer, reintroducing reverse-tabnabbing risk.

Suggestion

Use a safe open:

window.open('https://buy.stripe.com/...', '_blank', 'noopener,noreferrer')

Or render an <a target="_blank" rel="noopener noreferrer"> around the button. Reply with "@CharlieHelps yes please" if you'd like me to add a commit to fix this across the PR.

Comment on lines +40 to +44
<h2 className="text-xl font-semibold">Usage</h2>
<Button variant="ghost" size="icon" onClick={onClose}>
<X size={20} />
</Button>
</div>

Choose a reason for hiding this comment

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

DialogContent/SheetContent close buttons are missing accessible labels. The X icon button should include an aria-label or an sr-only span for screen readers.

Suggestion

Add an accessible label:

<Button variant="ghost" size="icon" onClick={onClose} aria-label="Close usage sidebar">
  <X size={20} />
</Button>

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this accessibility improvement.

Comment on lines +23 to +33
useEffect(() => {
if (isOpen) {
// Mock data for now as per the screenshot
setUsage([
{ details: 'Efficiently Fix Pull Request ...', date: '2026-01-17 08:05', change: -418 },
{ details: 'Fix Build and Add Parallel S...', date: '2026-01-16 06:10', change: -482 },
{ details: 'How to Add a Feature to a ...', date: '2026-01-14 10:42', change: -300 },
]);
setCredits(0);
}
}, [isOpen]);

Choose a reason for hiding this comment

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

useEffect sets mock usage data every time the sheet opens. That’s fine for scaffolding, but shipping this will present fake billing/usage to users. At minimum, gate it behind a feature flag or a TODO that prevents production exposure, and consider rendering an explicit “No usage data yet” empty state until a real API is wired.

Also, credits is always set to 0, which makes the UI misleading when combined with an “Upgrade” CTA.

Suggestion

Replace mock mutation with an empty-state and a placeholder loader until API integration:

useEffect(() => {
  if (!isOpen) return;
  // TODO: fetch usage/credits from API
  setUsage([]);
}, [isOpen]);

And in the UI, render a row like "No usage yet" when usage.length === 0.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with an empty-state + TODO and remove the mock rows.

Comment on lines +99 to +105
{usage.map((item, i) => (
<TableRow key={i}>
<TableCell className="text-xs font-medium">{item.details}</TableCell>
<TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
<TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
</TableRow>
))}

Choose a reason for hiding this comment

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

Using array index (key={i}) for rows is fragile if the list ever changes order or items are inserted/removed, which can cause incorrect row reuse and subtle UI bugs. Prefer a stable key derived from the data (e.g., an id, or a composite key such as date+details if that’s unique).

Suggestion

Use a stable key. If you don’t have an id yet, add one to the mock data and keep it when moving to real data:

{usage.map((item) => (
  <TableRow key={item.id}>
    ...
  </TableRow>
))}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit that introduces stable keys for usage rows.

Comment on lines +118 to +143
<div className="px-2">
<Button
variant="ghost"
size="sm"
className="w-full flex items-center justify-between text-muted-foreground hover:text-foreground"
onClick={() => setIsCreditsVisible(!isCreditsVisible)}
>
<div className="flex items-center gap-2">
<Zap size={14} className="text-yellow-500" />
<span className="text-xs font-medium">Credits Preview</span>
</div>
{isCreditsVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
</Button>

{isCreditsVisible && (
<div className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2">
<div className="flex justify-between items-center text-xs">
<span>Available Credits</span>
<span className="font-bold">0</span>
</div>
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
<div className="bg-yellow-500 h-full w-[0%]" />
</div>
<p className="text-[10px] text-muted-foreground">Upgrade to get more credits</p>
</div>
)}

Choose a reason for hiding this comment

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

The credits UI in the chat history is currently hard-coded to 0 and 0%, which makes the “Credits Preview” look broken rather than “preview”. If the real data source isn’t ready, this should be clearly marked as placeholder or wired to whatever the app already knows (even if it’s just a prop/config).

Suggestion

At minimum, extract constants and label as placeholder to avoid implying live data:

const availableCredits = 0 // TODO: wire to billing API
const percent = 0

Or hide the block unless data exists. Reply with "@CharlieHelps yes please" if you'd like me to add a commit that guards this UI behind a hasCreditsData flag and TODO plumbing.

@charliecreates charliecreates bot removed the request for review from CharlieHelps January 20, 2026 06:25
@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai the usage and credits ui that pops up on the right side should be in the square where the settings are displayed after the tenttree icon is clicked as seen in the image below, also there are two Xx's instead of one.
Screenshot 2026-01-19 103432

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the Header component to see how UsageSidebar is implemented
cat components/header.tsx

Length of output: 2847


🏁 Script executed:

#!/bin/bash
# Read the UsageSidebar component to check for duplicate close buttons
cat components/usage-sidebar.tsx

Length of output: 4951


🏁 Script executed:

#!/bin/bash
# Search for settings-related components to understand where the usage UI should be
fd -e tsx -e ts settings

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Search for any sheet or sidebar components related to settings
rg -n "settings" -i --type=tsx --type=ts -C 3

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Read the settings view component
cat components/settings/settings-view.tsx

Length of output: 1349


🏁 Script executed:

#!/bin/bash
# Read the settings component
cat components/settings/components/settings.tsx

Length of output: 10772


🏁 Script executed:

#!/bin/bash
# Search for where settings are opened/rendered in the header or other components
rg -n "settings" -i --type=ts --type=tsx -C 3 || rg -n "SettingsView\|Settings" -C 3

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Look for how the tenttree icon or settings area is structured
rg -n "TentTree" -C 5

Length of output: 5339


🏁 Script executed:

#!/bin/bash
# Read the ProfileToggle component to understand how settings are displayed
cat components/profile-toggle.tsx

Length of output: 3074


🏁 Script executed:

#!/bin/bash
# Read the ProfileToggle context to understand the pattern
cat components/profile-toggle-context.tsx

Length of output: 1274


🏁 Script executed:

#!/bin/bash
# Check if there's a Sheet component implementation to understand the built-in close button
cat components/ui/sheet.tsx

Length of output: 4275


🏁 Script executed:

#!/bin/bash
# Search for how the settings panel/view is rendered in the layout
rg -n "SettingsView" -A 5 -B 5

Length of output: 3149


@ngoiyaeric, I've identified both issues:

Issue 1: Duplicate X Close Buttons

The Sheet component automatically adds a close button (see components/ui/sheet.tsx lines 67-71), but the UsageSidebar component also manually adds one at lines 40-43. Remove the manual close button:

File: components/usage-sidebar.tsx

Remove lines 40-43:

// DELETE THESE LINES:
<Button variant="ghost" size="icon" onClick={onClose}>
  <X size={20} />
</Button>

The Sheet component will handle the close button automatically.

Issue 2: Usage UI Should Display in Settings Panel Area

Currently, the usage UI opens as a separate Sheet overlay. Based on your screenshot and the existing settings pattern, it should instead display in the same right-side panel where settings appear (where the map normally shows).

Looking at components/chat.tsx lines 110 and 221, settings use this pattern:

{activeView ? <SettingsView /> : <MapProvider />}

You have two architectural options:

Option A: Integrate into ProfileToggle System

Add a "Usage" option to the profile dropdown alongside Account, Settings, Appearance, and Security. This would require:

  1. Adding Usage to ProfileToggleEnum in components/profile-toggle-context.tsx
  2. Adding a menu item in components/profile-toggle.tsx
  3. Updating the conditional rendering in components/chat.tsx

Option B: Create Separate Context (Recommended)

Since the TentTree icon is separate from the profile icon, create a similar context pattern:

  1. Create a usage toggle context
  2. Refactor UsageSidebar to be a view component (not a Sheet)
  3. Update chat.tsx to check both contexts when deciding what to render

Which approach would you prefer? I can provide the complete code changes once you decide.


🏁 Script executed:

#!/bin/bash
# Show the exact lines that need to be modified in UsageSidebar
sed -n '38,45p' components/usage-sidebar.tsx

Length of output: 299


🏁 Script executed:

#!/bin/bash
# Check how the right panel is structured in chat.tsx to better understand the layout
sed -n '216,224p' components/chat.tsx

Length of output: 281


Task: Fix duplicate close buttons and integrate usage UI into settings panel area

Based on the code structure, here's the plan:

  1. Fix duplicate X buttons - Remove manual close button from UsageSidebar
  2. Create usage toggle context - Similar to ProfileToggleContext pattern
  3. Convert UsageSidebar to UsageView - Change from Sheet to regular view component
  4. Update Header - Use new context instead of local state
  5. Update Chat component - Include usage view in right panel conditional rendering

@ngoiyaeric, I've created a complete plan to fix both issues:

  1. Fixes the duplicate X buttons by removing the manual one
  2. Integrates usage UI into the settings panel area by:
    • Creating a usage toggle context (similar to profile toggle)
    • Converting UsageSidebar to UsageView (no longer a Sheet overlay)
    • Updating Header to use the new context
    • Updating Chat to conditionally render UsageView in the right panel

The usage view will now appear in the same fixed right panel where settings display, and the TentTree icon will toggle it on/off just like the profile dropdown toggles settings.

Note: You'll also need to add the UsageToggleProvider to your root layout file (wherever ProfileToggleProvider is added) to wrap your application.

Would you like me to proceed with implementing these changes?


🧠 Learnings used
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-20T10:04:04.963Z
Learning: The application has a comprehensive credits and payment UI system with: (1) PurchaseCreditsPopup that shows after user authentication with a 7-day cooldown, (2) History panel on the left side containing CreditsDisplay at the top and ChatHistoryClient below, (3) TentTree icon in header that opens UsageSidebar on the right side showing usage history and billing options. All these components are coordinated through RootLayout and only render for authenticated users.

💡 Use @coderabbitai plan to create a plan for code changes

@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai sure

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Task: Fix duplicate close buttons and integrate usage UI into settings panel

Overview:

  1. Remove duplicate X button from UsageSidebar
  2. Create usage toggle context following ProfileToggle pattern
  3. Convert UsageSidebar to UsageView (regular view instead of Sheet overlay)
  4. Update Header to use new context
  5. Update Chat to render UsageView in right panel
  6. Add UsageToggleProvider to root layout

@ngoiyaeric, I'll proceed with the implementation. The plan includes gathering some additional information about the exact file structure to ensure precise code changes. Once the scripts complete, I'll finalize all the code changes needed to fix both issues.


🏁 Script executed:

#!/bin/bash
# Read the chat.tsx file to understand the exact structure and find where to add the usage view logic
cat components/chat.tsx | head -50

Length of output: 2013


🏁 Script executed:

#!/bin/bash
# Search for the lines where activeView conditional rendering happens
rg -n "activeView.*SettingsView.*MapProvider" components/chat.tsx -A 2 -B 2

Length of output: 405


🏁 Script executed:

#!/bin/bash
# Find the layout file(s) and see where ProfileToggleProvider is added
rg -n "ProfileToggleProvider" -A 5 -B 5

Length of output: 3645


@ngoiyaeric, perfect! Here's the complete implementation to fix both issues:

Implementation Plan

1. Fix Duplicate X Button

File: components/usage-sidebar.tsx

Remove lines 40-43:

         <div className="p-6 space-y-6">
           <div className="flex justify-between items-center">
             <h2 className="text-xl font-semibold">Usage</h2>
-            <Button variant="ghost" size="icon" onClick={onClose}>
-              <X size={20} />
-            </Button>
           </div>

2. Create Usage Toggle Context

File: components/usage-toggle-context.tsx (new file)

'use client'
import { createContext, useContext, useState, ReactNode } from "react"

interface UsageToggleContextType {
  isUsageOpen: boolean
  toggleUsage: () => void
}

const UsageToggleContext = createContext<UsageToggleContextType | undefined>(undefined)

interface UsageToggleProviderProps {
  children: ReactNode
}

export const UsageToggleProvider: React.FC<UsageToggleProviderProps> = ({ children }) => {
  const [isUsageOpen, setIsUsageOpen] = useState(false)

  const toggleUsage = () => {
    setIsUsageOpen(prev => !prev)
  }

  return (
    <UsageToggleContext.Provider value={{ isUsageOpen, toggleUsage }}>
      {children}
    </UsageToggleContext.Provider>
  )
}

export const useUsageToggle = () => {
  const context = useContext(UsageToggleContext)
  if (context === undefined) {
    throw new Error('Usage toggle context must be used within a UsageToggleProvider')
  }
  return context
}

3. Create Usage View Component

File: components/usage-view.tsx (new file)

'use client';

import React, { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Zap, RefreshCw, LayoutPanelLeft, Minus } from 'lucide-react';
import { useUsageToggle } from './usage-toggle-context';

export function UsageView() {
  const [usage, setUsage] = useState<any[]>([]);
  const [credits, setCredits] = useState(0);
  const { toggleUsage } = useUsageToggle();

  useEffect(() => {
    // Mock data for now
    setUsage([
      { details: 'Efficiently Fix Pull Request ...', date: '2026-01-17 08:05', change: -418 },
      { details: 'Fix Build and Add Parallel S...', date: '2026-01-16 06:10', change: -482 },
      { details: 'How to Add a Feature to a ...', date: '2026-01-14 10:42', change: -300 },
    ]);
    setCredits(0);
  }, []);

  return (
    <div className="container py-8 h-full overflow-y-auto">
      <div className="flex justify-between items-center mb-8">
        <div>
          <h1 className="text-3xl font-bold tracking-tight">Usage</h1>
          <p className="text-muted-foreground">Track your credits and usage history</p>
        </div>
        <Button variant="ghost" size="icon" onClick={toggleUsage}>
          <Minus className="h-6 w-6" />
          <span className="sr-only">Close usage</span>
        </Button>
      </div>

      <div className="space-y-6">
        <div className="p-4 border rounded-xl space-y-4">
          <div className="flex justify-between items-center">
            <span className="italic font-medium text-lg">Free</span>
            <Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
          </div>

          <div className="space-y-2">
            <div className="flex items-center justify-between text-sm">
              <div className="flex items-center gap-2">
                <Zap size={16} className="text-muted-foreground" />
                <span>Credits</span>
              </div>
              <span className="font-bold">{credits}</span>
            </div>
            <div className="flex items-center justify-between text-xs text-muted-foreground pl-6">
              <span>Free credits</span>
              <span>0</span>
            </div>
          </div>

          <div className="space-y-1">
            <div className="flex items-center justify-between text-sm">
              <div className="flex items-center gap-2">
                <RefreshCw size={16} className="text-muted-foreground" />
                <span>Daily refresh credits</span>
              </div>
              <span className="font-bold">300</span>
            </div>
            <p className="text-[10px] text-muted-foreground pl-6">Refresh to 300 at 00:00 every day</p>
          </div>
        </div>

        <div className="space-y-4">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-2">
              <LayoutPanelLeft size={18} />
              <span className="font-medium">Website usage & billing</span>
            </div>
            <Button variant="ghost" size="icon" className="h-4 w-4">
              <span className="sr-only">View more</span>
              <svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-4"><path d="M6.1584 3.1356C6.35366 2.94034 6.67024 2.94034 6.8655 3.1356L10.8655 7.13561C11.0608 7.33087 11.0608 7.64745 10.8655 7.84271L6.8655 11.8427C6.67024 12.038 6.35366 12.038 6.1584 11.8427C5.96314 11.6474 5.96314 11.3309 6.1584 11.1356L9.80485 7.48916L6.1584 3.84271C5.96314 3.64745 5.96314 3.33087 6.1584 3.1356Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
            </Button>
          </div>

          <Table>
            <TableHeader>
              <TableRow>
                <TableHead className="text-xs">Details</TableHead>
                <TableHead className="text-xs">Date</TableHead>
                <TableHead className="text-xs text-right">Credits change</TableHead>
              </TableRow>
            </TableHeader>
            <TableBody>
              {usage.map((item, i) => (
                <TableRow key={i}>
                  <TableCell className="text-xs font-medium">{item.details}</TableCell>
                  <TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
                  <TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
                </TableRow>
              ))}
            </TableBody>
          </Table>
        </div>
      </div>
    </div>
  );
}

4. Update Header Component

File: components/header.tsx

 "use client"
 import React from 'react'
 import Image from 'next/image'
 import { useCalendarToggle } from './calendar-toggle-context'
+import { useUsageToggle } from './usage-toggle-context'
 import { ModeToggle } from './mode-toggle'
 import { cn } from '@/lib/utils'
 import HistoryContainer from './history-container'
 import { Button } from '@/components/ui/button'
 import {
   Search,
   CircleUserRound,
   Map,
   CalendarDays,
   TentTree
 } from 'lucide-react'
 import { MapToggle } from './map-toggle'
 import { ProfileToggle } from './profile-toggle'
 import { PurchaseCreditsPopup } from './purchase-credits-popup'
-import { UsageSidebar } from './usage-sidebar'
 import { useState, useEffect } from 'react'

 export const Header = () => {
   const { toggleCalendar } = useCalendarToggle()
+  const { toggleUsage } = useUsageToggle()
   const [isPurchaseOpen, setIsPurchaseOpen] = useState(false)
-  const [isUsageOpen, setIsUsageOpen] = useState(false)

   useEffect(() => {
     // Open payment popup as soon as application opens
     setIsPurchaseOpen(true)
   }, [])

   return (
     <>
       <PurchaseCreditsPopup isOpen={isPurchaseOpen} onClose={() => setIsPurchaseOpen(false)} />
-      <UsageSidebar isOpen={isUsageOpen} onClose={() => setIsUsageOpen(false)} />
       <header className="fixed w-full p-1 md:p-2 flex justify-between items-center z-20 backdrop-blur bg-background/95 border-b border-border/40">
       {/* ... rest of header ... */}
       
-        <Button variant="ghost" size="icon" onClick={() => setIsUsageOpen(true)}>
+        <Button variant="ghost" size="icon" onClick={toggleUsage}>
           <TentTree className="h-[1.2rem] w-[1.2rem]" />
         </Button>
         
         {/* ... in mobile menu buttons ... */}
-        <Button variant="ghost" size="icon" onClick={() => setIsUsageOpen(true)}>
+        <Button variant="ghost" size="icon" onClick={toggleUsage}>
           <TentTree className="h-[1.2rem] w-[1.2rem]" />
         </Button>

5. Update Chat Component

File: components/chat.tsx

Add imports after line 17:

 import { useProfileToggle, ProfileToggleEnum } from "@/components/profile-toggle-context";
+import { useUsageToggle } from "@/components/usage-toggle-context";
 import SettingsView from "@/components/settings/settings-view";
+import { UsageView } from "@/components/usage-view";

Add hook usage after line 35:

   const { activeView } = useProfileToggle();
+  const { isUsageOpen } = useUsageToggle();
   const { isCalendarOpen } = useCalendarToggle()

Update line 110 (mobile layout):

-          {activeView ? <SettingsView /> : <MapProvider />}
+          {isUsageOpen ? <UsageView /> : (activeView ? <SettingsView /> : <MapProvider />)}

Update line 221 (desktop layout):

-          {activeView ? <SettingsView /> : <MapProvider />}
+          {isUsageOpen ? <UsageView /> : (activeView ? <SettingsView /> : <MapProvider />)}

6. Add Provider to Layout

File: app/layout.tsx

Add import after line 14:

 import { ProfileToggleProvider } from '@/components/profile-toggle-context'
+import { UsageToggleProvider } from '@/components/usage-toggle-context'
 import { CalendarToggleProvider } from '@/components/calendar-toggle-context'

Wrap providers (lines 74-93):

         <CalendarToggleProvider>
           <MapToggleProvider>
             <ProfileToggleProvider>
+              <UsageToggleProvider>
                 <ThemeProvider
                   attribute="class"
                   defaultTheme="earth"
                   enableSystem
                   disableTransitionOnChange
                 >
                   {/* ... rest of children ... */}
                 </ThemeProvider>
+              </UsageToggleProvider>
             </ProfileToggleProvider>
           </MapToggleProvider>
         </CalendarToggleProvider>

Once these changes are applied:

  • ✅ The duplicate X button will be removed (Sheet provides it automatically)
  • ✅ Usage UI will display in the same right panel where settings show
  • ✅ TentTree icon will toggle the usage view on/off
  • ✅ Usage view will have priority over settings view when both are triggered

Let me know if you need any clarification on these changes!

🐰✨


🧠 Learnings used
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-20T10:04:04.963Z
Learning: The application has a comprehensive credits and payment UI system with: (1) PurchaseCreditsPopup that shows after user authentication with a 7-day cooldown, (2) History panel on the left side containing CreditsDisplay at the top and ChatHistoryClient below, (3) TentTree icon in header that opens UsageSidebar on the right side showing usage history and billing options. All these components are coordinated through RootLayout and only render for authenticated users.

💡 Use @coderabbitai plan to create a plan for code changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants