-
-
Notifications
You must be signed in to change notification settings - Fork 7
Fix Usage View Integration #449
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,10 +15,23 @@ import { | |
| } from 'lucide-react' | ||
| import { MapToggle } from './map-toggle' | ||
| import { ProfileToggle } from './profile-toggle' | ||
| import { PurchaseCreditsPopup } from './purchase-credits-popup' | ||
| import { useUsageToggle } from './usage-toggle-context' | ||
| import { useState, useEffect } from 'react' | ||
|
|
||
| export const Header = () => { | ||
| const { toggleCalendar } = useCalendarToggle() | ||
| const [isPurchaseOpen, setIsPurchaseOpen] = useState(false) | ||
| const { toggleUsage } = useUsageToggle() | ||
|
|
||
| useEffect(() => { | ||
| // Open payment popup as soon as application opens | ||
| setIsPurchaseOpen(true) | ||
| }, []) | ||
|
|
||
|
Comment on lines
+24
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
SuggestionGate the popup behind an explicit condition (e.g., user is out of credits, or a query param like const [isPurchaseOpen, setIsPurchaseOpen] = useState(false)
// Example: open only when a flag is set
useEffect(() => {
if (shouldPromptForUpgrade) setIsPurchaseOpen(true)
}, [shouldPromptForUpgrade])Or remove the auto-open entirely and open it only from Reply with "@CharlieHelps yes please" if you’d like me to add a commit implementing a safe gating approach (query-param or credit-based). |
||
| return ( | ||
| <> | ||
| <PurchaseCreditsPopup isOpen={isPurchaseOpen} onClose={() => setIsPurchaseOpen(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"> | ||
| <div> | ||
| <a href="/"> | ||
|
|
@@ -53,11 +66,9 @@ export const Header = () => { | |
|
|
||
| <div id="header-search-portal" /> | ||
|
|
||
| <a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer"> | ||
| <Button variant="ghost" size="icon"> | ||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||
| </Button> | ||
| </a> | ||
| <Button variant="ghost" size="icon" onClick={toggleUsage}> | ||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||
| </Button> | ||
|
Comment on lines
+69
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add accessible labels to icon-only usage buttons. Icon-only buttons need ♿ Proposed fix- <Button variant="ghost" size="icon" onClick={toggleUsage}>
+ <Button variant="ghost" size="icon" onClick={toggleUsage} aria-label="Open usage" title="Open usage">
<TentTree className="h-[1.2rem] w-[1.2rem]" />
</Button>
...
- <Button variant="ghost" size="icon" onClick={toggleUsage}>
+ <Button variant="ghost" size="icon" onClick={toggleUsage} aria-label="Open usage" title="Open usage">
<TentTree className="h-[1.2rem] w-[1.2rem]" />
</Button>Also applies to: 81-83 🤖 Prompt for AI Agents |
||
|
|
||
| <ModeToggle /> | ||
|
|
||
|
|
@@ -67,15 +78,14 @@ export const Header = () => { | |
| {/* Mobile menu buttons */} | ||
| <div className="flex md:hidden gap-2"> | ||
|
|
||
| <a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer"> | ||
| <Button variant="ghost" size="icon"> | ||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||
| </Button> | ||
| </a> | ||
| <Button variant="ghost" size="icon" onClick={toggleUsage}> | ||
| <TentTree className="h-[1.2rem] w-[1.2rem]" /> | ||
| </Button> | ||
| <ProfileToggle/> | ||
| </div> | ||
| </header> | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| export default Header | ||
| export default Header | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,57 @@ | ||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| Dialog, | ||||||||||||||||||||||
| DialogContent, | ||||||||||||||||||||||
| DialogHeader, | ||||||||||||||||||||||
| DialogTitle, | ||||||||||||||||||||||
| DialogDescription, | ||||||||||||||||||||||
| DialogFooter, | ||||||||||||||||||||||
| } from '@/components/ui/dialog'; | ||||||||||||||||||||||
| import { Button } from '@/components/ui/button'; | ||||||||||||||||||||||
| import { CreditCard, Zap } from 'lucide-react'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| interface PurchaseCreditsPopupProps { | ||||||||||||||||||||||
| isOpen: boolean; | ||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) { | ||||||||||||||||||||||
| const handlePurchase = () => { | ||||||||||||||||||||||
| window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank'); | ||||||||||||||||||||||
| onClose(); | ||||||||||||||||||||||
|
Comment on lines
+21
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: To prevent reverse tabnabbing, you need to ensure the newly opened page cannot access Recommended patterns (per MDN)
Sources[1] MDN: Harden Suggested fix- window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
+ window.open(
+ 'https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00',
+ '_blank',
+ 'noopener,noreferrer'
+ );📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
Comment on lines
+21
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Opening Stripe with SuggestionPrefer an const w = window.open(url, '_blank', 'noopener,noreferrer')
w?.opener = nullReply with "@CharlieHelps yes please" if you’d like me to add a commit updating both |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| <Dialog open={isOpen} onOpenChange={onClose}> | ||||||||||||||||||||||
| <DialogContent className="sm:max-w-[425px]"> | ||||||||||||||||||||||
|
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
SuggestionHandle <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. |
||||||||||||||||||||||
| <DialogHeader> | ||||||||||||||||||||||
| <DialogTitle className="flex items-center gap-2"> | ||||||||||||||||||||||
| <Zap className="text-yellow-500" /> | ||||||||||||||||||||||
| Upgrade Your Plan | ||||||||||||||||||||||
| </DialogTitle> | ||||||||||||||||||||||
| <DialogDescription> | ||||||||||||||||||||||
| You've reached your credit limit. Upgrade now to continue using all features seamlessly. | ||||||||||||||||||||||
| </DialogDescription> | ||||||||||||||||||||||
| </DialogHeader> | ||||||||||||||||||||||
| <div className="grid gap-4 py-4"> | ||||||||||||||||||||||
| <div className="flex items-center justify-between p-4 border rounded-lg bg-muted/50"> | ||||||||||||||||||||||
| <div> | ||||||||||||||||||||||
| <p className="font-medium">Standard Tier</p> | ||||||||||||||||||||||
| <p className="text-sm text-muted-foreground">Unlimited searches & more</p> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <p className="font-bold">$20/mo</p> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
| <DialogFooter> | ||||||||||||||||||||||
| <Button variant="outline" onClick={onClose}>Later</Button> | ||||||||||||||||||||||
| <Button onClick={handlePurchase} className="gap-2"> | ||||||||||||||||||||||
| <CreditCard size={16} /> | ||||||||||||||||||||||
| Pay Now | ||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||
| </DialogFooter> | ||||||||||||||||||||||
| </DialogContent> | ||||||||||||||||||||||
| </Dialog> | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from '@/components/ui/alert-dialog'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from 'sonner'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Spinner } from '@/components/ui/spinner'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Zap, ChevronDown, ChevronUp } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import HistoryItem from '@/components/history-item'; // Adjust path if HistoryItem is moved or renamed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Chat as DrizzleChat } from '@/lib/actions/chat-db'; // Use the Drizzle-based Chat type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -31,6 +32,7 @@ export function ChatHistoryClient({}: ChatHistoryClientProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [error, setError] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isClearPending, startClearTransition] = useTransition(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isAlertDialogOpen, setIsAlertDialogOpen] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isCreditsVisible, setIsCreditsVisible] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -113,6 +115,34 @@ export function ChatHistoryClient({}: ChatHistoryClientProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col flex-1 space-y-3 h-full"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <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> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+118
to
+143
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new “Credits Preview” UI is hardcoded to SuggestionDrive the preview from real credit data (context, server call, or a prop) and render an explicit loading/unknown state when unavailable. If the data isn’t ready yet, consider removing the UI until it’s wired. Reply with "@CharlieHelps yes please" if you’d like me to add a commit that adds a minimal
Comment on lines
+118
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Credits preview is hardcoded (will show incorrect data). 🤖 Prompt for AI Agents
Comment on lines
+119
to
+143
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add disclosure a11y state on the toggle button. 🔧 Suggested diff- <Button
+ <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"
>
@@
- {isCreditsVisible && (
- <div className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2">
+ {isCreditsVisible && (
+ <div
+ id="credits-preview-panel"
+ className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2"
+ >📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-2 flex-1 overflow-y-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {!chats?.length ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="text-foreground/30 text-sm text-center py-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| 'use client' | ||
|
|
||
| import { createContext, useContext, useState, ReactNode } from "react" | ||
|
|
||
| interface UsageToggleContextType { | ||
| isUsageOpen: boolean | ||
| toggleUsage: () => void | ||
| } | ||
|
|
||
| const UsageToggleContext = createContext<UsageToggleContextType | undefined>(undefined) | ||
|
|
||
| export const UsageToggleProvider: React.FC<{ children: ReactNode }> = ({ 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) throw new Error('useUsageToggle must be used within UsageToggleProvider') | ||
| return context | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,96 @@ | ||||||||||||||||||||||||||||||
| '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] = useState([ | ||||||||||||||||||||||||||||||
| { 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 }, | ||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||
| const [credits] = useState(0) | ||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usage data is hardcoded (view will always be wrong). Also applies to: 40-61, 83-88 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| const { toggleUsage } = useUsageToggle() | ||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
SuggestionEither wire this to real data (server-provided props / fetch hook) or make it explicitly empty/loading until data exists. At minimum, remove unused imports and avoid const usage = [] as Array<{details: string; date: string; change: number}>
const credits = 0If real data is available elsewhere, pass it in as props to keep the component presentational. Reply with "@CharlieHelps yes please" if you’d like me to add a commit that removes placeholder state, deletes the unused import, and adds a simple empty-state UI. |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+36
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Reverse tabnabbing happens when you open an untrusted page in a new tab/window and that page can use Fix for
|
||||||||||||||||||||||||||||||
| <Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}> | |
| Upgrade | |
| <Button | |
| size="sm" | |
| className="rounded-full px-4" | |
| onClick={() => | |
| window.open( | |
| 'https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', | |
| '_blank', | |
| 'noopener,noreferrer' | |
| ) | |
| } | |
| > | |
| Upgrade |
🤖 Prompt for AI Agents
In `@components/usage-view.tsx` around lines 35 - 36, Update the onClick handler
in the Button in usage-view.tsx (the inline arrow function passed to onClick) to
call window.open with a third argument including "noopener,noreferrer" (i.e.,
window.open(url, '_blank', 'noopener,noreferrer')) so the opened page cannot
access window.opener and the Referer header is suppressed; leave the existing
target '_blank' as the second argument.
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.
UsageView opens Stripe via window.open(...) from an onClick handler, which repeats the reverse-tabnabbing issue and also makes it harder to test/override. The header previously used a plain <a> with rel="noopener noreferrer"; keeping links as links is more accessible and secure.
Suggestion
Use an anchor with proper rel attributes:
<a
href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00"
target="_blank"
rel="noopener noreferrer"
>
<Button size="sm" className="rounded-full px-4">Upgrade</Button>
</a>Reply with "@CharlieHelps yes please" if you’d like me to add a commit applying this change.
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.
Avoid auto-opening the purchase popup on every mount.
Unconditional
useEffectopens the dialog for all users and bypasses existing eligibility checks (auth + cooldown + credit state). Gate this behind an explicit user action or a purchase-eligibility signal before callingsetIsPurchaseOpen(true). Based on learnings, this popup is intended to appear only after authentication with a 7‑day cooldown.🤖 Prompt for AI Agents