Skip to content

Commit 6eeae28

Browse files
committed
Integrate billing popup, usage sidebar, and credit preview toggle
1 parent 25e7bb4 commit 6eeae28

File tree

5 files changed

+222
-10
lines changed

5 files changed

+222
-10
lines changed

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/header.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,24 @@ import {
1515
} from 'lucide-react'
1616
import { MapToggle } from './map-toggle'
1717
import { ProfileToggle } from './profile-toggle'
18+
import { PurchaseCreditsPopup } from './purchase-credits-popup'
19+
import { UsageSidebar } from './usage-sidebar'
20+
import { useState, useEffect } from 'react'
1821

1922
export const Header = () => {
2023
const { toggleCalendar } = useCalendarToggle()
24+
const [isPurchaseOpen, setIsPurchaseOpen] = useState(false)
25+
const [isUsageOpen, setIsUsageOpen] = useState(false)
26+
27+
useEffect(() => {
28+
// Open payment popup as soon as application opens
29+
setIsPurchaseOpen(true)
30+
}, [])
31+
2132
return (
33+
<>
34+
<PurchaseCreditsPopup isOpen={isPurchaseOpen} onClose={() => setIsPurchaseOpen(false)} />
35+
<UsageSidebar isOpen={isUsageOpen} onClose={() => setIsUsageOpen(false)} />
2236
<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">
2337
<div>
2438
<a href="/">
@@ -53,11 +67,9 @@ export const Header = () => {
5367

5468
<div id="header-search-portal" />
5569

56-
<a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer">
57-
<Button variant="ghost" size="icon">
58-
<TentTree className="h-[1.2rem] w-[1.2rem]" />
59-
</Button>
60-
</a>
70+
<Button variant="ghost" size="icon" onClick={() => setIsUsageOpen(true)}>
71+
<TentTree className="h-[1.2rem] w-[1.2rem]" />
72+
</Button>
6173

6274
<ModeToggle />
6375

@@ -67,14 +79,13 @@ export const Header = () => {
6779
{/* Mobile menu buttons */}
6880
<div className="flex md:hidden gap-2">
6981

70-
<a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer">
71-
<Button variant="ghost" size="icon">
72-
<TentTree className="h-[1.2rem] w-[1.2rem]" />
73-
</Button>
74-
</a>
82+
<Button variant="ghost" size="icon" onClick={() => setIsUsageOpen(true)}>
83+
<TentTree className="h-[1.2rem] w-[1.2rem]" />
84+
</Button>
7585
<ProfileToggle/>
7686
</div>
7787
</header>
88+
</>
7889
)
7990
}
8091

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogHeader,
8+
DialogTitle,
9+
DialogDescription,
10+
DialogFooter,
11+
} from '@/components/ui/dialog';
12+
import { Button } from '@/components/ui/button';
13+
import { CreditCard, Zap } from 'lucide-react';
14+
15+
interface PurchaseCreditsPopupProps {
16+
isOpen: boolean;
17+
onClose: () => void;
18+
}
19+
20+
export function PurchaseCreditsPopup({ isOpen, onClose }: PurchaseCreditsPopupProps) {
21+
const handlePurchase = () => {
22+
window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank');
23+
onClose();
24+
};
25+
26+
return (
27+
<Dialog open={isOpen} onOpenChange={onClose}>
28+
<DialogContent className="sm:max-w-[425px]">
29+
<DialogHeader>
30+
<DialogTitle className="flex items-center gap-2">
31+
<Zap className="text-yellow-500" />
32+
Upgrade Your Plan
33+
</DialogTitle>
34+
<DialogDescription>
35+
You&apos;ve reached your credit limit. Upgrade now to continue using all features seamlessly.
36+
</DialogDescription>
37+
</DialogHeader>
38+
<div className="grid gap-4 py-4">
39+
<div className="flex items-center justify-between p-4 border rounded-lg bg-muted/50">
40+
<div>
41+
<p className="font-medium">Standard Tier</p>
42+
<p className="text-sm text-muted-foreground">Unlimited searches & more</p>
43+
</div>
44+
<p className="font-bold">$20/mo</p>
45+
</div>
46+
</div>
47+
<DialogFooter>
48+
<Button variant="outline" onClick={onClose}>Later</Button>
49+
<Button onClick={handlePurchase} className="gap-2">
50+
<CreditCard size={16} />
51+
Pay Now
52+
</Button>
53+
</DialogFooter>
54+
</DialogContent>
55+
</Dialog>
56+
);
57+
}

components/sidebar/chat-history-client.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '@/components/ui/alert-dialog';
1919
import { toast } from 'sonner';
2020
import { Spinner } from '@/components/ui/spinner';
21+
import { Zap, ChevronDown, ChevronUp } from 'lucide-react';
2122
import HistoryItem from '@/components/history-item'; // Adjust path if HistoryItem is moved or renamed
2223
import type { Chat as DrizzleChat } from '@/lib/actions/chat-db'; // Use the Drizzle-based Chat type
2324

@@ -31,6 +32,7 @@ export function ChatHistoryClient({}: ChatHistoryClientProps) {
3132
const [error, setError] = useState<string | null>(null);
3233
const [isClearPending, startClearTransition] = useTransition();
3334
const [isAlertDialogOpen, setIsAlertDialogOpen] = useState(false);
35+
const [isCreditsVisible, setIsCreditsVisible] = useState(false);
3436
const router = useRouter();
3537

3638
useEffect(() => {
@@ -113,6 +115,34 @@ export function ChatHistoryClient({}: ChatHistoryClientProps) {
113115

114116
return (
115117
<div className="flex flex-col flex-1 space-y-3 h-full">
118+
<div className="px-2">
119+
<Button
120+
variant="ghost"
121+
size="sm"
122+
className="w-full flex items-center justify-between text-muted-foreground hover:text-foreground"
123+
onClick={() => setIsCreditsVisible(!isCreditsVisible)}
124+
>
125+
<div className="flex items-center gap-2">
126+
<Zap size={14} className="text-yellow-500" />
127+
<span className="text-xs font-medium">Credits Preview</span>
128+
</div>
129+
{isCreditsVisible ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
130+
</Button>
131+
132+
{isCreditsVisible && (
133+
<div className="mt-2 p-3 rounded-lg bg-muted/50 border border-border/50 space-y-2">
134+
<div className="flex justify-between items-center text-xs">
135+
<span>Available Credits</span>
136+
<span className="font-bold">0</span>
137+
</div>
138+
<div className="w-full bg-secondary h-1.5 rounded-full overflow-hidden">
139+
<div className="bg-yellow-500 h-full w-[0%]" />
140+
</div>
141+
<p className="text-[10px] text-muted-foreground">Upgrade to get more credits</p>
142+
</div>
143+
)}
144+
</div>
145+
116146
<div className="flex flex-col gap-2 flex-1 overflow-y-auto">
117147
{!chats?.length ? (
118148
<div className="text-foreground/30 text-sm text-center py-4">

components/usage-sidebar.tsx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
'use client';
2+
3+
import React, { useEffect, useState } from 'react';
4+
import {
5+
Sheet,
6+
SheetContent,
7+
SheetHeader,
8+
SheetTitle,
9+
} from '@/components/ui/sheet';
10+
import { Button } from '@/components/ui/button';
11+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
12+
import { Zap, RefreshCw, LayoutPanelLeft, X } from 'lucide-react';
13+
14+
interface UsageSidebarProps {
15+
isOpen: boolean;
16+
onClose: () => void;
17+
}
18+
19+
export function UsageSidebar({ isOpen, onClose }: UsageSidebarProps) {
20+
const [usage, setUsage] = useState<any[]>([]);
21+
const [credits, setCredits] = useState(0);
22+
23+
useEffect(() => {
24+
if (isOpen) {
25+
// Mock data for now as per the screenshot
26+
setUsage([
27+
{ details: 'Efficiently Fix Pull Request ...', date: '2026-01-17 08:05', change: -418 },
28+
{ details: 'Fix Build and Add Parallel S...', date: '2026-01-16 06:10', change: -482 },
29+
{ details: 'How to Add a Feature to a ...', date: '2026-01-14 10:42', change: -300 },
30+
]);
31+
setCredits(0);
32+
}
33+
}, [isOpen]);
34+
35+
return (
36+
<Sheet open={isOpen} onOpenChange={onClose}>
37+
<SheetContent className="w-full sm:max-w-[400px] p-0 overflow-y-auto">
38+
<div className="p-6 space-y-6">
39+
<div className="flex justify-between items-center">
40+
<h2 className="text-xl font-semibold">Usage</h2>
41+
<Button variant="ghost" size="icon" onClick={onClose}>
42+
<X size={20} />
43+
</Button>
44+
</div>
45+
46+
<div className="p-4 border rounded-xl space-y-4">
47+
<div className="flex justify-between items-center">
48+
<span className="italic font-medium text-lg">Free</span>
49+
<Button size="sm" className="rounded-full px-4" onClick={() => window.open('https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00', '_blank')}>Upgrade</Button>
50+
</div>
51+
52+
<div className="space-y-2">
53+
<div className="flex items-center justify-between text-sm">
54+
<div className="flex items-center gap-2">
55+
<Zap size={16} className="text-muted-foreground" />
56+
<span>Credits</span>
57+
</div>
58+
<span className="font-bold">{credits}</span>
59+
</div>
60+
<div className="flex items-center justify-between text-xs text-muted-foreground pl-6">
61+
<span>Free credits</span>
62+
<span>0</span>
63+
</div>
64+
</div>
65+
66+
<div className="space-y-1">
67+
<div className="flex items-center justify-between text-sm">
68+
<div className="flex items-center gap-2">
69+
<RefreshCw size={16} className="text-muted-foreground" />
70+
<span>Daily refresh credits</span>
71+
</div>
72+
<span className="font-bold">300</span>
73+
</div>
74+
<p className="text-[10px] text-muted-foreground pl-6">Refresh to 300 at 00:00 every day</p>
75+
</div>
76+
</div>
77+
78+
<div className="space-y-4">
79+
<div className="flex items-center justify-between">
80+
<div className="flex items-center gap-2">
81+
<LayoutPanelLeft size={18} />
82+
<span className="font-medium">Website usage & billing</span>
83+
</div>
84+
<Button variant="ghost" size="icon" className="h-4 w-4">
85+
<span className="sr-only">View more</span>
86+
<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>
87+
</Button>
88+
</div>
89+
90+
<Table>
91+
<TableHeader>
92+
<TableRow>
93+
<TableHead className="text-xs">Details</TableHead>
94+
<TableHead className="text-xs">Date</TableHead>
95+
<TableHead className="text-xs text-right">Credits change</TableHead>
96+
</TableRow>
97+
</TableHeader>
98+
<TableBody>
99+
{usage.map((item, i) => (
100+
<TableRow key={i}>
101+
<TableCell className="text-xs font-medium">{item.details}</TableCell>
102+
<TableCell className="text-[10px] text-muted-foreground">{item.date}</TableCell>
103+
<TableCell className="text-xs text-right font-medium">{item.change}</TableCell>
104+
</TableRow>
105+
))}
106+
</TableBody>
107+
</Table>
108+
</div>
109+
</div>
110+
</SheetContent>
111+
</Sheet>
112+
);
113+
}

0 commit comments

Comments
 (0)