diff --git a/app/api/billing/balance/route.js b/app/api/billing/balance/route.js
new file mode 100644
index 0000000..83cefe0
--- /dev/null
+++ b/app/api/billing/balance/route.js
@@ -0,0 +1,14 @@
+import { NextResponse } from 'next/server';
+import { requireUser } from '@/lib/session';
+import { engine } from '@/lib/engine';
+
+export async function GET(req) {
+ const user = await requireUser();
+ if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ try {
+ const data = await engine.getBillingBalance(user.id);
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/app/api/billing/transactions/route.js b/app/api/billing/transactions/route.js
new file mode 100644
index 0000000..20cce0d
--- /dev/null
+++ b/app/api/billing/transactions/route.js
@@ -0,0 +1,15 @@
+import { NextResponse } from 'next/server';
+import { requireUser } from '@/lib/session';
+import { engine } from '@/lib/engine';
+
+export async function GET(req) {
+ const user = await requireUser();
+ if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ const { searchParams } = new URL(req.url);
+ try {
+ const data = await engine.getTransactions(Object.fromEntries(searchParams));
+ return NextResponse.json(data);
+ } catch (err) {
+ return NextResponse.json({ error: err.message }, { status: 500 });
+ }
+}
diff --git a/app/billing/page.js b/app/billing/page.js
new file mode 100644
index 0000000..56bed8a
--- /dev/null
+++ b/app/billing/page.js
@@ -0,0 +1,155 @@
+'use client';
+import { useState, useEffect } from 'react';
+import { Coins, RefreshCw, TrendingDown, TrendingUp, Loader2, ArrowRight } from 'lucide-react';
+import Link from 'next/link';
+
+const TYPE_LABELS = {
+ spend_image: { label: 'Генерация картинки', sign: '-', color: 'text-red-400' },
+ spend_text_post: { label: 'Генерация поста', sign: '-', color: 'text-red-400' },
+ spend_article: { label: 'Генерация статьи', sign: '-', color: 'text-red-400' },
+ spend_autopublish:{ label: 'Публикация', sign: '-', color: 'text-gray-400' },
+ plan_credit: { label: 'Начисление по тарифу',sign: '+', color: 'text-green-400' },
+ topup: { label: 'Пополнение', sign: '+', color: 'text-green-400' },
+ bonus: { label: 'Бонус', sign: '+', color: 'text-blue-400' },
+ refund: { label: 'Возврат', sign: '+', color: 'text-blue-400' },
+};
+
+function fmtDate(s) {
+ const d = new Date(s);
+ return d.toLocaleString('ru-RU', { day:'2-digit', month:'2-digit', hour:'2-digit', minute:'2-digit' });
+}
+
+export default function BillingPage() {
+ const [balance, setBalance] = useState(null);
+ const [txs, setTxs] = useState([]);
+ const [total, setTotal] = useState(0);
+ const [loading, setLoading] = useState(true);
+ const [page, setPage] = useState(0);
+ const PER_PAGE = 30;
+
+ async function load(p = 0) {
+ setLoading(true);
+ try {
+ const [balRes, txRes] = await Promise.all([
+ fetch('/api/billing/balance').then(r => r.json()),
+ fetch(`/api/billing/transactions?limit=${PER_PAGE}&offset=${p * PER_PAGE}`).then(r => r.json()),
+ ]);
+ setBalance(balRes);
+ setTxs(txRes.transactions || []);
+ setTotal(txRes.total || 0);
+ } catch {}
+ setLoading(false);
+ }
+
+ useEffect(() => { load(0); }, []);
+
+ const PLAN_COLORS = { free: 'text-gray-400', starter: 'text-blue-400', pro: 'text-purple-400', business: 'text-yellow-400' };
+
+ return (
+
+
+
+ История
+ {loading &&