From f2a0fd1260acb6c36249fad0ac1cebf60042bc47 Mon Sep 17 00:00:00 2001 From: belviskhoremk Date: Mon, 23 Feb 2026 17:24:27 +0000 Subject: [PATCH] ADDED ANALYTICS --- src/App.tsx | 7 +- src/components/Layout.tsx | 177 +++++++++-------- src/lib/utils.ts | 57 +++++- src/pages/AnalyticsPage.tsx | 379 ++++++++++++++++++++++++++++++++++++ src/pages/PricingPage.tsx | 65 ++++--- src/services/api.ts | 85 ++++---- 6 files changed, 597 insertions(+), 173 deletions(-) create mode 100644 src/pages/AnalyticsPage.tsx diff --git a/src/App.tsx b/src/App.tsx index cc698dc..26e666e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,7 +6,7 @@ import { PublicLayout } from '@/components/PublicLayout' import { Spinner } from '@/components/ui' import './App.css' -// IMP-02 FIX: Route code splitting with lazy imports +// Route code splitting with lazy imports const LandingPage = lazy(() => import('@/pages/LandingPage').then(m => ({ default: m.LandingPage }))) const LoginPage = lazy(() => import('@/pages/AuthPages').then(m => ({ default: m.LoginPage }))) const SignupPage = lazy(() => import('@/pages/AuthPages').then(m => ({ default: m.SignupPage }))) @@ -16,6 +16,7 @@ const MarketplacePage = lazy(() => import('@/pages/MarketplacePage').then(m => ( const ChatbotDetailPage = lazy(() => import('@/pages/MarketplacePage').then(m => ({ default: m.ChatbotDetailPage }))) const PricingPage = lazy(() => import('@/pages/PricingPage').then(m => ({ default: m.PricingPage }))) const SettingsPage = lazy(() => import('@/pages/SettingsPage').then(m => ({ default: m.SettingsPage }))) +const AnalyticsPage = lazy(() => import('@/pages/AnalyticsPage').then(m => ({ default: m.AnalyticsPage }))) const PageLoader = () => (
@@ -35,14 +36,11 @@ const PublicOnlyRoute: React.FC<{ children: React.ReactNode }> = ({ children }) return <>{children} } -// BUG-07/08 FIX: Smart wrapper that uses AppLayout for authenticated users -// and PublicLayout for unauthenticated users, solving the "lost sidebar" issue const SmartPublicRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { isAuthenticated } = useAuthStore() if (isAuthenticated) { return {children} } - // R-07 FIX: PublicLayout adds navigation header for unauthenticated users return {children} } @@ -63,6 +61,7 @@ export const App: React.FC = () => ( {/* Protected */} } /> + } /> } /> } /> } /> diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 73e15e9..59d7c68 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -6,11 +6,12 @@ import { authAPI } from '@/services/api' import { getPlanColor } from '@/lib/utils' import { Bot, LayoutDashboard, ShoppingBag, Settings, - LogOut, Menu, X, Sparkles, ChevronDown + LogOut, Menu, X, Sparkles, ChevronDown, BarChart3 } from 'lucide-react' const NAV_ITEMS = [ { label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard }, + { label: 'Analytics', href: '/analytics', icon: BarChart3 }, { label: 'Marketplace', href: '/marketplace', icon: ShoppingBag }, { label: 'Settings', href: '/settings', icon: Settings }, ] @@ -28,96 +29,94 @@ export const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) } return ( -
- {/* Mobile backdrop */} - {sidebarOpen && ( -
setSidebarOpen(false)} /> - )} +
+ {/* Mobile backdrop */} + {sidebarOpen && ( +
setSidebarOpen(false)} /> + )} - {/* Sidebar */} - - - {/* Main content */} -
- {/* Mobile header */} -
- -
-
- -
- Contexta -
-
- -
- {children} -
-
) -} +} \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 87ce78e..889f507 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -60,19 +60,64 @@ export function useDebounce(value: T, delay: number = 300): T { return debouncedValue } + // ─── REMOVED: AVAILABLE_MODELS ──────────────────────────────────────────────── // Models are now loaded dynamically from the backend via GET /api/v1/models/available -// This ensures the frontend always reflects the backend's model configuration -// and changes only need to be made in one place (backend MODEL_CATALOG + PLAN_LIMITS). + +// ═══════════════════════════════════════════════════════════════════════════════ +// CATEGORIES & INDUSTRIES — Expanded for all business types +// Synced with backend: app/routers/marketplace.py +// ═══════════════════════════════════════════════════════════════════════════════ export const CATEGORIES = [ - 'Customer Support', 'Sales', 'FAQ', 'E-commerce', - 'Healthcare', 'Finance', 'Education', 'HR', 'Legal', 'Other' + 'Customer Support', + 'Sales Assistant', + 'FAQ & Knowledge Base', + 'Appointment Booking', + 'Order & Delivery Tracking', + 'Product Recommendations', + 'Lead Generation', + 'Onboarding & Training', + 'Feedback & Surveys', + 'Personal Assistant', + 'Consultation', + 'Other', ] export const INDUSTRIES = [ - 'Technology', 'E-commerce', 'Healthcare', 'Finance', - 'Education', 'Legal', 'Real Estate', 'Hospitality', 'Retail', 'Other' + // Small businesses / Local services + 'Restaurant & Food', + 'Beauty & Barbershop', + 'Retail & Shopping', + 'Phone & Electronics', + 'Automotive & Repair', + 'Fitness & Wellness', + 'Cleaning & Home Services', + 'Photography & Events', + // Professional services + 'Healthcare & Medical', + 'Legal & Law', + 'Finance & Insurance', + 'Real Estate', + 'Accounting & Tax', + // Tech & Digital + 'Technology & SaaS', + 'E-commerce', + 'Agency & Marketing', + // Education & Non-profit + 'Education & Training', + 'Non-profit & NGO', + // Large scale + 'Hospitality & Hotels', + 'Travel & Tourism', + 'Manufacturing', + 'Logistics & Transport', + 'Agriculture', + 'Government', + // Personal + 'Personal Brand', + 'Freelancer & Consultant', + 'Other', ] export const LANGUAGES = [ diff --git a/src/pages/AnalyticsPage.tsx b/src/pages/AnalyticsPage.tsx new file mode 100644 index 0000000..66543e2 --- /dev/null +++ b/src/pages/AnalyticsPage.tsx @@ -0,0 +1,379 @@ +import React, { useState } from 'react' +import { useQuery } from '@tanstack/react-query' +import { useNavigate, Link } from 'react-router-dom' +import { analyticsAPI } from '@/services/api' +import { useAuthStore } from '@/store/authStore' +import { Card, Spinner, Button, Badge } from '@/components/ui' +import { formatDate } from '@/lib/utils' +import { + BarChart3, Users, MessageSquare, Star, TrendingUp, + Clock, Globe, ArrowRight, Lock, Bot, Calendar, + ArrowUp, ArrowDown, Minus, ChevronDown, ChevronUp +} from 'lucide-react' + +// ═══════════════════════════════════════════════════════════════════════════════ +// ANALYTICS PAGE — Available for Starter and Pro plans +// Shows: conversations, unique users, ratings, top queries, daily trends +// Does NOT show: LLM costs, token usage costs, API spending +// ═══════════════════════════════════════════════════════════════════════════════ + +interface DailyConversation { + date: string + count: number +} + +interface TopQuery { + query: string + count: number +} + +interface ChatbotAnalytics { + chatbot_id: string + chatbot_name: string + total_conversations: number + unique_sessions: number + total_messages: number + average_messages_per_conversation: number + average_rating: number | null + total_ratings: number + conversations_today: number + conversations_this_week: number + conversations_this_month: number + daily_conversations: DailyConversation[] + top_queries: TopQuery[] + languages_used: Record + peak_hour: number | null +} + +interface OverviewData { + total_chatbots: number + published_chatbots: number + total_conversations: number + total_messages: number + unique_sessions: number + conversations_this_month: number + average_rating: number | null + chatbots: ChatbotAnalytics[] + plan: string + conversations_limit: number + conversations_used: number +} + +// ─── Mini bar chart component ───────────────────────────────────────────────── +const MiniBarChart: React.FC<{ data: DailyConversation[] }> = ({ data }) => { + if (!data.length) { + return
No data yet
+ } + + const max = Math.max(...data.map(d => d.count), 1) + + // Fill last 30 days + const today = new Date() + const days: { date: string; count: number }[] = [] + for (let i = 29; i >= 0; i--) { + const d = new Date(today) + d.setDate(d.getDate() - i) + const dateStr = d.toISOString().slice(0, 10) + const found = data.find(x => x.date === dateStr) + days.push({ date: dateStr, count: found?.count || 0 }) + } + + return ( +
+ {days.map((d, i) => ( +
0 ? 8 : 2)}%` }} + title={`${d.date}: ${d.count} conversations`} + /> + ))} +
+ ) +} + +// ─── Stat card ──────────────────────────────────────────────────────────────── +const StatCard: React.FC<{ + label: string + value: string | number + icon: React.ReactNode + subtitle?: string + color?: string +}> = ({ label, value, icon, subtitle, color = 'primary' }) => ( + +
+ {label} +
+ {icon} +
+
+
{typeof value === 'number' ? value.toLocaleString() : value}
+ {subtitle &&

{subtitle}

} +
+) + +// ─── Usage bar ──────────────────────────────────────────────────────────────── +const UsageBar: React.FC<{ used: number; limit: number }> = ({ used, limit }) => { + const pct = limit > 0 ? Math.min((used / limit) * 100, 100) : 0 + const isHigh = pct > 80 + const isFull = pct >= 100 + + return ( +
+
+ Monthly conversations + + {used.toLocaleString()} / {limit.toLocaleString()} + +
+
+
+
+
+ ) +} + +// ─── Chatbot detail row ─────────────────────────────────────────────────────── +const ChatbotRow: React.FC<{ chatbot: ChatbotAnalytics }> = ({ chatbot }) => { + const [expanded, setExpanded] = useState(false) + + return ( + + + + {expanded && ( +
+ {/* Stats row */} +
+
+

Today

+

{chatbot.conversations_today}

+
+
+

This week

+

{chatbot.conversations_this_week}

+
+
+

This month

+

{chatbot.conversations_this_month}

+
+
+

Avg messages/convo

+

{chatbot.average_messages_per_conversation}

+
+
+ + {/* Daily chart */} +
+

Last 30 days

+ +
+ + {/* Top queries & Languages side by side */} +
+ {chatbot.top_queries.length > 0 && ( +
+

Top questions

+
    + {chatbot.top_queries.slice(0, 5).map((q, i) => ( +
  • + {i + 1}. + {q.query} + {q.count}× +
  • + ))} +
+
+ )} + + {Object.keys(chatbot.languages_used).length > 0 && ( +
+

Languages

+
+ {Object.entries(chatbot.languages_used) + .sort(([, a], [, b]) => b - a) + .slice(0, 5) + .map(([lang, count]) => ( +
+ + {lang} + {count} convos +
+ ))} +
+
+ )} +
+ + {chatbot.peak_hour !== null && ( +
+ + Peak hour: {chatbot.peak_hour}:00 - {chatbot.peak_hour + 1}:00 +
+ )} +
+ )} +
+ ) +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// MAIN ANALYTICS PAGE +// ═══════════════════════════════════════════════════════════════════════════════ + +export const AnalyticsPage: React.FC = () => { + const { user } = useAuthStore() + const navigate = useNavigate() + + const { data, isLoading, error } = useQuery({ + queryKey: ['analytics-overview'], + queryFn: analyticsAPI.overview, + staleTime: 60_000, // 1 min cache + retry: false, + }) + + // Handle plan gate (402 response) + if (error && (error as any)?.response?.status === 402) { + return ( +
+ +
+ +
+

Analytics Dashboard

+

+ Unlock analytics to see how your chatbots are performing — conversations, user engagement, top questions, and more. +

+ +

Available on Starter and Pro plans

+
+
+ ) + } + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (!data) { + return ( +
+ + +

Unable to load analytics. Please try again.

+
+
+ ) + } + + return ( +
+ {/* Header */} +
+
+

Analytics

+

+ Track how your chatbots are performing +

+
+ {data.plan} plan +
+ + {/* Usage bar */} + + + + + {/* Overview stat cards */} +
+ } + subtitle={`${data.conversations_this_month} this month`} + /> + } + subtitle="Across all chatbots" + /> + } + subtitle="Total messages exchanged" + /> + } + subtitle={data.average_rating ? 'Across rated chatbots' : 'No ratings yet'} + /> +
+ + {/* Chatbot breakdown header */} +
+

+ Your chatbots ({data.total_chatbots}) +

+

{data.published_chatbots} published

+
+ + {/* Per-chatbot expandable rows */} + {data.chatbots.length === 0 ? ( + + +

No chatbots yet. Create your first chatbot to see analytics.

+ +
+ ) : ( +
+ {data.chatbots.map(cb => ( + + ))} +
+ )} +
+ ) +} diff --git a/src/pages/PricingPage.tsx b/src/pages/PricingPage.tsx index d94a9ba..7a58839 100644 --- a/src/pages/PricingPage.tsx +++ b/src/pages/PricingPage.tsx @@ -11,7 +11,7 @@ const PLANS = [ id: 'free', name: 'Free', price: 0, - description: 'Perfect for testing and development', + description: 'Build and test chatbots, no credit card needed', icon: '🆓', color: 'gray', features: [ @@ -19,50 +19,54 @@ const PLANS = [ { text: 'Upload PDF, DOCX, CSV, XLSX', included: true }, { text: 'Unlimited preview testing', included: true }, { text: 'Shareable preview links', included: true }, + { text: '50 preview conversations/month', included: true }, + { text: '3 documents per chatbot', included: true }, + { text: 'Llama 3.3 70B model', included: true }, { text: 'Publish to marketplace', included: false }, - { text: 'Premium AI models', included: false }, - { text: 'Code export', included: false }, { text: 'Analytics dashboard', included: false }, + { text: 'Code export', included: false }, ], }, { id: 'starter', name: 'Starter', - price: 39, - description: 'For small businesses launching their first chatbot', + price: 3, + description: 'Go live with your first chatbot', icon: '🚀', color: 'blue', - badge: 'Popular', + badge: 'Most Popular', features: [ { text: 'Everything in Free', included: true }, { text: 'Publish 1 chatbot to marketplace', included: true }, - { text: 'Fireworks AI models (Llama, Mixtral)', included: true }, - { text: '5,000 conversations/month', included: true }, + { text: '500 conversations/month', included: true }, + { text: '10 documents per chatbot', included: true }, + { text: '4 Fireworks AI models (Qwen3, DeepSeek, Kimi, Llama)', included: true }, { text: 'Analytics dashboard', included: true }, { text: 'Custom branding', included: true }, { text: 'Email support', included: true }, - { text: 'Premium AI models (GPT-4, Claude)', included: false }, + { text: 'Premium models (GPT-4o, Claude, Gemini)', included: false }, { text: 'Code export', included: false }, ], }, { id: 'pro', name: 'Pro', - price: 119, - description: 'For growing businesses with multiple products', + price: 20, + description: 'For growing businesses with multiple chatbots', icon: '⚡', color: 'purple', highlighted: true, features: [ { text: 'Everything in Starter', included: true }, - { text: 'Build & publish 3 chatbots', included: true }, - { text: 'GPT-4o, Claude 3.5, Gemini 1.5', included: true }, - { text: '20,000 conversations/month', included: true }, + { text: 'Build & publish up to 5 chatbots', included: true }, + { text: '2,000 conversations/month', included: true }, + { text: '50 documents per chatbot', included: true }, + { text: 'GPT-4o, GPT-4o Mini', included: true }, + { text: 'Claude Haiku 4.5', included: true }, + { text: 'Gemini 2.5 Flash, Lite & Pro', included: true }, { text: 'Code export (FastAPI + React widget)', included: true }, { text: 'Advanced analytics', included: true }, - { text: 'Remove "Powered by" badge', included: true }, { text: 'Priority support', included: true }, - { text: 'Custom domain', included: true }, ], }, { @@ -91,11 +95,10 @@ export const PricingPage: React.FC = () => { const navigate = useNavigate() const [loading, setLoading] = useState(null) - // BUG-10 FIX: Fetch current subscription to show "Current Plan" badge const { data: subscription } = useQuery({ queryKey: ['subscription'], queryFn: billingAPI.getSubscription, - enabled: !!user, // Only fetch if logged in + enabled: !!user, }) const currentPlan = subscription?.plan || user?.plan || 'free' @@ -110,7 +113,6 @@ export const PricingPage: React.FC = () => { navigate('/dashboard') return } - // BUG-10 FIX: Don't allow re-subscribing to current plan if (planId === currentPlan) return setLoading(planId) @@ -128,7 +130,6 @@ export const PricingPage: React.FC = () => { } } - // Helper to determine CTA text const getCtaText = (planId: string): string => { if (!user) return planId === 'enterprise' ? 'Contact Sales' : 'Get Started' if (planId === currentPlan) return 'Current Plan' @@ -142,13 +143,12 @@ export const PricingPage: React.FC = () => { return (
-

Simple, transparent pricing

+

Simple, affordable pricing

- Start free and build as many chatbots as you want. Upgrade when you're ready to publish and go live. + Start free, go live for just $3/month. Built for individuals, small businesses, and enterprises alike.

- {/* R-06 FIX: Better grid breakpoints - md:grid-cols-2 instead of jumping to 4 */}
{PLANS.map((plan) => (
{ : 'border-gray-200 bg-white' }`} > - {/* BUG-10 FIX: Show "Current Plan" badge */} {isCurrentPlan(plan.id) && (
@@ -198,7 +197,8 @@ export const PricingPage: React.FC = () => { {plan.features.map((feature) => (
  • @@ -213,7 +213,6 @@ export const PricingPage: React.FC = () => {