VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
"use client";
import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { auth } from '@/lib/firebase/config';
import { toast } from 'sonner';
import { DollarSign, TrendingUp, TrendingDown, Calendar } from 'lucide-react';
import { useParams } from 'next/navigation';
interface CostData {
total: number;
thisMonth: number;
lastMonth: number;
byProject: Array<{
projectId: string;
projectName: string;
cost: number;
}>;
byDate: Array<{
date: string;
cost: number;
}>;
}
export default function CostsPage() {
const params = useParams();
const workspace = params.workspace as string;
const [costs, setCosts] = useState<CostData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadCosts();
}, []);
const loadCosts = async () => {
try {
const user = auth.currentUser;
if (!user) return;
const token = await user.getIdToken();
const response = await fetch('/api/costs', {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
setCosts(data);
}
} catch (error) {
console.error('Error loading costs:', error);
toast.error('Failed to load cost data');
} finally {
setLoading(false);
}
};
const percentageChange = costs && costs.lastMonth > 0
? ((costs.thisMonth - costs.lastMonth) / costs.lastMonth) * 100
: 0;
return (
<div className="flex h-full flex-col overflow-auto">
<div className="flex-1 p-8 space-y-8 max-w-7xl">
{/* Header */}
<div>
<h1 className="text-4xl font-bold mb-2">Costs</h1>
<p className="text-muted-foreground text-lg">
Track your AI usage costs across all projects
</p>
</div>
{/* Summary Cards */}
{loading ? (
<Card>
<CardContent className="pt-6">
<p className="text-center text-muted-foreground">Loading cost data...</p>
</CardContent>
</Card>
) : (
<>
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Costs</CardTitle>
<DollarSign className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">${costs?.total.toFixed(2) || '0.00'}</div>
<p className="text-xs text-muted-foreground">All time</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">This Month</CardTitle>
<Calendar className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">${costs?.thisMonth.toFixed(2) || '0.00'}</div>
<p className="text-xs text-muted-foreground">
{new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">vs Last Month</CardTitle>
{percentageChange >= 0 ? (
<TrendingUp className="h-4 w-4 text-red-500" />
) : (
<TrendingDown className="h-4 w-4 text-green-500" />
)}
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{percentageChange >= 0 ? '+' : ''}{percentageChange.toFixed(1)}%
</div>
<p className="text-xs text-muted-foreground">
Last month: ${costs?.lastMonth.toFixed(2) || '0.00'}
</p>
</CardContent>
</Card>
</div>
{/* Costs by Project */}
<Card>
<CardHeader>
<CardTitle>Costs by Project</CardTitle>
<CardDescription>Your spending broken down by project</CardDescription>
</CardHeader>
<CardContent>
{costs?.byProject && costs.byProject.length > 0 ? (
<div className="space-y-3">
{costs.byProject.map((project) => (
<div key={project.projectId} className="flex items-center justify-between p-3 rounded-lg border">
<div>
<p className="font-medium">{project.projectName}</p>
<p className="text-sm text-muted-foreground">Project ID: {project.projectId}</p>
</div>
<div className="text-right">
<p className="text-lg font-semibold">${project.cost.toFixed(2)}</p>
<p className="text-xs text-muted-foreground">
{((project.cost / (costs.total || 1)) * 100).toFixed(1)}% of total
</p>
</div>
</div>
))}
</div>
) : (
<p className="text-center text-muted-foreground py-8">No project costs yet</p>
)}
</CardContent>
</Card>
{/* Info Card */}
<Card className="border-blue-500/20 bg-blue-500/5">
<CardHeader>
<CardTitle className="text-base">About Cost Tracking</CardTitle>
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p>
<strong>📊 Automatic Tracking:</strong> All AI API costs are automatically tracked when you use Vibn features.
</p>
<p>
<strong>💰 Your Keys, Your Costs:</strong> Costs reflect usage of your own API keys - Vibn doesn't add any markup.
</p>
<p>
<strong>📈 Project Attribution:</strong> Costs are attributed to projects based on session metadata.
</p>
</CardContent>
</Card>
</>
)}
</div>
</div>
);
}