From 6bdcb155525f98a38a7b5f8c38d158b0f9b4ae8e Mon Sep 17 00:00:00 2001
From: NotII <46204250+NotII@users.noreply.github.com>
Date: Wed, 19 Mar 2025 01:46:09 +0100
Subject: [PATCH] HMM
---
app/auth/login/page.tsx | 3 +-
app/dashboard/admin/page.tsx | 172 +++++
components/admin/AdminAnalytics.tsx | 937 ++++++++++++++++++++++++++++
public/favicon.ico | 0
4 files changed, 1111 insertions(+), 1 deletion(-)
create mode 100644 app/dashboard/admin/page.tsx
create mode 100644 components/admin/AdminAnalytics.tsx
create mode 100644 public/favicon.ico
diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx
index 19cd2dd..8fea306 100644
--- a/app/auth/login/page.tsx
+++ b/app/auth/login/page.tsx
@@ -13,7 +13,7 @@ import { Label } from "@/components/ui/label";
import { toast } from "sonner";
export default function LoginPage() {
- const [username, setUsername] = useState("");
+ const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isRedirecting, setIsRedirecting] = useState(false);
@@ -26,6 +26,7 @@ export default function LoginPage() {
progress: 0,
message: "Preparing your session..."
});
+ const [error, setError] = useState("");
const router = useRouter();
// Check if already logged in
diff --git a/app/dashboard/admin/page.tsx b/app/dashboard/admin/page.tsx
new file mode 100644
index 0000000..579c561
--- /dev/null
+++ b/app/dashboard/admin/page.tsx
@@ -0,0 +1,172 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Button } from "@/components/ui/button";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import {
+ AlertCircle, Users, MessageSquare, BarChart as BarChartIcon, Trash2, Bot, Database,
+ DollarSign, CreditCard, Landmark, Shield, Tag
+} from "lucide-react";
+import AdminAnalytics from "@/components/admin/AdminAnalytics";
+
+export default function AdminDashboardPage() {
+ const [activeTab, setActiveTab] = useState("overview");
+ const [isAdmin, setIsAdmin] = useState(false);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ // Check if the current user is an admin
+ const checkAdminStatus = async () => {
+ try {
+ // Get auth token from cookies
+ const token = document.cookie
+ .split("; ")
+ .find((row) => row.startsWith("Authorization="))
+ ?.split("=")[1];
+
+ if (!token) {
+ setIsAdmin(false);
+ setLoading(false);
+ return;
+ }
+
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/me`, {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ const data = await response.json();
+ // Check if vendor has admin privileges
+ setIsAdmin(data.vendor?.isAdmin === true);
+ } catch (error) {
+ console.error("Error checking admin status:", error);
+ setIsAdmin(false);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ checkAdminStatus();
+ }, []);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (!isAdmin) {
+ return (
+
+
+
+ Access Denied
+
+ You do not have permission to access the admin dashboard.
+
+
+
+ );
+ }
+
+ return (
+
+
Admin Dashboard
+
+
+
+ Overview
+ Vendors
+ Bot Management
+ Sessions
+ Analytics
+
+
+
+
+ setActiveTab("vendors")}>
+
+ Vendor Management
+
+
+
+
+ View and manage vendors across the platform.
+
+
+
+
+ setActiveTab("bot")}>
+
+ Bot Management
+
+
+
+
+ Configure and manage the Telegram bot settings.
+
+
+
+
+ setActiveTab("sessions")}>
+
+ Session Management
+
+
+
+
+ Manage active sessions and clear inactive ones.
+
+
+
+
+ setActiveTab("analytics")}>
+
+ Analytics
+
+
+
+
+ View platform statistics and performance metrics.
+
+
+
+
+ setActiveTab("analytics")}>
+
+ Promotions
+
+
+
+
+ Track promotion usage and performance metrics.
+
+
+
+
+ setActiveTab("analytics")}>
+
+ Payments & Escrow
+
+
+
+
+ Monitor escrow status and financial operations.
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/admin/AdminAnalytics.tsx b/components/admin/AdminAnalytics.tsx
new file mode 100644
index 0000000..f2c1357
--- /dev/null
+++ b/components/admin/AdminAnalytics.tsx
@@ -0,0 +1,937 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Button } from "@/components/ui/button";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { AlertCircle, BarChart as BarChartIcon, LineChart, RefreshCw, Users, ShoppingCart,
+ TrendingUp, TrendingDown, DollarSign, MessageSquare, Clock, Landmark } from "lucide-react";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
+import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip, Legend, XAxis, YAxis, BarChart, Bar } from "recharts";
+
+// API response data structure
+interface AnalyticsData {
+ vendors?: {
+ total?: number;
+ newToday?: number;
+ newThisWeek?: number;
+ activeToday?: number;
+ active?: number;
+ stores?: number;
+ dailyGrowth?: { date: string; count: number }[];
+ data?: { date: string; count: number }[];
+ };
+ orders?: {
+ total?: number;
+ totalToday?: number;
+ totalThisWeek?: number;
+ recent?: number;
+ pending?: number;
+ completed?: number;
+ dailyOrders?: { date: string; count: number }[];
+ data?: { date: string; count: number }[];
+ };
+ revenue?: {
+ total?: number;
+ today?: number;
+ thisWeek?: number;
+ dailyRevenue?: { date: string; amount: number }[];
+ };
+ engagement?: {
+ totalMessages?: number;
+ activeChats?: number;
+ avgResponseTime?: number;
+ dailyMessages?: { date: string; count: number }[];
+ };
+ products?: {
+ total?: number;
+ recent?: number;
+ };
+ stores?: {
+ total?: number;
+ active?: number;
+ };
+ sessions?: {
+ total?: number;
+ active?: number;
+ };
+ promotions?: {
+ total?: number;
+ active?: number;
+ used?: number;
+ totalDiscountAmount?: number;
+ discountPercentage?: number;
+ topPromotions?: { _id: string; count: number; totalDiscount: number }[];
+ discountByType?: {
+ percentage?: { count: number; totalDiscount: number };
+ fixed?: { count: number; totalDiscount: number };
+ };
+ };
+ chats?: {
+ totalChats?: number;
+ activeChats?: number;
+ totalMessages?: number;
+ recentMessages?: number;
+ messagesBySender?: { buyer: number; vendor: number };
+ avgResponseTimeMin?: number;
+ messagesByHour?: number[];
+ };
+ telegram?: {
+ totalUsers?: number;
+ totalStoreConnections?: number;
+ avgStoresPerUser?: number;
+ multiStoreUsers?: number;
+ };
+ escrow?: {
+ total?: number;
+ active?: number;
+ released?: number;
+ disputed?: number;
+ disputeRate?: number;
+ totalByCurrency?: { ltc: number; btc: number; xmr: number };
+ heldByCurrency?: { ltc: number; btc: number; xmr: number };
+ avgReleaseTimeHours?: number;
+ };
+ security?: {
+ blockedUsers?: {
+ total?: number;
+ recentlyBlocked?: number;
+ }
+ };
+}
+
+export default function AdminAnalytics() {
+ const [analyticsData, setAnalyticsData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [dateRange, setDateRange] = useState("7days");
+ const [errorMessage, setErrorMessage] = useState(null);
+ const [refreshing, setRefreshing] = useState(false);
+
+ const fetchAnalyticsData = async () => {
+ try {
+ setLoading(true);
+ setErrorMessage(null);
+
+ const token = document.cookie
+ .split("; ")
+ .find((row) => row.startsWith("Authorization="))
+ ?.split("=")[1];
+
+ console.log("Token from cookie:", token ? token.substring(0, 10) + "..." : "not found");
+ console.log("API URL:", `${process.env.NEXT_PUBLIC_API_URL}/admin/analytics?range=${dateRange}`);
+
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/admin/analytics?range=${dateRange}`, {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ console.log("Response status:", response.status);
+ console.log("Response ok:", response.ok);
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ console.error("Error response:", errorText);
+ throw new Error(`Failed to fetch analytics data: ${response.status} ${errorText}`);
+ }
+
+ const data = await response.json();
+ console.log("Analytics data received:", data);
+ setAnalyticsData(data);
+ } catch (error: any) {
+ console.error("Error fetching analytics data:", error);
+ setErrorMessage(`Failed to load analytics data: ${error.message}`);
+
+ // For demo purposes, load mock data if API fails
+ setAnalyticsData({
+ vendors: {
+ total: 1248,
+ newToday: 24,
+ newThisWeek: 124,
+ activeToday: 356,
+ stores: 100,
+ dailyGrowth: [
+ { date: "2023-11-01", count: 15 },
+ { date: "2023-11-02", count: 18 },
+ { date: "2023-11-03", count: 12 },
+ { date: "2023-11-04", count: 22 },
+ { date: "2023-11-05", count: 26 },
+ { date: "2023-11-06", count: 17 },
+ { date: "2023-11-07", count: 14 },
+ ],
+ },
+ orders: {
+ total: 5672,
+ totalToday: 86,
+ totalThisWeek: 432,
+ pending: 47,
+ completed: 5625,
+ dailyOrders: [
+ { date: "2023-11-01", count: 54 },
+ { date: "2023-11-02", count: 62 },
+ { date: "2023-11-03", count: 58 },
+ { date: "2023-11-04", count: 71 },
+ { date: "2023-11-05", count: 68 },
+ { date: "2023-11-06", count: 65 },
+ { date: "2023-11-07", count: 54 },
+ ],
+ },
+ revenue: {
+ total: 156320,
+ today: 3280,
+ thisWeek: 18642,
+ dailyRevenue: [
+ { date: "2023-11-01", amount: 2345 },
+ { date: "2023-11-02", amount: 2762 },
+ { date: "2023-11-03", amount: 2458 },
+ { date: "2023-11-04", amount: 3121 },
+ { date: "2023-11-05", amount: 2968 },
+ { date: "2023-11-06", amount: 2708 },
+ { date: "2023-11-07", amount: 2280 },
+ ],
+ },
+ engagement: {
+ totalMessages: 32450,
+ activeChats: 123,
+ avgResponseTime: 5.2,
+ dailyMessages: [
+ { date: "2023-11-01", count: 432 },
+ { date: "2023-11-02", count: 512 },
+ { date: "2023-11-03", count: 478 },
+ { date: "2023-11-04", count: 541 },
+ { date: "2023-11-05", count: 498 },
+ { date: "2023-11-06", count: 465 },
+ { date: "2023-11-07", count: 423 },
+ ],
+ },
+ });
+ } finally {
+ setLoading(false);
+ setRefreshing(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchAnalyticsData();
+ }, [dateRange]);
+
+ const handleRefresh = () => {
+ setRefreshing(true);
+ fetchAnalyticsData();
+ };
+
+ if (loading && !analyticsData) {
+ return (
+
+ );
+ }
+
+ // These would normally be real chart components
+ // For this example, we'll use simplified representations
+ const SimpleAreaChart = ({ data, label, color }: { data: any[]; label: string; color: string }) => {
+ // Handle empty data or undefined
+ if (!data || data.length === 0) {
+ return (
+
+ No data available
+
+ );
+ }
+
+ return (
+
+ {/* Simplified chart visualization */}
+
+ {data.map((item, index) => {
+ // Normalize height between 10% and 90%
+ const values = data.map(d => d.count || d.amount);
+ const max = Math.max(...values);
+ const min = Math.min(...values);
+ const range = max - min;
+ const value = item.count || item.amount;
+ const normalizedHeight = range === 0
+ ? 50
+ : 10 + (((value - min) / range) * 80);
+
+ return (
+
+ );
+ })}
+
+
+
+ {new Date(data[0].date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})}
+ {new Date(data[data.length - 1].date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'})}
+
+
+ );
+ };
+
+ // Function to render trend indicator
+ const TrendIndicator = ({ value, prefix = "", suffix = "" }: { value: number, prefix?: string, suffix?: string }) => {
+ if (value === 0) return null;
+
+ return (
+ 0 ? 'text-green-500' : 'text-red-500'}`}>
+ {value > 0 ? : }
+ {prefix}{Math.abs(value).toFixed(1)}{suffix}
+
+ );
+ };
+
+ // Mini sparkline component for metric cards
+ const MiniSparkline = ({ data, color = "#3b82f6" }: { data: any[], color?: string }) => {
+ if (!data || data.length === 0) return null;
+
+ const values = data.map(item => item.count || item.amount || 0);
+ const max = Math.max(...values, 1);
+
+ return (
+
+ {values.map((value, index) => {
+ const height = (value / max) * 100;
+ return (
+
+ );
+ })}
+
+ );
+ };
+
+ return (
+
+
+
Platform Analytics
+
+
+
+
+
+
+ {errorMessage && (
+
+
+ Error
+ {errorMessage}
+
+ )}
+
+
+
+ Orders
+ Vendors
+ Revenue
+ Promotions
+ Communication
+ Payments
+
+
+
+
+
+ Order Analytics
+ Order volume and revenue metrics
+
+
+ {analyticsData && (
+
+
+
Total Orders
+
{analyticsData.orders?.total?.toLocaleString() || '0'}
+
+
+
+
Orders Today
+
{analyticsData.orders?.totalToday?.toLocaleString() || '0'}
+
+
+
+
Pending Orders
+
{analyticsData.orders?.pending?.toLocaleString() || '0'}
+
+
+
+
Revenue
+
${analyticsData.revenue?.total?.toLocaleString() || '0'}
+
+
+ )}
+
+
+
Order & Revenue Trends
+ {analyticsData && (
+
+
Order and revenue charts would render here
+
+ )}
+
+
+
+
+
+
+
+
+ Vendor Analytics
+ Detailed vendor metrics and store statistics
+
+
+ {analyticsData && (
+
+
+
Total Vendors
+
{analyticsData.vendors?.total?.toLocaleString() || '0'}
+
+
+
+
New Today
+
{analyticsData.vendors?.newToday?.toLocaleString() || '0'}
+
+
+
+
New This Week
+
{analyticsData.vendors?.newThisWeek?.toLocaleString() || '0'}
+
+
+
+
Active Stores
+
{analyticsData.vendors?.stores?.toLocaleString() || analyticsData.stores?.total?.toLocaleString() || '0'}
+
+
+ )}
+
+
+
Vendor Growth Trend
+ {analyticsData && (
+
+
Vendor growth chart would render here
+
+ )}
+
+
+
+
+
+
+
+
+ Revenue Analytics
+ Revenue and financial metrics
+
+
+ {analyticsData && (
+
+
+
Total Processed
+
${analyticsData.revenue?.total?.toLocaleString() || '0'}
+
+
+
+
Today's Revenue
+
${analyticsData.revenue?.today?.toLocaleString() || '0'}
+
+
+
+
This Week
+
${analyticsData.revenue?.thisWeek?.toLocaleString() || '0'}
+
+
+
+
Average Per Order
+
+ ${analyticsData.revenue && analyticsData.orders?.total
+ ? Math.round((analyticsData.revenue.total || 0) / analyticsData.orders.total).toLocaleString()
+ : '0'}
+
+
+
+ )}
+
+
+
Revenue Trend
+ {analyticsData && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ Active Promotions
+
+
+
+
+
+ {analyticsData?.promotions?.active || 0}
+
+
+ out of {analyticsData?.promotions?.total || 0} total
+
+
+
+
+
+
+ Total Discount Amount
+
+
+
+
+
+ ${analyticsData?.promotions?.totalDiscountAmount?.toLocaleString() || 0}
+
+
+ {analyticsData?.promotions?.discountPercentage || 0}% of revenue
+
+
+
+
+
+
+ Promotion Uses
+
+
+
+
+
+ {analyticsData?.promotions?.used || 0}
+
+
+
+
+
+
+
+
+ Top Promotions
+ Most used promotion codes
+
+
+
+ {analyticsData?.promotions?.topPromotions?.map((promo, i) => (
+
+
+
{promo._id}
+
+ {promo.count} uses ยท ${promo.totalDiscount.toLocaleString()}
+
+
+
+ ${(promo.totalDiscount / promo.count).toFixed(2)}
+ avg
+
+
+ ))}
+
+
+
+
+
+
+ Discount Types
+ Breakdown by discount type
+
+
+
+ {analyticsData?.promotions?.discountByType && (
+
+
+
+
Percentage Discounts
+
+ {analyticsData.promotions.discountByType.percentage?.count || 0} uses
+
+
+
+ ${analyticsData.promotions.discountByType.percentage?.totalDiscount?.toLocaleString() || 0}
+
+
+
+
+
Fixed Amount Discounts
+
+ {analyticsData.promotions.discountByType.fixed?.count || 0} uses
+
+
+
+ ${analyticsData.promotions.discountByType.fixed?.totalDiscount?.toLocaleString() || 0}
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ Total Chats
+
+
+
+
+
+ {analyticsData?.chats?.totalChats || 0}
+
+
+ {analyticsData?.chats?.activeChats || 0} active in selected period
+
+
+
+
+
+
+ Total Messages
+
+
+
+
+
+ {analyticsData?.chats?.totalMessages?.toLocaleString() || 0}
+
+
+ {analyticsData?.chats?.recentMessages?.toLocaleString() || 0} in selected period
+
+
+
+
+
+
+ Avg Response Time
+
+
+
+
+
+ {analyticsData?.chats?.avgResponseTimeMin || 0} min
+
+
+
+
+
+
+
+
+ Message Volume by Hour
+ Chat activity distribution
+
+
+
+ {analyticsData?.chats?.messagesByHour && (
+
+ ({ hour, count }))}>
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ Message Breakdown
+ By sender type
+
+
+
+ {analyticsData?.chats?.messagesBySender && (
+
+
+
+ |
+ |
+
+
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ Telegram Users
+ Bot user statistics
+
+
+
+
+
+
+ {analyticsData?.telegram?.totalUsers || 0}
+
+
+
+
+
+ {analyticsData?.telegram?.totalStoreConnections || 0}
+
+
+
+
+
+ {analyticsData?.telegram?.avgStoresPerUser?.toFixed(2) || 0}
+
+
+
+
+
+ {analyticsData?.telegram?.multiStoreUsers || 0}
+
+
+
+
+
+
+
+
+ Security
+ Blocked users and security metrics
+
+
+
+
+
+
+ {analyticsData?.security?.blockedUsers?.total || 0}
+
+
+
+
+
Recently Blocked
+
+ In selected period
+
+
+
+ {analyticsData?.security?.blockedUsers?.recentlyBlocked || 0}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Active Escrows
+
+
+
+
+
+ {analyticsData?.escrow?.active || 0}
+
+
+ out of {analyticsData?.escrow?.total || 0} total
+
+
+
+
+
+
+ Released Escrows
+
+
+
+
+
+ {analyticsData?.escrow?.released || 0}
+
+
+
+
+
+
+ Dispute Rate
+
+
+
+
+
+ {analyticsData?.escrow?.disputeRate?.toFixed(1) || 0}%
+
+
+ {analyticsData?.escrow?.disputed || 0} disputed escrows
+
+
+
+
+
+
+
+
+ Escrow by Currency
+ Currently held amounts
+
+
+
+ {analyticsData?.escrow?.heldByCurrency && (
+ <>
+
+
+
+ {analyticsData.escrow.heldByCurrency.btc.toFixed(4)} BTC
+
+
+
+
+
+ {analyticsData.escrow.heldByCurrency.ltc.toFixed(2)} LTC
+
+
+
+
+
+ {analyticsData.escrow.heldByCurrency.xmr.toFixed(2)} XMR
+
+
+ >
+ )}
+
+
+
+
+
+
+ Escrow Metrics
+ Release time and amounts
+
+
+
+
+
+
+ {analyticsData?.escrow?.avgReleaseTimeHours ? (
+ <>
+ {Math.floor(analyticsData.escrow.avgReleaseTimeHours / 24)} days, {analyticsData.escrow.avgReleaseTimeHours % 24} hours
+ >
+ ) : (
+ "8 days, 0 hours"
+ )}
+
+
+
+ {analyticsData?.escrow?.totalByCurrency && (
+
+
Total Processed (lifetime)
+
+
+ BTC:
+ {analyticsData.escrow.totalByCurrency.btc.toFixed(4)} BTC
+
+
+ LTC:
+ {analyticsData.escrow.totalByCurrency.ltc.toFixed(2)} LTC
+
+
+ XMR:
+ {analyticsData.escrow.totalByCurrency.xmr.toFixed(2)} XMR
+
+
+
+ )}
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..e69de29