-
-
Notifications
You must be signed in to change notification settings - Fork 7
Integrate billing popup, usage sidebar, and credit preview toggle #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
WalkthroughIntroduces 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||||||
There was a problem hiding this 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
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
components/header.tsxcomponents/purchase-credits-popup.tsxcomponents/sidebar/chat-history-client.tsxcomponents/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.
| import { PurchaseCreditsPopup } from './purchase-credits-popup' | ||
| import { UsageSidebar } from './usage-sidebar' | ||
| import { useState, useEffect } from 'react' |
There was a problem hiding this comment.
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.
| export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) { | ||
| const handlePurchase = () => { | ||
| window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank'); | ||
| onClose(); | ||
| }; |
There was a problem hiding this comment.
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.
| <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> | ||
| )} |
There was a problem hiding this comment.
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.
| <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.
| <div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden"> | ||
| <div className="bg-yellow-500 h-full w-[0%]" /> | ||
| </div> |
There was a problem hiding this comment.
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.
| <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[]>([]); |
There was a problem hiding this comment.
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.
| 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> |
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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).
| {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> | ||
| ))} |
There was a problem hiding this comment.
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.
| {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.
There was a problem hiding this 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
PurchaseCreditsPopupandUsageSidebartocomponents/header.tsx. - Replaces the Stripe checkout
<a>link behind theTentTreeicon with a button that opens the new usage sidebar. - Introduces
useEffectto auto-open the purchase dialog on app load.
- Adds
-
New UI components
- Adds
components/purchase-credits-popup.tsx: aDialogprompting the user to upgrade and opening a Stripe checkout link in a new tab. - Adds
components/usage-sidebar.tsx: aSheetshowing plan/credits and a table of usage items (currently mocked).
- Adds
-
Sidebar credit preview toggle
- Updates
components/sidebar/chat-history-client.tsxto add a “Credits Preview” collapsible section with placeholder values.
- Updates
| const [isPurchaseOpen, setIsPurchaseOpen] = useState(false) | ||
| const [isUsageOpen, setIsUsageOpen] = useState(false) | ||
|
|
||
| useEffect(() => { | ||
| // Open payment popup as soon as application opens | ||
| setIsPurchaseOpen(true) | ||
| }, []) | ||
|
|
There was a problem hiding this comment.
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}> |
There was a problem hiding this comment.
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.
| const handlePurchase = () => { | ||
| window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank'); | ||
| onClose(); | ||
| }; |
There was a problem hiding this comment.
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}> |
There was a problem hiding this comment.
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.
| export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) { | ||
| const [usage, setUsage] = useState<any[]>([]); | ||
| const [credits, setCredits] = useState(0); | ||
|
|
There was a problem hiding this comment.
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.
| <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> |
There was a problem hiding this comment.
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.
| <h2 className="text-xl font-semibold">Usage</h2> | ||
| <Button variant="ghost" size="icon" onClick={onClose}> | ||
| <X size={20} /> | ||
| </Button> | ||
| </div> |
There was a problem hiding this comment.
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.
| 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]); |
There was a problem hiding this comment.
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.
| {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> | ||
| ))} |
There was a problem hiding this comment.
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.
| <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> | ||
| )} |
There was a problem hiding this comment.
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 = 0Or 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.
|
@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. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2847 🏁 Script executed: Length of output: 4951 🏁 Script executed: Length of output: 156 🏁 Script executed: Length of output: 27 🏁 Script executed: Length of output: 1349 🏁 Script executed: Length of output: 10772 🏁 Script executed: Length of output: 27 🏁 Script executed: Length of output: 5339 🏁 Script executed: Length of output: 3074 🏁 Script executed: Length of output: 1274 🏁 Script executed: Length of output: 4275 🏁 Script executed: Length of output: 3149
Issue 1: Duplicate X Close ButtonsThe File: 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 AreaCurrently, 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 {activeView ? <SettingsView /> : <MapProvider />}You have two architectural options: Option A: Integrate into ProfileToggle SystemAdd a "Usage" option to the profile dropdown alongside Account, Settings, Appearance, and Security. This would require:
Option B: Create Separate Context (Recommended)Since the TentTree icon is separate from the profile icon, create a similar context pattern:
Which approach would you prefer? I can provide the complete code changes once you decide. 🏁 Script executed: Length of output: 299 🏁 Script executed: Length of output: 281 Task: Fix duplicate close buttons and integrate usage UI into settings panel areaBased on the code structure, here's the plan:
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 Would you like me to proceed with implementing these changes? 🧠 Learnings used💡 Use |
|
@coderabbitai sure |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainTask: Fix duplicate close buttons and integrate usage UI into settings panelOverview:
🏁 Script executed: Length of output: 2013 🏁 Script executed: Length of output: 405 🏁 Script executed: Length of output: 3645
Implementation Plan1. Fix Duplicate X ButtonFile: 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 ContextFile: '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 ComponentFile: '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 ComponentFile: "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 ComponentFile: 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 LayoutFile: 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:
Let me know if you need any clarification on these changes! 🐰✨ 🧠 Learnings used💡 Use |

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
File Walkthrough
header.tsx
Integrate billing popup and usage sidebarcomponents/header.tsx
PurchaseCreditsPopupandUsageSidebarcomponents with statemanagement
useEffecthook to open purchase popup automatically on app startuponClickhandler to openusage sidebar
sidebar
components
purchase-credits-popup.tsx
New billing payment popup componentcomponents/purchase-credits-popup.tsx
window
usage-sidebar.tsx
New usage tracking sidebar componentcomponents/usage-sidebar.tsx
(300)
chat-history-client.tsx
Add collapsible credits preview togglecomponents/sidebar/chat-history-client.tsx
isCreditsVisiblestate to toggle credits preview sectionSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.