+ {/* Sidebar */}
+
+
+ {/* Mobile Overlay */}
+ {isSidebarOpen && (
+
setIsSidebarOpen(false)}
+ />
+ )}
+
+ {/* Main Content */}
+
+
+
+
+ CustomerAgent
+
+
+
+
+
+ {unreadNotifications > 0 && (
+
+ )}
+
+
+
+
+ {/* Page Content */}
+
+
+
+
+
+
+
+ );
+};
+
+export default Layout;
diff --git a/client/src/components/PublicLayout.css b/client/src/components/PublicLayout.css
new file mode 100644
index 0000000000000000000000000000000000000000..aa37dbd782934cba37da6ea4679ad17c2c2f0609
--- /dev/null
+++ b/client/src/components/PublicLayout.css
@@ -0,0 +1,220 @@
+/* Public Layout - Navigation & Footer */
+
+/* Navigation */
+.public-nav {
+ background: white;
+ border-bottom: 1px solid #e0e0e0;
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+[data-theme="dark"] .public-nav {
+ background: #1a1a1a;
+ border-bottom-color: #404040;
+}
+
+.public-nav__container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 20px;
+ height: 70px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 40px;
+}
+
+.public-nav__logo {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ text-decoration: none;
+ font-weight: 700;
+ font-size: 1.25rem;
+ color: #1a1a1a;
+}
+
+[data-theme="dark"] .public-nav__logo {
+ color: white;
+}
+
+.public-nav__logo-icon {
+ font-size: 2rem;
+}
+
+.public-nav__links {
+ display: flex;
+ gap: 32px;
+ flex: 1;
+ justify-content: center;
+}
+
+.public-nav__link {
+ text-decoration: none;
+ color: #666;
+ font-weight: 500;
+ font-size: 1rem;
+ transition: color 0.3s ease;
+ position: relative;
+}
+
+.public-nav__link:hover {
+ color: #667eea;
+}
+
+.public-nav__link.active {
+ color: #667eea;
+}
+
+.public-nav__link.active::after {
+ content: '';
+ position: absolute;
+ bottom: -12px;
+ left: 0;
+ right: 0;
+ height: 3px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 2px 2px 0 0;
+}
+
+.public-nav__actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.btn--ghost {
+ background: transparent;
+ color: #667eea;
+ border: 1px solid #667eea;
+}
+
+.btn--ghost:hover {
+ background: rgba(102, 126, 234, 0.1);
+ transform: none;
+}
+
+/* Main Content */
+.public-main {
+ min-height: calc(100vh - 70px);
+}
+
+/* Footer */
+.public-footer {
+ background: #1a1a1a;
+ color: white;
+ padding: 60px 20px 30px;
+}
+
+.public-footer__container {
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.public-footer__grid {
+ display: grid;
+ grid-template-columns: 2fr 1fr 1fr 1fr;
+ gap: 60px;
+ margin-bottom: 48px;
+}
+
+.public-footer__brand {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.public-footer__logo {
+ font-size: 2rem;
+}
+
+.public-footer__brand-name {
+ font-size: 1.5rem;
+ font-weight: 700;
+}
+
+.public-footer__tagline {
+ color: #b3b3b3;
+ line-height: 1.6;
+}
+
+.public-footer__col h4 {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin-bottom: 16px;
+}
+
+.public-footer__col ul {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.public-footer__col a {
+ color: #b3b3b3;
+ text-decoration: none;
+ transition: color 0.3s ease;
+}
+
+.public-footer__col a:hover {
+ color: white;
+}
+
+.public-footer__bottom {
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ padding-top: 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ color: #b3b3b3;
+ font-size: 0.875rem;
+}
+
+.public-footer__links {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+}
+
+.public-footer__links a {
+ color: #b3b3b3;
+ text-decoration: none;
+}
+
+.public-footer__links a:hover {
+ color: white;
+}
+
+/* Responsive */
+@media (max-width: 992px) {
+ .public-nav__links {
+ display: none;
+ }
+
+ .public-footer__grid {
+ grid-template-columns: 1fr 1fr;
+ gap: 40px;
+ }
+}
+
+@media (max-width: 768px) {
+ .public-nav__actions .btn {
+ padding: 10px 20px;
+ font-size: 0.9rem;
+ }
+
+ .public-footer__grid {
+ grid-template-columns: 1fr;
+ }
+
+ .public-footer__bottom {
+ flex-direction: column;
+ gap: 16px;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/client/src/components/PublicLayout.jsx b/client/src/components/PublicLayout.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..bccfb458e4a6b08b453818e301d1113ae31787b1
--- /dev/null
+++ b/client/src/components/PublicLayout.jsx
@@ -0,0 +1,225 @@
+import React, { useState, useEffect } from 'react';
+import { Link, useLocation, Outlet } from 'react-router-dom';
+import { Menu, X, ChevronRight, Twitter, Linkedin, Facebook, Youtube } from 'lucide-react';
+
+const PublicLayout = ({ children }) => {
+ const location = useLocation();
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 20);
+ };
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ const isActive = (path) => location.pathname === path;
+
+ const navLinks = [
+ { path: '/', label: 'Home' },
+ { path: '/pricing', label: 'Pricing' },
+ { path: '/about', label: 'About' },
+ { path: '/contact', label: 'Contact' },
+ { path: '/blog', label: 'Blog' },
+ ];
+
+ return (
+
+ {/* Navigation */}
+
+
+ {/* Main Content */}
+
+
+
+
+ {/* Footer */}
+
+
+ );
+};
+
+export default PublicLayout;
diff --git a/client/src/components/Website/HealthcareConfigModal.jsx b/client/src/components/Website/HealthcareConfigModal.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..af2091ee298c2abb264681c4f6cf0a4c4d57b516
--- /dev/null
+++ b/client/src/components/Website/HealthcareConfigModal.jsx
@@ -0,0 +1,215 @@
+import React, { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { X, Heart, Stethoscope, Activity, Calendar, Languages, Shield, AlertCircle } from 'lucide-react';
+import api from '../../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+export default function HealthcareConfigModal({ isOpen, onClose, website, onUpdate }) {
+ const [config, setConfig] = useState({
+ healthcare_sub_industry: 'general',
+ symptom_checker_enabled: true,
+ language: 'en',
+ custom_disclaimer: '',
+ appointment_url: ''
+ });
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ if (website && website.widget_config) {
+ const currentConfig = typeof website.widget_config === 'string'
+ ? JSON.parse(website.widget_config)
+ : website.widget_config;
+
+ setConfig({
+ healthcare_sub_industry: currentConfig.healthcare_sub_industry || 'general',
+ symptom_checker_enabled: currentConfig.symptom_checker_enabled !== false,
+ language: currentConfig.language || 'en',
+ custom_disclaimer: currentConfig.custom_disclaimer || '',
+ appointment_url: currentConfig.appointment_url || ''
+ });
+ }
+ }, [website]);
+
+ const handleSave = async () => {
+ setLoading(true);
+ try {
+ // Merge into existing widget_config
+ const updatedWidgetConfig = {
+ ...(typeof website.widget_config === 'string' ? JSON.parse(website.widget_config) : website.widget_config),
+ ...config
+ };
+
+ await api.put(`/websites/${website.id}/widget-config`, updatedWidgetConfig);
+ toast.success('Healthcare settings updated!');
+ onUpdate();
+ onClose();
+ } catch (error) {
+ toast.error('Failed to update settings');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (!isOpen) return null;
+
+ const subIndustries = [
+ { id: 'general', name: 'General Practice', icon:
},
+ { id: 'dental', name: 'Dental Care', icon:
},
+ { id: 'pharmacy', name: 'Pharmacy', icon:
},
+ { id: 'mental_health', name: 'Mental Health', icon:
},
+ { id: 'pediatrics', name: 'Pediatrics', icon:
}
+ ];
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+
+
Healthcare AI Configuration
+
Tailor your medical assistant for {website?.name}
+
+
+
+
+
+ {/* Content */}
+
+ {/* Sub-industry selection */}
+
+
+
+ {subIndustries.map((si) => (
+
+ ))}
+
+
+
+
+ {/* Language selection */}
+
+
+
+
+
+ {/* Symptom Checker Toggle */}
+
+
+
+
+
+
+
+
+
+ {/* Appointment integration */}
+
+
+
+ setConfig({ ...config, appointment_url: e.target.value })}
+ placeholder="https://appointments.yourclinic.com"
+ className="w-full pl-12 pr-4 py-4 bg-secondary-50 border-2 border-secondary-100 rounded-2xl focus:border-blue-500 outline-none transition-all text-secondary-700 placeholder:text-secondary-300 shadow-inner"
+ />
+
+
+
+
+ {/* Disclaimer customization */}
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/context/AuthContext.jsx b/client/src/context/AuthContext.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b3aa5633500b5e8b5b6bddc7a42d61466efbcdd8
--- /dev/null
+++ b/client/src/context/AuthContext.jsx
@@ -0,0 +1,100 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import api from '../api/axiosConfig';
+
+const AuthContext = createContext();
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};
+
+export const AuthProvider = ({ children }) => {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ checkAuth();
+ }, []);
+
+ const checkAuth = async () => {
+ try {
+ const response = await api.get('/auth/me');
+ setUser(response.data);
+ } catch (error) {
+ console.log('Auth check failed:', error.message);
+ setUser(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const login = async (email, password) => {
+ try {
+ const response = await api.post('/auth/login', { email, password });
+ if (response.data.access_token) {
+ localStorage.setItem('access_token', response.data.access_token);
+ }
+ await checkAuth();
+ return response.data;
+ } catch (error) {
+ console.error('Login error:', error.response?.data);
+ const message = error.response?.data?.message || error.response?.data?.error || error.response?.data?.detail || error.message;
+ throw message;
+ }
+ };
+
+ const register = async (email, password) => {
+ try {
+ const response = await api.post('/auth/register', { email, password });
+ return response.data;
+ } catch (error) {
+ console.error('Registration error:', error.response?.data);
+ const message = error.response?.data?.message || error.response?.data?.error || error.response?.data?.detail || error.message;
+ throw message;
+ }
+ };
+
+ const googleLogin = async (idToken) => {
+ try {
+ const response = await api.post('/auth/google', { id_token: idToken });
+ if (response.data.access_token) {
+ localStorage.setItem('access_token', response.data.access_token);
+ }
+ await checkAuth();
+ return response.data;
+ } catch (error) {
+ console.error('Google login error:', error.response?.data);
+ const message = error.response?.data?.message || error.response?.data?.error || error.response?.data?.detail || error.message;
+ throw message;
+ }
+ };
+
+ const logout = async () => {
+ try {
+ await api.post('/auth/logout');
+ } catch (error) {
+ console.error('Logout error:', error);
+ } finally {
+ localStorage.removeItem('access_token');
+ setUser(null);
+ }
+ };
+
+ const value = {
+ user,
+ login,
+ googleLogin,
+ register,
+ logout,
+ loading
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/context/WebsiteContext.jsx b/client/src/context/WebsiteContext.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..bb90b7d2a19397d9e2fb982b4ab7db2c8dfc0bab
--- /dev/null
+++ b/client/src/context/WebsiteContext.jsx
@@ -0,0 +1,51 @@
+import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
+import api from '../api/axiosConfig';
+import { useAuth } from './AuthContext';
+
+const WebsiteContext = createContext();
+
+export const WebsiteProvider = ({ children }) => {
+ const [websites, setWebsites] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const { user } = useAuth();
+
+ const fetchWebsites = useCallback(async () => {
+ if (!user) {
+ setWebsites([]);
+ setLoading(false);
+ return;
+ }
+
+ try {
+ const response = await api.get('/websites/');
+ setWebsites(response.data);
+ } catch (error) {
+ console.error('Failed to fetch websites:', error);
+ } finally {
+ setLoading(false);
+ }
+ }, [user]);
+
+ useEffect(() => {
+ fetchWebsites();
+ }, [fetchWebsites]);
+
+ const refreshWebsites = () => {
+ setLoading(true);
+ return fetchWebsites();
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useWebsites = () => {
+ const context = useContext(WebsiteContext);
+ if (!context) {
+ throw new Error('useWebsites must be used within a WebsiteProvider');
+ }
+ return context;
+};
diff --git a/client/src/hooks/useAuth.js b/client/src/hooks/useAuth.js
new file mode 100644
index 0000000000000000000000000000000000000000..05a9dd8c3a3ad049516411923e2dfa3b25cfdfaa
--- /dev/null
+++ b/client/src/hooks/useAuth.js
@@ -0,0 +1,12 @@
+import { useContext } from 'react';
+import { AuthContext } from '../context/AuthContext';
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+
+ return context;
+};
\ No newline at end of file
diff --git a/client/src/hooks/useTheme.jsx b/client/src/hooks/useTheme.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..34543c7e8e065a5c6fb4777b80b62dd8543748cd
--- /dev/null
+++ b/client/src/hooks/useTheme.jsx
@@ -0,0 +1,64 @@
+// Dark Mode Hook - React Implementation
+import { useState, useEffect } from 'react';
+
+export const useTheme = () => {
+ const [theme, setTheme] = useState(() => {
+ // Check localStorage first
+ const saved = localStorage.getItem('theme');
+ if (saved) return saved;
+
+ // Check system preference
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ return 'dark';
+ }
+
+ return 'light';
+ });
+
+ useEffect(() => {
+ // Apply theme to document
+ document.documentElement.setAttribute('data-theme', theme);
+
+ // Save to localStorage
+ localStorage.setItem('theme', theme);
+ }, [theme]);
+
+ // Listen for system theme changes
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+
+ const handleChange = (e) => {
+ // Only auto-switch if user hasn't set preference
+ if (!localStorage.getItem('theme')) {
+ setTheme(e.matches ? 'dark' : 'light');
+ }
+ };
+
+ mediaQuery.addEventListener('change', handleChange);
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }, []);
+
+ const toggleTheme = () => {
+ setTheme(prev => prev === 'light' ? 'dark' : 'light');
+ };
+
+ return { theme, toggleTheme };
+};
+
+// Theme Toggle Component
+export const ThemeToggle = () => {
+ const { theme, toggleTheme } = useTheme();
+
+ return (
+
+ );
+};
diff --git a/client/src/index.css b/client/src/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..999f2113e64489b81d075b002409c708639be561
--- /dev/null
+++ b/client/src/index.css
@@ -0,0 +1,35 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ html {
+ scroll-behavior: smooth;
+ }
+
+ body {
+ background-color: theme('colors.secondary.50');
+ color: theme('colors.secondary.900');
+ @apply font-sans antialiased selection:bg-primary-500 selection:text-white;
+ }
+}
+
+@layer utilities {
+ .glass {
+ @apply bg-white/70 backdrop-blur-md border border-white/20 shadow-lg;
+ }
+
+ .glass-dark {
+ @apply bg-secondary-900/70 backdrop-blur-md border border-secondary-800/50 shadow-lg;
+ }
+
+ .card-hover {
+ @apply transition-all duration-300 hover:shadow-xl hover:-translate-y-1;
+ }
+
+ .text-gradient {
+ @apply bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-accent-600;
+ }
+}
\ No newline at end of file
diff --git a/client/src/main.jsx b/client/src/main.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..0e6a1197b95354dec0c44d5788f1ca11a7b62dff
--- /dev/null
+++ b/client/src/main.jsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App.jsx'
+import { GoogleOAuthProvider } from '@react-oauth/google';
+
+const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || "PASTE_YOUR_GOOGLE_CLIENT_ID_HERE";
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+
+ ,
+)
\ No newline at end of file
diff --git a/client/src/pages/About.css b/client/src/pages/About.css
new file mode 100644
index 0000000000000000000000000000000000000000..e8046e7f862db3c1456bec4d10aea0fce194c1e3
--- /dev/null
+++ b/client/src/pages/About.css
@@ -0,0 +1,177 @@
+.about-page {
+ background: white;
+}
+
+.about-hero {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 120px 20px;
+ text-align: center;
+}
+
+.about-hero h1 {
+ font-size: clamp(2.5rem, 5vw, 4rem);
+ font-weight: 800;
+ margin-bottom: 20px;
+}
+
+.about-hero p {
+ font-size: 1.25rem;
+ max-width: 600px;
+ margin: 0 auto;
+ opacity: 0.95;
+}
+
+.story-section {
+ padding: 100px 20px;
+}
+
+.story-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 60px;
+ align-items: center;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.story-content h2 {
+ font-size: 2.5rem;
+ font-weight: 700;
+ margin-bottom: 24px;
+ color: #1a1a1a;
+}
+
+.story-content p {
+ font-size: 1.125rem;
+ line-height: 1.8;
+ color: #666;
+ margin-bottom: 20px;
+}
+
+.story-image img {
+ width: 100%;
+ border-radius: 20px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
+}
+
+.values-section {
+ padding: 100px 20px;
+ background: #f9fafb;
+}
+
+.values-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 32px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.value-card {
+ background: white;
+ padding: 40px 32px;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ text-align: center;
+}
+
+.value-card__icon {
+ font-size: 3rem;
+ margin-bottom: 20px;
+}
+
+.value-card h3 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 12px;
+ color: #1a1a1a;
+}
+
+.value-card p {
+ color: #666;
+ line-height: 1.6;
+}
+
+.team-section {
+ padding: 100px 20px;
+}
+
+.team-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 32px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.team-card {
+ background: white;
+ padding: 32px;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ text-align: center;
+}
+
+.team-card img {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ margin-bottom: 20px;
+ object-fit: cover;
+}
+
+.team-card h3 {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: #1a1a1a;
+}
+
+.team-card__role {
+ color: #667eea;
+ font-weight: 500;
+ margin-bottom: 12px;
+}
+
+.team-card__bio {
+ color: #666;
+ font-size: 0.95rem;
+ line-height: 1.6;
+}
+
+.cta-section {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ padding: 100px 20px;
+ text-align: center;
+}
+
+.cta-container {
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.cta-container h2 {
+ font-size: clamp(2rem, 4vw, 3rem);
+ font-weight: 700;
+ color: white;
+ margin-bottom: 20px;
+}
+
+.cta-container p {
+ font-size: 1.25rem;
+ color: white;
+ opacity: 0.95;
+ margin-bottom: 40px;
+}
+
+@media (max-width: 768px) {
+ .story-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .values-grid,
+ .team-grid {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/client/src/pages/About.jsx b/client/src/pages/About.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..409b7a48d1b858b3715e489db5e8ef3b6208ed86
--- /dev/null
+++ b/client/src/pages/About.jsx
@@ -0,0 +1,254 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { Link } from 'react-router-dom';
+import { Target, Rocket, Lightbulb, Heart, Users, Award, Globe, ArrowRight } from 'lucide-react';
+
+const About = () => {
+ return (
+
+ {/* Hero Section */}
+
+
+
+
+
+
+
+ Revolutionizing Customer Support with AI
+
+
+ We're on a mission to make world-class customer service accessible to every business, everywhere.
+
+
+
+
+
+ {/* Story Section */}
+
+
+
+
+
+
+ Our Journey
+
+
+ From Frustration to Innovation
+
+
+
+ Founded in 2024, we saw businesses struggling with overwhelming support tickets, long response times, and the inability to scale customer service without exponentially increasing costs.
+
+
+ We realized that traditional chatbots were clunky and frustrating, while human support was unscalable. There had to be a better way.
+
+
+ We built CustomerAgent to bridge this gap—a solution that combines cutting-edge AI with human empathy. Because the best customer service isn't about replacing humans, it's about empowering them to do their best work.
+
+
+
+
+
+
+
+
+
+
+
+
+
50+
+
Countries Served
+
+
+
+ Helping businesses globally connect with their customers.
+
+
+
+
+
+
+
+ {/* Values Section */}
+
+
+
+ Our Core Values
+
+ The principles that guide everything we do
+
+
+
+
+ {values.map((value, index) => (
+
+
+
+
+ {value.title}
+
+ {value.description}
+
+
+ ))}
+
+
+
+
+ {/* Team Section */}
+
+
+
+ Meet the Team
+
+ The passionate people behind CustomerAgent
+
+
+
+
+ {team.map((member, index) => (
+
+
+

+
+
+
+
{member.name}
+
{member.role}
+
+
+ ))}
+
+
+
+
+ {/* CTA Section */}
+
+
+
+
+
+ Join Us on Our Mission
+
+ Be part of the customer support revolution and transform how you connect with your users.
+
+
+ Get Started Today
+
+
+
+
+
+
+
+ );
+};
+
+const values = [
+ {
+ icon: Target,
+ title: 'Customer First',
+ description: 'Every decision we make starts with "How does this help our customers?" We succeed only when you do.'
+ },
+ {
+ icon: Lightbulb,
+ title: 'Innovation',
+ description: 'We push boundaries and embrace new technologies to stay ahead of the curve and solve real problems.'
+ },
+ {
+ icon: Heart,
+ title: 'Transparency',
+ description: 'Open communication, honest pricing, and no hidden agendas. We build trust through clarity.'
+ },
+ {
+ icon: Users,
+ title: 'Partnership',
+ description: 'We view ourselves as an extension of your team. Your growth is our growth, and we\'re in this together.'
+ }
+];
+
+const team = [
+ {
+ name: 'Anas Raza',
+ role: 'Founder & CEO',
+ bio: 'Former tech lead with 10+ years building AI systems. Passionate about democratizing AI technology.',
+ avatar: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&w=400&q=80'
+ },
+ {
+ name: 'Sarah Chen',
+ role: 'CTO',
+ bio: 'ML expert passionate about NLP and customer experience. PhD in Computer Science from Stanford.',
+ avatar: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&w=400&q=80'
+ },
+ {
+ name: 'Michael Park',
+ role: 'Head of Product',
+ bio: 'Product designer focused on delightful user experiences. Previously at Airbnb and Stripe.',
+ avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=400&q=80'
+ },
+ {
+ name: 'Aisha Khan',
+ role: 'Head of Support',
+ bio: 'Customer success champion with deep empathy. Dedicated to ensuring every user succeeds.',
+ avatar: 'https://images.unsplash.com/photo-1580489944761-15a19d654956?auto=format&fit=crop&w=400&q=80'
+ }
+];
+
+export default About;
diff --git a/client/src/pages/AnonymousUsers.jsx b/client/src/pages/AnonymousUsers.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a84638b8d3a3b87f9cc8fc00531078a8dc44594f
--- /dev/null
+++ b/client/src/pages/AnonymousUsers.jsx
@@ -0,0 +1,356 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { motion } from 'framer-motion';
+import api from '../api/axiosConfig';
+import { Globe, ChevronDown, Send, X, User, Search, MapPin, CheckCheck, MessageCircle } from 'lucide-react';
+import toast from 'react-hot-toast';
+
+const AnonymousUsers = () => {
+ const [selectedWebsiteId, setSelectedWebsiteId] = useState(null);
+ const [showDropdown, setShowDropdown] = useState(false);
+ const [sessions, setSessions] = useState([]);
+ const [selectedSession, setSelectedSession] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [newMessage, setNewMessage] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+ const messagesEndRef = useRef(null);
+
+ const { data: websites, isLoading: websitesLoading } = useQuery({
+ queryKey: ['websites'],
+ queryFn: async () => {
+ const response = await api.get('/websites/');
+ return response.data;
+ },
+ });
+
+ const selectedWebsite = websites?.find(w => w.id === selectedWebsiteId);
+
+ useEffect(() => {
+ if (selectedWebsiteId) {
+ fetchSessions();
+ const interval = setInterval(fetchSessions, 10000);
+ return () => clearInterval(interval);
+ }
+ }, [selectedWebsiteId]);
+
+ useEffect(() => {
+ if (selectedSession) {
+ fetchMessages(selectedSession.id);
+ }
+ }, [selectedSession]);
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ const fetchSessions = async () => {
+ if (!selectedWebsiteId) return;
+ try {
+ const response = await api.get('/manual-chat/sessions');
+ const filtered = response.data.filter(s => s.website_name === selectedWebsite?.name);
+ const sessionsWithGeo = filtered.map(session => ({
+ ...session,
+ location_display: session.ip_address ? `${session.ip_address} (${session.city || 'Unknown'}, ${session.country || 'Unknown'})` : null
+ }));
+ setSessions(sessionsWithGeo);
+ } catch (error) {
+ console.error('Failed to fetch sessions:', error);
+ toast.error('Failed to load chats');
+ }
+ };
+
+ const fetchMessages = async (sessionId) => {
+ try {
+ const response = await api.get(`/manual-chat/sessions/${sessionId}/messages`);
+ setMessages(response.data);
+ } catch (error) {
+ console.error('Failed to fetch messages:', error);
+ toast.error('Failed to load messages');
+ }
+ };
+
+ const sendMessage = async () => {
+ if (!newMessage.trim() || !selectedSession) return;
+ setLoading(true);
+ try {
+ await api.post(`/manual-chat/sessions/${selectedSession.id}/send`, {
+ message: newMessage
+ });
+ setNewMessage('');
+ fetchMessages(selectedSession.id);
+ } catch (error) {
+ toast.error('Failed to send message');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const closeSession = async (sessionId) => {
+ try {
+ await api.post(`/manual-chat/sessions/${sessionId}/close`);
+ fetchSessions();
+ if (selectedSession?.id === sessionId) {
+ setSelectedSession(null);
+ setMessages([]);
+ }
+ toast.success('Session closed');
+ } catch (error) {
+ toast.error('Failed to close session');
+ }
+ };
+
+ const filteredSessions = sessions.filter(session => {
+ const query = searchQuery.toLowerCase();
+ const name = session.visitor_name?.toLowerCase() || '';
+ const email = session.visitor_email?.toLowerCase() || '';
+ const ip = session.ip_address?.toLowerCase() || '';
+ return name.includes(query) || email.includes(query) || ip.includes(query);
+ });
+
+ if (websitesLoading) {
+ return (
+
+
+
Anonymous Users
+
Loading...
+
+
+ );
+ }
+
+ return (
+
+ {/* Header with Website Selector */}
+
+
+
+
+ {/* Mobile Dropdown */}
+
+
+ {showDropdown && (
+
+ {websites?.map((website) => (
+
+ ))}
+
+ )}
+
+
+ {/* Desktop Select */}
+
+
+
+ {filteredSessions.length} chat{filteredSessions.length !== 1 ? 's' : ''}
+
+
+
+
+ {/* Main Content */}
+
+ {/* Sessions List */}
+
+
+
Anonymous Users
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full pl-9 pr-4 py-2 bg-white border border-secondary-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+ />
+
+
+
+
+ {!selectedWebsiteId ? (
+
+
Select a website to view chats
+
+ ) : filteredSessions.length === 0 ? (
+
+
No conversations found
+
+ ) : (
+
+ {filteredSessions.map((session) => (
+
+ ))}
+
+ )}
+
+
+
+ {/* Chat Area */}
+
+ {selectedSession ? (
+ <>
+
+
+
+
+
+
+
{selectedSession.ip_address || 'Anonymous'}
+
+
+ {selectedSession.is_active ? 'Online' : 'Offline'}
+
+ {selectedSession.location_display && (
+
+
+ {selectedSession.location_display}
+
+ )}
+
+
+
+
+
+
+
+
+ {messages.map((message) => {
+ const isMe = !message.is_from_visitor;
+ return (
+
+
+
+
+
+
+
+
+ {new Date(message.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+ {isMe && }
+
+
+
+
+ );
+ })}
+
+
+
+
+
+ setNewMessage(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
+ placeholder="Type your message..."
+ className="flex-1 px-4 py-3 bg-secondary-50 border border-secondary-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
+ />
+
+
+
+ >
+ ) : (
+
+
+
+
+
No Chat Selected
+
+ Select a conversation from the sidebar to start chatting with your visitors.
+
+
+ )}
+
+
+
+ );
+};
+
+export default AnonymousUsers;
diff --git a/client/src/pages/Blog.jsx b/client/src/pages/Blog.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..18c9ab97ed5a54203075b9514093d4859c2bc26b
--- /dev/null
+++ b/client/src/pages/Blog.jsx
@@ -0,0 +1,167 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { Calendar, User, ArrowRight, Tag } from 'lucide-react';
+import { Link } from 'react-router-dom';
+
+const Blog = () => {
+ return (
+
+ {/* Hero Section */}
+
+
+
+
+ Latest Updates & Insights
+
+
+ News, tips, and best practices for customer support and AI.
+
+
+
+
+
+ {/* Featured Post */}
+
+
+
+
+
+
+
+ Product Update
+ Dec 7, 2024
+
+
+ Introducing CustomerAgent 2.0: The Future of AI Support
+
+
+ We've completely rebuilt our core engine to deliver faster, more accurate, and more human-like responses. Here's what's new.
+
+
+ Read Article
+
+
+
+
+
+
+ {/* Blog Grid */}
+
+
+
+ {blogPosts.map((post, index) => (
+
+
+

+
+
+ {post.category}
+
+
+
+
+
+ {post.date}
+ {post.author}
+
+
+ {post.title}
+
+
+ {post.excerpt}
+
+
+ Read More
+
+
+
+ ))}
+
+
+
+
+ );
+};
+
+const blogPosts = [
+ {
+ title: '5 Ways AI is Revolutionizing Customer Service',
+ slug: '5-ways-ai-revolutionizing-customer-service',
+ excerpt: 'Artificial Intelligence is not just a buzzword anymore. It is transforming how businesses interact with their customers, providing faster and more personalized support.',
+ date: 'Dec 5, 2024',
+ author: 'Sarah Johnson',
+ category: 'Industry Trends',
+ image: 'https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-4.0.3&auto=format&fit=crop&w=1770&q=80'
+ },
+ {
+ title: 'Best Practices for Chatbot Implementation',
+ slug: 'best-practices-chatbot-implementation',
+ excerpt: 'Implementing a chatbot can be tricky. Learn the do\'s and don\'ts to ensure a smooth rollout and maximum user adoption.',
+ date: 'Dec 3, 2024',
+ author: 'Mike Chen',
+ category: 'Guides',
+ image: 'https://images.unsplash.com/photo-1535378437327-b7149236addf?ixlib=rb-4.0.3&auto=format&fit=crop&w=1770&q=80'
+ },
+ {
+ title: 'Understanding Customer Sentiment Analysis',
+ slug: 'understanding-customer-sentiment-analysis',
+ excerpt: 'Sentiment analysis allows you to gauge how your customers are feeling. Discover how to leverage this powerful tool to improve satisfaction.',
+ date: 'Nov 28, 2024',
+ author: 'Emily Davis',
+ category: 'Analytics',
+ image: 'https://images.unsplash.com/photo-1551836022-d5d88e9218df?ixlib=rb-4.0.3&auto=format&fit=crop&w=1770&q=80'
+ },
+ {
+ title: 'The Importance of Omnichannel Support',
+ slug: 'importance-omnichannel-support',
+ excerpt: 'Customers expect to reach you on their preferred channels. Learn why an omnichannel strategy is crucial for modern businesses.',
+ date: 'Nov 25, 2024',
+ author: 'David Wilson',
+ category: 'Strategy',
+ image: 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?ixlib=rb-4.0.3&auto=format&fit=crop&w=1932&q=80'
+ },
+ {
+ title: 'How to Reduce Customer Churn with AI',
+ slug: 'reduce-customer-churn-with-ai',
+ excerpt: 'Predicting and preventing churn is easier with AI. Explore strategies to retain more customers and boost lifetime value.',
+ date: 'Nov 20, 2024',
+ author: 'Sarah Johnson',
+ category: 'Growth',
+ image: 'https://images.unsplash.com/photo-1552664730-d307ca884978?ixlib=rb-4.0.3&auto=format&fit=crop&w=1770&q=80'
+ },
+ {
+ title: 'Top Customer Support Metrics to Track',
+ slug: 'top-customer-support-metrics',
+ excerpt: 'Are you measuring what matters? We break down the key performance indicators (KPIs) every support team should be monitoring.',
+ date: 'Nov 15, 2024',
+ author: 'Mike Chen',
+ category: 'Metrics',
+ image: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&auto=format&fit=crop&w=2015&q=80'
+ }
+];
+
+export default Blog;
diff --git a/client/src/pages/BlogDetail.jsx b/client/src/pages/BlogDetail.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..213a0fa45a902daecaa8a169abc93e128981021c
--- /dev/null
+++ b/client/src/pages/BlogDetail.jsx
@@ -0,0 +1,117 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { Calendar, User, ArrowLeft, Share2, Twitter, Linkedin, Facebook } from 'lucide-react';
+import { Link, useParams } from 'react-router-dom';
+
+const BlogDetail = () => {
+ const { slug } = useParams();
+
+ // In a real app, fetch post data based on slug
+ const post = {
+ title: '5 Ways AI is Revolutionizing Customer Service',
+ date: 'Dec 5, 2024',
+ author: 'Sarah Johnson',
+ category: 'Industry Trends',
+ image: 'https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-4.0.3&auto=format&fit=crop&w=1770&q=80',
+ content: `
+
Artificial Intelligence is rapidly transforming the customer service landscape. From chatbots to predictive analytics, AI technologies are enabling businesses to provide faster, more personalized, and more efficient support.
+
+
1. 24/7 Availability
+
One of the most significant benefits of AI in customer service is the ability to provide round-the-clock support. AI-powered chatbots and virtual assistants can handle customer inquiries at any time of day, ensuring that help is always available when needed.
+
+
2. Personalized Interactions
+
AI can analyze vast amounts of customer data to deliver highly personalized experiences. By understanding customer preferences, purchase history, and past interactions, AI systems can tailor recommendations and solutions to each individual.
+
+
3. Faster Response Times
+
AI can instantly process and respond to common queries, significantly reducing wait times. For more complex issues, AI can route the inquiry to the most appropriate human agent, ensuring a quicker resolution.
+
+
4. Predictive Support
+
AI can predict potential issues before they arise. By monitoring user behavior and system performance, AI can identify patterns that indicate a problem and proactively offer solutions or alert support teams.
+
+
5. Cost Efficiency
+
Automating routine tasks with AI reduces the workload on human agents, allowing them to focus on high-value interactions. This not only improves efficiency but also reduces operational costs associated with customer support.
+
+
In conclusion, AI is not replacing human agents but rather augmenting their capabilities. By handling repetitive tasks and providing valuable insights, AI empowers support teams to deliver exceptional service at scale.
+ `
+ };
+
+ return (
+
+
+ {/* Header */}
+
+

+
+
+
+
+
+ {post.category}
+ {post.date}
+
+
+ {post.title}
+
+
+
+
+
+
By {post.author}
+
+
+
+
+
+
+ {/* Content */}
+
+
+
+ Back to Blog
+
+
+
+
+
+
+ Share this article
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default BlogDetail;
diff --git a/client/src/pages/ChatManagement.jsx b/client/src/pages/ChatManagement.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..93d30e07e5cbc17feb0c9d2acab694caf244e9e6
--- /dev/null
+++ b/client/src/pages/ChatManagement.jsx
@@ -0,0 +1,393 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { MessageCircle, Send, X, Clock, User, Search, MoreVertical, CheckCheck, MapPin, Globe, Filter, ChevronLeft, ChevronRight } from 'lucide-react';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+const ChatManagement = () => {
+ const [websites, setWebsites] = useState([]);
+ const [selectedWebsite, setSelectedWebsite] = useState(null);
+ const [sessions, setSessions] = useState([]);
+ const [selectedSession, setSelectedSession] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [newMessage, setNewMessage] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [filterAnonymous, setFilterAnonymous] = useState(false);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalSessions, setTotalSessions] = useState(0);
+ const itemsPerPage = 10;
+ const messagesEndRef = useRef(null);
+
+ useEffect(() => {
+ fetchWebsites();
+ }, []);
+
+ useEffect(() => {
+ if (selectedWebsite) {
+ setCurrentPage(1);
+ fetchSessions();
+ const interval = setInterval(fetchSessions, 10000);
+ return () => clearInterval(interval);
+ }
+ }, [selectedWebsite]);
+
+ useEffect(() => {
+ if (selectedWebsite) {
+ fetchSessions();
+ }
+ }, [currentPage]);
+
+ useEffect(() => {
+ if (selectedSession) {
+ fetchMessages(selectedSession.id);
+ }
+ }, [selectedSession]);
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ const fetchWebsites = async () => {
+ try {
+ const response = await api.get('/websites/');
+ setWebsites(response.data);
+ if (response.data.length > 0 && !selectedWebsite) {
+ setSelectedWebsite(response.data[0]);
+ }
+ } catch (error) {
+ console.error('Failed to fetch websites:', error);
+ toast.error('Failed to load websites');
+ }
+ };
+
+ const fetchSessions = async () => {
+ if (!selectedWebsite) return;
+ try {
+ const response = await api.get('/manual-chat/sessions', {
+ params: { page: currentPage, limit: itemsPerPage }
+ });
+ const filtered = response.data.sessions.filter(s => s.website_name === selectedWebsite.name);
+ const sessionsWithGeo = filtered.map(session => ({
+ ...session,
+ display_name: (session.visitor_name && session.visitor_name !== 'Anonymous')
+ ? session.visitor_name
+ : (session.visitor_email || session.ip_address || 'Anonymous'),
+ location_display: session.ip_address ? `${session.ip_address} (${session.city || 'Unknown'}, ${session.country || 'Unknown'})` : null
+ }));
+ setSessions(sessionsWithGeo);
+ setTotalSessions(response.data.total);
+ } catch (error) {
+ console.error('Failed to fetch sessions:', error);
+ toast.error('Failed to load chats');
+ }
+ };
+
+ const fetchMessages = async (sessionId) => {
+ try {
+ const response = await api.get(`/manual-chat/sessions/${sessionId}/messages`);
+ setMessages(response.data);
+ } catch (error) {
+ console.error('Failed to fetch messages:', error);
+ toast.error('Failed to load messages');
+ }
+ };
+
+ const sendMessage = async () => {
+ if (!newMessage.trim() || !selectedSession) return;
+ setLoading(true);
+ try {
+ await api.post(`/manual-chat/sessions/${selectedSession.id}/send`, {
+ message: newMessage
+ });
+ setNewMessage('');
+ fetchMessages(selectedSession.id);
+ } catch (error) {
+ toast.error('Failed to send message');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const closeSession = async (sessionId) => {
+ try {
+ await api.post(`/manual-chat/sessions/${sessionId}/close`);
+ fetchSessions();
+ if (selectedSession?.id === sessionId) {
+ setSelectedSession(null);
+ setMessages([]);
+ }
+ toast.success('Session closed');
+ } catch (error) {
+ toast.error('Failed to close session');
+ }
+ };
+
+ const filteredSessions = sessions.filter(session => {
+ const query = searchQuery.toLowerCase();
+ const name = session.visitor_name?.toLowerCase() || '';
+ const email = session.visitor_email?.toLowerCase() || '';
+ const ip = session.ip_address?.toLowerCase() || '';
+ const matchesSearch = name.includes(query) || email.includes(query) || ip.includes(query);
+
+ if (filterAnonymous) {
+ const isAnonymous = !session.visitor_name || session.visitor_name === 'Anonymous';
+ return matchesSearch && isAnonymous;
+ }
+ return matchesSearch;
+ });
+
+ const totalPages = Math.ceil(totalSessions / itemsPerPage);
+
+ const handlePageChange = (page) => {
+ setCurrentPage(Math.max(1, Math.min(page, totalPages)));
+ };
+
+ return (
+
+ {/* Website Selector */}
+
+
+
+
+
+ {totalSessions} chat{totalSessions !== 1 ? 's' : ''}
+
+
+
+
+ {/* Main Content */}
+
+ {/* Sessions List */}
+
+
+
+
Chats
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full pl-9 pr-4 py-2 bg-white border border-secondary-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+ />
+
+ {filterAnonymous && (
+
+ Showing anonymous users only
+
+ )}
+
+
+
+ {!selectedWebsite ? (
+
+
Select a website to view chats
+
+ ) : filteredSessions.length === 0 ? (
+
+
No conversations found
+
+ ) : (
+
+ {filteredSessions.map((session) => (
+
+ ))}
+
+ )}
+
+
+ {totalSessions > 0 && (
+
+
+ {totalSessions > 0 ? `${(currentPage - 1) * itemsPerPage + 1}-${Math.min(currentPage * itemsPerPage, totalSessions)} of ${totalSessions}` : '0'}
+
+
+
+
+ {currentPage} / {totalPages}
+
+
+
+
+ )}
+
+
+ {/* Chat Area */}
+
+ {selectedSession ? (
+ <>
+
+
+
+
+
+
+
{selectedSession.display_name}
+ {selectedSession.visitor_email && selectedSession.display_name !== selectedSession.visitor_email && (
+
{selectedSession.visitor_email}
+ )}
+
+
+ {selectedSession.is_active ? 'Online' : 'Offline'}
+
+ {selectedSession.location_display && (
+
+
+ {selectedSession.location_display}
+
+ )}
+
+
+
+
+
+
+
+
+ {messages.map((message) => {
+ const isMe = !message.is_from_visitor;
+ return (
+
+
+
+
+
+
+
+
+ {new Date(message.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+ {isMe && }
+
+
+
+
+ );
+ })}
+
+
+
+
+
+ setNewMessage(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
+ placeholder="Type your message..."
+ className="flex-1 px-4 py-3 bg-secondary-50 border border-secondary-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
+ />
+
+
+
+ >
+ ) : (
+
+
+
+
+
No Chat Selected
+
+ Select a conversation from the sidebar to start chatting with your visitors.
+
+
+ )}
+
+
+
+ );
+};
+
+export default ChatManagement;
diff --git a/client/src/pages/Contact.css b/client/src/pages/Contact.css
new file mode 100644
index 0000000000000000000000000000000000000000..fe310caa8584b3400a233a055cd8f9b86fe800ab
--- /dev/null
+++ b/client/src/pages/Contact.css
@@ -0,0 +1,165 @@
+/* Contact Page */
+.contact-page {
+ min-height: 100vh;
+}
+
+.contact-hero {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 100px 20px 60px;
+ text-align: center;
+}
+
+.contact-hero h1 {
+ font-size: clamp(2.5rem, 5vw, 4rem);
+ font-weight: 800;
+ margin-bottom: 16px;
+}
+
+.contact-hero p {
+ font-size: 1.25rem;
+ opacity: 0.95;
+}
+
+.contact-section {
+ padding: 80px 20px;
+ background: #f9fafb;
+ margin-top: -40px;
+}
+
+.contact-grid {
+ display: grid;
+ grid-template-columns: 1fr 1.2fr;
+ gap: 60px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.contact-info h2 {
+ font-size: 2.5rem;
+ font-weight: 700;
+ margin-bottom: 20px;
+ color: #1a1a1a;
+}
+
+.contact-info > p {
+ font-size: 1.125rem;
+ line-height: 1.6;
+ color: #666;
+ margin-bottom: 40px;
+}
+
+.contact-methods {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ margin-bottom: 48px;
+}
+
+.contact-method {
+ display: flex;
+ align-items: start;
+ gap: 16px;
+ padding: 20px;
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.contact-method__icon {
+ font-size: 2rem;
+ min-width: 50px;
+}
+
+.contact-method h4 {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin-bottom: 4px;
+ color: #1a1a1a;
+}
+
+.contact-method p {
+ color: #666;
+ font-size: 1rem;
+}
+
+.social-links h4 {
+ font-size: 1.125rem;
+ font-weight: 600;
+ margin-bottom: 16px;
+ color: #1a1a1a;
+}
+
+.social-icons {
+ display: flex;
+ gap: 12px;
+}
+
+.social-icon {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.5rem;
+ text-decoration: none;
+ transition: transform 0.3s ease;
+}
+
+.social-icon:hover {
+ transform: translateY(-4px);
+}
+
+.contact-form-container {
+ background: white;
+ padding: 40px;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+}
+
+.contact-form {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.form-group label {
+ font-weight: 600;
+ color: #1a1a1a;
+ font-size: 0.95rem;
+}
+
+.form-group input,
+.form-group textarea {
+ padding: 12px 16px;
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ font-size: 1rem;
+ transition: border-color 0.3s ease;
+}
+
+.form-group input:focus,
+.form-group textarea:focus {
+ outline: none;
+ border-color: #667eea;
+}
+
+.form-group textarea {
+ resize: vertical;
+ min-height: 120px;
+}
+
+@media (max-width: 768px) {
+ .contact-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/client/src/pages/Contact.jsx b/client/src/pages/Contact.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d7929feba3690397022bc1a47fd78be8043c27a3
--- /dev/null
+++ b/client/src/pages/Contact.jsx
@@ -0,0 +1,246 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import { Mail, Phone, MessageSquare, MapPin, Send, CheckCircle, Twitter, Linkedin, Facebook, Youtube, User, Building } from 'lucide-react';
+
+const Contact = () => {
+ const [formData, setFormData] = useState({
+ name: '',
+ email: '',
+ company: '',
+ message: ''
+ });
+ const [submitted, setSubmitted] = useState(false);
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ // Handle form submission
+ console.log('Form submitted:', formData);
+ setSubmitted(true);
+ setTimeout(() => setSubmitted(false), 3000);
+ };
+
+ const handleChange = (e) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value
+ });
+ };
+
+ return (
+
+ {/* Hero Section */}
+
+
+
+
+ Get in Touch
+
+
+ Have questions? We'd love to hear from you.
+
+
+
+
+
+
+
+
+ {/* Contact Info */}
+
+
+
Let's Talk
+
+ Whether you're curious about features, pricing, or just want to say hi - our team is ready to answer all your questions.
+
+
+
+
+ {contactMethods.map((method, index) => (
+
+
+
+
+
+
{method.title}
+
{method.value}
+
+
+ ))}
+
+
+
+
Follow Us
+
+ {[Twitter, Linkedin, Facebook, Youtube].map((Icon, i) => (
+
+
+
+ ))}
+
+
+
+
+ {/* Contact Form */}
+
+
+
+
+
+
+
+ );
+};
+
+const contactMethods = [
+ {
+ icon: Mail,
+ title: 'Email',
+ value: 'support@customeragent.com'
+ },
+ {
+ icon: Phone,
+ title: 'Phone',
+ value: '+1 (555) 123-4567'
+ },
+ {
+ icon: MessageSquare,
+ title: 'Live Chat',
+ value: 'Available 24/7'
+ },
+ {
+ icon: MapPin,
+ title: 'Office',
+ value: 'San Francisco, CA'
+ }
+];
+
+export default Contact;
diff --git a/client/src/pages/ContentManager.jsx b/client/src/pages/ContentManager.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cbee09d9a5c760f9e165cb1023ead7fa14140782
--- /dev/null
+++ b/client/src/pages/ContentManager.jsx
@@ -0,0 +1,474 @@
+import React, { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Search, Plus, Download, Filter, ExternalLink, Trash2, Globe, FileText, RefreshCw, Check, AlertCircle, Clock } from 'lucide-react';
+import { useSearchParams } from 'react-router-dom';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+const ContentManager = () => {
+ const [websites, setWebsites] = useState([]);
+ const [selectedWebsite, setSelectedWebsite] = useState(null);
+ const [searchParams] = useSearchParams();
+ const websiteIdFromUrl = searchParams.get('websiteId');
+
+ const [discoveredUrls, setDiscoveredUrls] = useState([]);
+ const [indexedUrls, setIndexedUrls] = useState([]);
+ const [contentCount, setContentCount] = useState(0);
+ const [selectedUrls, setSelectedUrls] = useState(new Set());
+ const [manualUrl, setManualUrl] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [filter, setFilter] = useState('');
+ const [activeTab, setActiveTab] = useState('discover');
+ const [scrapingState, setScrapingState] = useState({
+ isScraping: false,
+ progress: 0,
+ total: 0,
+ message: ''
+ });
+
+ useEffect(() => {
+ fetchWebsites();
+ }, []);
+
+ useEffect(() => {
+ if (websites.length > 0 && websiteIdFromUrl) {
+ const website = websites.find(w => w.id === parseInt(websiteIdFromUrl));
+ if (website) {
+ setSelectedWebsite(website);
+ }
+ }
+ }, [websites, websiteIdFromUrl]);
+
+ useEffect(() => {
+ if (selectedWebsite) {
+ fetchContentStatus(selectedWebsite.id);
+ fetchIndexedUrls(selectedWebsite.id);
+
+ // WebSocket connection for real-time scraping updates
+ const wsUrl = `ws://${window.location.hostname}:8000/ws/admin/${selectedWebsite.id}`;
+ const ws = new WebSocket(wsUrl);
+
+ ws.onmessage = (event) => {
+ try {
+ const data = JSON.parse(event.data);
+ if (data.type === 'scraping_progress') {
+ setScrapingState({
+ isScraping: data.status === 'scraping',
+ progress: data.current || 0,
+ total: data.total || 0,
+ message: data.message || ''
+ });
+
+ if (data.status === 'completed') {
+ toast.success('Scraping completed successfully');
+ setScrapingState(prev => ({ ...prev, isScraping: false }));
+ fetchContentStatus(selectedWebsite.id);
+ fetchIndexedUrls(selectedWebsite.id);
+ setDiscoveredUrls([]); // clear discovered as they are now indexed/processed
+ } else if (data.status === 'failed') {
+ toast.error(`Scraping failed: ${data.message}`);
+ setScrapingState(prev => ({ ...prev, isScraping: false }));
+ }
+ }
+ } catch (err) {
+ console.error('WS Error:', err);
+ }
+ };
+
+ return () => {
+ if (ws.readyState === WebSocket.OPEN) {
+ ws.close();
+ }
+ };
+ }
+ }, [selectedWebsite]);
+
+ const fetchWebsites = async () => {
+ try {
+ const response = await api.get('/websites/');
+ setWebsites(response.data);
+ if (response.data.length > 0) {
+ setSelectedWebsite(response.data[0]);
+ }
+ } catch (error) {
+ toast.error('Failed to fetch websites');
+ }
+ };
+
+ const fetchContentStatus = async (websiteId) => {
+ try {
+ const response = await api.get(`/ingest/${websiteId}/status`);
+ setContentCount(response.data.content_count || 0);
+ } catch (error) {
+ console.error('Failed to fetch content status');
+ setContentCount(0);
+ }
+ };
+
+ const fetchIndexedUrls = async (websiteId) => {
+ try {
+ const response = await api.get(`/websites/${websiteId}/indexed-urls`);
+ setIndexedUrls(response.data.urls || []);
+ } catch (error) {
+ console.error('Failed to fetch indexed URLs');
+ setIndexedUrls([]);
+ }
+ };
+
+ const discoverUrls = async () => {
+ if (!selectedWebsite) return;
+ setLoading(true);
+ try {
+ const response = await api.get(`/ingest/${selectedWebsite.id}/discover`);
+ setDiscoveredUrls(response.data.urls || []);
+ toast.success(`Found ${response.data.total_found} URLs`);
+ } catch (error) {
+ toast.error('Failed to discover URLs');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleUrlSelect = (url) => {
+ const newSelected = new Set(selectedUrls);
+ if (newSelected.has(url)) {
+ newSelected.delete(url);
+ } else {
+ newSelected.add(url);
+ }
+ setSelectedUrls(newSelected);
+ };
+
+ const selectAll = () => {
+ const filtered = getFilteredUrls();
+ setSelectedUrls(new Set(filtered));
+ };
+
+ const selectNone = () => {
+ setSelectedUrls(new Set());
+ };
+
+ const fetchSelectedContent = async () => {
+ if (selectedUrls.size === 0) {
+ toast.error('Please select URLs to fetch');
+ return;
+ }
+ setLoading(true);
+ try {
+ // This endpoint triggers background job which sends WS updates
+ await api.post(`/ingest/${selectedWebsite.id}/fetch-selected`, {
+ urls: Array.from(selectedUrls)
+ });
+ toast.success(`Started fetching ${selectedUrls.size} URLs`);
+ setSelectedUrls(new Set());
+ setDiscoveredUrls([]);
+ // No need to timeout reload, WS will handle it
+ } catch (error) {
+ toast.error('Failed to start content fetch');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const addManualUrl = async () => {
+ if (!manualUrl.trim()) {
+ toast.error('Please enter a URL');
+ return;
+ }
+ setLoading(true);
+ try {
+ await api.post(`/ingest/${selectedWebsite.id}/crawl`);
+ toast.success('Content fetch started');
+ setManualUrl('');
+ setDiscoveredUrls([]);
+ // WS will handle updates
+ } catch (error) {
+ toast.error('Failed to add URL');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getFilteredUrls = () => {
+ return discoveredUrls.filter(url =>
+ url.toLowerCase().includes(filter.toLowerCase())
+ );
+ };
+
+ const getFilteredIndexedUrls = () => {
+ return indexedUrls.filter(url =>
+ url.toLowerCase().includes(filter.toLowerCase())
+ );
+ };
+
+ return (
+
+
+
+
Content Manager
+
Manage and scrape content from your websites
+
+
+
+
+
+
+
+
+
+
+ {/* Progress Bar for Scraping */}
+
+ {scrapingState.isScraping && (
+
+
+
+
+ {scrapingState.message}
+
+
+ {scrapingState.progress} / {scrapingState.total}
+
+
+
+
+ )}
+
+
+ {selectedWebsite ? (
+
+
+
+
+
+
+
+
+ {activeTab === 'discover' ? (
+
+
+
+
+
+
setManualUrl(e.target.value)}
+ placeholder="Or enter a specific URL manually..."
+ className="flex-1 px-4 py-3 bg-secondary-50 border border-secondary-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all"
+ />
+
+
+
+
+ {discoveredUrls.length > 0 && (
+
+
+
+
+ setFilter(e.target.value)}
+ placeholder="Filter discovered URLs..."
+ className="w-full pl-9 pr-4 py-2 bg-white border border-secondary-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+ />
+
+
+
+
+
+
+
+
+ {getFilteredUrls().map((url) => (
+
+ ))}
+
+
+ {selectedUrls.size > 0 && (
+
+ {selectedUrls.size} URLs selected
+
+
+ )}
+
+ )}
+
+ ) : (
+
+ {contentCount === 0 ? (
+
+
+
+
+
No content indexed yet
+
Switch to the Discover tab to scan your website and add content.
+
+
+ ) : (
+
+
+
{contentCount} Pages Indexed
+
+
+ setFilter(e.target.value)}
+ placeholder="Filter indexed URLs..."
+ className="w-full pl-9 pr-4 py-2 bg-white border border-secondary-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
+ />
+
+
+
+ {getFilteredIndexedUrls().map((url) => (
+
+ ))}
+
+
+ )}
+
+ )}
+
+
+
+ ) : (
+
+
+
No Website Selected
+
Please select a website from the dropdown above to manage its content.
+
+ )}
+
+ );
+};
+
+export default ContentManager;
diff --git a/client/src/pages/CookiePolicy.jsx b/client/src/pages/CookiePolicy.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8a4e8ee43f8bd9ab9be436cdea357b4a0c1f0f2e
--- /dev/null
+++ b/client/src/pages/CookiePolicy.jsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+
+const CookiePolicy = () => {
+ return (
+
+
+
+
+
+ Cookie Policy
+
+
+ Last updated: December 7, 2024
+
+
+
+
+
+
+
+
+
+
1. What Are Cookies
+
+ Cookies are small text files that are placed on your computer or mobile device when you visit a website. They are widely used to make websites work more efficiently and provide information to the owners of the site.
+
+
+
+
+
2. How We Use Cookies
+
+ We use cookies for the following purposes:
+
+
+ - Essential Cookies: Necessary for the website to function properly.
+ - Performance Cookies: To analyze how visitors use our website and monitor performance.
+ - Functional Cookies: To remember your preferences and settings.
+ - Marketing Cookies: To deliver relevant advertisements and track campaign performance.
+
+
+
+
+
3. Managing Cookies
+
+ Most web browsers allow you to control cookies through their settings preferences. However, if you limit the ability of websites to set cookies, you may worsen your overall user experience.
+
+
+
+
+
4. Third-Party Cookies
+
+ In addition to our own cookies, we may also use various third-parties cookies to report usage statistics of the Service, deliver advertisements on and through the Service, and so on.
+
+
+
+
+
Contact Us
+
+ If you have questions about our Cookie Policy, please contact us at:
+
+
+
Email: privacy@customeragent.com
+
Address: 123 Tech Street, San Francisco, CA 94105
+
+
+
+
+
+
+ );
+};
+
+export default CookiePolicy;
diff --git a/client/src/pages/CreateFirstWebsite.jsx b/client/src/pages/CreateFirstWebsite.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..320caa4c27e034a5f3b845fc814ee0ccceb0a555
--- /dev/null
+++ b/client/src/pages/CreateFirstWebsite.jsx
@@ -0,0 +1,187 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import { Globe, ArrowRight, Loader2, Sparkles, Layout, MessageSquare, ShieldCheck } from 'lucide-react';
+import { useNavigate } from 'react-router-dom';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+const CreateFirstWebsite = () => {
+ const [websiteName, setWebsiteName] = useState('');
+ const [websiteUrl, setWebsiteUrl] = useState('');
+ const [loading, setLoading] = useState(false);
+ const [fetchingMetadata, setFetchingMetadata] = useState(false);
+ const navigate = useNavigate();
+
+ const getNameFromUrl = (url) => {
+ try {
+ let domain = url.replace(/^(https?:\/\/)?(www\.)?/, '');
+ domain = domain.split('.')[0];
+ return domain.charAt(0).toUpperCase() + domain.slice(1).replace(/[-_]/g, ' ');
+ } catch (e) {
+ return '';
+ }
+ };
+
+ const fetchMetadata = async (url) => {
+ if (!url || !url.includes('.')) return;
+ setFetchingMetadata(true);
+ try {
+ const response = await api.get(`/metadata/fetch?url=${encodeURIComponent(url)}`);
+ if (response.data.title) {
+ setWebsiteName(prev => {
+ const generatedName = getNameFromUrl(url);
+ return prev && prev !== generatedName ? prev : response.data.title;
+ });
+ }
+ } catch (error) {
+ console.error('Failed to fetch metadata', error);
+ } finally {
+ setFetchingMetadata(false);
+ }
+ };
+
+ const handleUrlChange = (e) => {
+ const url = e.target.value;
+ setWebsiteUrl(url);
+
+ const generatedName = getNameFromUrl(url);
+ setWebsiteName(prev => {
+ const prevUrl = websiteUrl;
+ const prevGeneratedName = getNameFromUrl(prevUrl);
+ return !prev || prev === prevGeneratedName ? generatedName : prev;
+ });
+
+ // Simple debounce
+ const timer = setTimeout(() => fetchMetadata(url), 1000);
+ return () => clearTimeout(timer);
+ };
+
+ const handleCreate = async (e) => {
+ e.preventDefault();
+ if (!websiteName || !websiteUrl) {
+ toast.error('Please fill in both fields');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ await api.post('/websites/', {
+ name: websiteName,
+ url: websiteUrl,
+ industry: 'other',
+ tone: 'friendly'
+ });
+ toast.success('Awesome! Your agent is ready.');
+ navigate('/dashboard');
+ } catch (error) {
+ toast.error(error.response?.data?.detail || 'Failed to create website');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ {/* Background Elements */}
+
+
+
+
+
+
+
+ Quick Setup
+
+
+ Launch your AI Agent
+
+
+ Just tell us where you'll be using the agent.
+
+
+
+
+
+
+
+
+
+
+
Expert Tip
+
+ After creation, you can customize the agent's tone and upload your FAQs to make it even smarter.
+
+
+
+
+
+
+ );
+};
+
+export default CreateFirstWebsite;
diff --git a/client/src/pages/Dashboard.jsx b/client/src/pages/Dashboard.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5c879d50c845c2ebd59c2793b809024c58b43e4b
--- /dev/null
+++ b/client/src/pages/Dashboard.jsx
@@ -0,0 +1,292 @@
+import React from 'react';
+import { useQuery } from '@tanstack/react-query';
+import api from '../api/axiosConfig';
+import {
+ Users,
+ MessageSquare,
+ Clock,
+ TrendingUp,
+ ArrowUpRight,
+ ArrowDownRight,
+ Activity,
+ CheckCircle,
+ AlertCircle,
+ Smile,
+ Frown,
+ Meh
+} from 'lucide-react';
+
+const StatCard = ({ title, value, change, icon: Icon, trend }) => (
+
+
+
+
+ {trend === 'up' ? : }
+ {change}
+
+
vs last month
+
+
+);
+
+// Mock data - realistic demo data
+const mockData = {
+ stats: {
+ totalChats: '12,456',
+ activeUsers: '2,348',
+ avgResponseTime: '1.8s',
+ satisfaction: '96.5%',
+ changeChats: '+23.5%',
+ changeUsers: '+15.2%',
+ changeResponse: '-12.4%',
+ changeSatisfaction: '+3.8%'
+ },
+ recentChats: [
+ { id: 1, user: 'Sarah Johnson', message: 'What are your business hours?', time: '2m ago', status: 'resolved' },
+ { id: 2, user: 'Ahmed Khan', message: 'How do I reset my password?', time: '5m ago', status: 'resolved' },
+ { id: 3, user: 'Maria Garcia', message: 'Pricing for enterprise plan?', time: '8m ago', status: 'active' },
+ { id: 4, user: 'John Smith', message: 'Integration with Shopify?', time: '12m ago', status: 'resolved' },
+ { id: 5, user: 'Li Wei', message: 'Multi-language support details', time: '15m ago', status: 'resolved' }
+ ],
+ sentimentData: [
+ { label: 'Positive', value: 78, color: 'bg-green-500', icon: Smile },
+ { label: 'Neutral', value: 18, color: 'bg-gray-400', icon: Meh },
+ { label: 'Negative', value: 4, color: 'bg-red-500', icon: Frown }
+ ],
+ popularFAQs: [
+ { question: 'What are your business hours?', hits: 245 },
+ { question: 'How do I reset my password?', hits: 189 },
+ { question: 'What payment methods do you accept?', hits: 156 },
+ { question: 'How long is the free trial?', hits: 134 }
+ ],
+ responseTimes: [
+ { time: '< 1s', count: 45 },
+ { time: '1-2s', count: 35 },
+ { time: '2-3s', count: 15 },
+ { time: '> 3s', count: 5 }
+ ]
+};
+
+const Dashboard = () => {
+ const { data: stats, isLoading, error } = useQuery({
+ queryKey: ['dashboard-stats'],
+ queryFn: async () => {
+ try {
+ // Fetch REAL data from backend API
+ const response = await api.get('/dashboard/all');
+ return response.data;
+ } catch (error) {
+ console.warn('API call failed, using fallback data:', error);
+ // Fallback to mock data if API fails
+ return mockData;
+ }
+ },
+ refetchInterval: 30000, // Refresh every 30 seconds
+ retry: 1,
+ staleTime: 10000,
+ });
+
+ if (isLoading || !stats) {
+ return (
+
+
+
Dashboard Overview
+
Loading your dashboard...
+
+
+ {[...Array(4)].map((_, i) => (
+
+ ))}
+
+
+
+ );
+ }
+
+ return (
+
+
+
Dashboard Overview
+
Welcome back! Here's what's happening with your customer support.
+
+
+ {/* Main Stats */}
+
+
+
+
+
+
+
+ {/* Recent Activity & Sentiment */}
+
+ {/* Recent Chats */}
+
+
+
Recent Chats
+ View All
+
+
+ {(stats?.recentChats || mockData.recentChats).map((chat) => (
+
+
+
+ {chat.user.split(' ').map(n => n[0]).join('')}
+
+
+
+
{chat.user}
+ {chat.status === 'resolved' && (
+
+ )}
+
+
{chat.message}
+
+
+
{chat.time}
+
+ ))}
+
+
+
+ {/* Sentiment Analysis */}
+
+
Sentiment Analysis
+
+ {(stats?.sentimentData || mockData.sentimentData).map((sentiment, index) => {
+ const Icon = {
+ 'Positive': Smile,
+ 'Neutral': Meh,
+ 'Negative': Frown
+ }[sentiment.label] || Meh;
+
+ return (
+
+
+
+
+ {sentiment.label}
+
+
{sentiment.value}%
+
+
+
+ )
+ })}
+
+
+
+
+ {/* Popular FAQs & Response Times */}
+
+ {/* Popular FAQs */}
+
+
Most Asked Questions
+
+ {(stats?.popularFAQs || mockData.popularFAQs).map((faq, index) => (
+
+
+
+ {index + 1}
+
+
{faq.question}
+
+
{faq.hits}
+
+ ))}
+
+
+
+ {/* Response Time Distribution */}
+
+
Response Time Distribution
+
+ {(stats?.responseTimes || mockData.responseTimes).map((item, index) => (
+
+
+ {item.time}
+ {item.count}%
+
+
+
+ ))}
+
+
+
+
+
+ 80% of responses under 2 seconds!
+
+
+
+
+
+
+ {/* System Status */}
+
+
+ );
+};
+
+export default Dashboard;
\ No newline at end of file
diff --git a/client/src/pages/FAQManagement.jsx b/client/src/pages/FAQManagement.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..76413dabcabc4a91e6ca2084a0511c978b638cb0
--- /dev/null
+++ b/client/src/pages/FAQManagement.jsx
@@ -0,0 +1,374 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Plus, Search, Edit2, Trash2, CheckCircle, XCircle, ChevronDown, ChevronUp, Save, X, HelpCircle, Tag, AlertCircle, Upload, FileText, Download, Loader, Pause, Play, XCircle as CancelIcon, Check } from 'lucide-react';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+import BulkUploadModal from '../components/BulkUploadModal';
+
+const FAQManagement = () => {
+ const [faqs, setFaqs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [editingFaq, setEditingFaq] = useState(null);
+ const [websites, setWebsites] = useState([]);
+ const [selectedWebsiteId, setSelectedWebsiteId] = useState(null);
+ const [isBulkUploadOpen, setIsBulkUploadOpen] = useState(false);
+ const [uploadQueue, setUploadQueue] = useState([]);
+ const [isUploading, setIsUploading] = useState(false);
+ const [uploadProgress, setUploadProgress] = useState({});
+
+ // Form state
+ const [formData, setFormData] = useState({
+ question: '',
+ answer: '',
+ category: 'General',
+ priority: 0,
+ is_active: true
+ });
+
+ useEffect(() => {
+ fetchWebsites();
+ }, []);
+
+ useEffect(() => {
+ if (selectedWebsiteId) {
+ fetchFaqs();
+ }
+ }, [selectedWebsiteId]);
+
+ const fetchWebsites = async () => {
+ try {
+ const response = await api.get('/websites/');
+ setWebsites(response.data);
+ if (response.data.length > 0) {
+ setSelectedWebsiteId(response.data[0].id);
+ }
+ } catch (error) {
+ toast.error('Failed to fetch websites');
+ }
+ };
+
+ const fetchFaqs = async () => {
+ try {
+ setLoading(true);
+ const response = await api.get(`/faqs/${selectedWebsiteId}`);
+ setFaqs(response.data);
+ } catch (error) {
+ toast.error('Failed to fetch FAQs');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!formData.question.trim() || !formData.answer.trim()) {
+ toast.error('Question and Answer are required');
+ return;
+ }
+
+ try {
+ if (editingFaq) {
+ await api.put(`/faqs/${editingFaq.id}`, formData);
+ toast.success('FAQ updated successfully');
+ } else {
+ await api.post('/faqs', { ...formData, website_id: selectedWebsiteId });
+ toast.success('FAQ created successfully');
+ }
+
+ setIsModalOpen(false);
+ resetForm();
+ fetchFaqs();
+ } catch (error) {
+ toast.error(editingFaq ? 'Failed to update FAQ' : 'Failed to create FAQ');
+ }
+ };
+
+ const handleDelete = async (id) => {
+ if (!confirm('Are you sure you want to delete this FAQ?')) return;
+
+ try {
+ await api.delete(`/faqs/${id}`);
+ toast.success('FAQ deleted successfully');
+ fetchFaqs();
+ } catch (error) {
+ toast.error('Failed to delete FAQ');
+ }
+ };
+
+ const handleEdit = (faq) => {
+ setEditingFaq(faq);
+ setFormData({
+ question: faq.question,
+ answer: faq.answer,
+ category: faq.category,
+ priority: faq.priority,
+ is_active: faq.is_active
+ });
+ setIsModalOpen(true);
+ };
+
+ const resetForm = () => {
+ setEditingFaq(null);
+ setFormData({
+ question: '',
+ answer: '',
+ category: 'General',
+ priority: 0,
+ is_active: true
+ });
+ };
+
+ const filteredFaqs = faqs.filter(faq =>
+ faq.question.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ faq.answer.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ faq.category.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ return (
+
+
+
+
FAQ Management
+
Manage frequently asked questions for your chatbot
+
+
+
+
+
+
+
+
+
+
+
+ {/* Search and Stats */}
+
+
+
+ setSearchTerm(e.target.value)}
+ className="flex-1 outline-none text-secondary-900 placeholder-secondary-400"
+ />
+
+
+
+
Total FAQs
+
{faqs.length}
+
+
+
+
+
+ {/* FAQ List */}
+
+ {loading ? (
+
Loading FAQs...
+ ) : filteredFaqs.length === 0 ? (
+
+
+
+
+
No FAQs Found
+
Get started by adding your first FAQ.
+
+ ) : (
+
+ {filteredFaqs.map((faq) => (
+
+
+
+
+
+ {faq.is_active ? 'Active' : 'Inactive'}
+
+
+
+ {faq.category}
+
+ {faq.priority > 0 && (
+
+
+ Priority: {faq.priority}
+
+ )}
+
+
{faq.question}
+
{faq.answer}
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+ {/* Add/Edit Modal */}
+
+ {isModalOpen && (
+
+
+
+
+ {editingFaq ? 'Edit FAQ' : 'Add New FAQ'}
+
+
+
+
+
+
+
+ )}
+
+
+ {/* Bulk Upload Modal */}
+
setIsBulkUploadOpen(false)}
+ websiteId={selectedWebsiteId}
+ onSuccess={fetchFaqs}
+ />
+
+ );
+};
+
+export default FAQManagement;
diff --git a/client/src/pages/GDPR.jsx b/client/src/pages/GDPR.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..471a6d7e4d8ed5b1d32434d93072d4db803a6e93
--- /dev/null
+++ b/client/src/pages/GDPR.jsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+
+const GDPR = () => {
+ return (
+
+
+
+
+
+ GDPR Compliance
+
+
+ General Data Protection Regulation Commitment
+
+
+
+
+
+
+
+
+
+
Our Commitment to GDPR
+
+ Customer Agent is fully committed to compliance with the General Data Protection Regulation (GDPR). We understand the importance of protecting personal data and have implemented comprehensive measures to ensure the security and privacy of our users' information.
+
+
+
+
+
Data Processing Principles
+
+ We adhere to the following key principles:
+
+
+ - Lawfulness, Fairness, and Transparency: We process data lawfully and transparently.
+ - Purpose Limitation: Data is collected for specified, explicit, and legitimate purposes.
+ - Data Minimization: We only collect data that is necessary for our services.
+ - Accuracy: We take reasonable steps to ensure data is accurate and up-to-date.
+ - Storage Limitation: Data is kept only for as long as necessary.
+ - Integrity and Confidentiality: We ensure appropriate security of personal data.
+
+
+
+
+
Your Rights Under GDPR
+
+ If you are a resident of the European Economic Area (EEA), you have certain data protection rights:
+
+
+ - The right to access, update, or delete your information.
+ - The right of rectification.
+ - The right to object.
+ - The right of restriction.
+ - The right to data portability.
+ - The right to withdraw consent.
+
+
+
+
+
Data Transfers
+
+ We may transfer personal data to countries outside the EEA. When we do so, we ensure appropriate safeguards are in place, such as Standard Contractual Clauses (SCCs), to protect your data.
+
+
+
+
+
Data Protection Officer
+
+ We have appointed a Data Protection Officer (DPO) to oversee our GDPR compliance. You can contact our DPO at:
+
+
+
Email: dpo@customeragent.com
+
Address: 123 Tech Street, San Francisco, CA 94105
+
+
+
+
+
+
+ );
+};
+
+export default GDPR;
diff --git a/client/src/pages/HelpCenter.jsx b/client/src/pages/HelpCenter.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..60ed2878eb45feac4d501b3554b6ebe4c586d737
--- /dev/null
+++ b/client/src/pages/HelpCenter.jsx
@@ -0,0 +1,149 @@
+import React, { useState } from 'react';
+import { motion } from 'framer-motion';
+import { Search, Book, MessageCircle, FileText, Settings, CreditCard, Users, ArrowRight } from 'lucide-react';
+import { Link } from 'react-router-dom';
+
+const HelpCenter = () => {
+ const [searchQuery, setSearchQuery] = useState('');
+
+ return (
+
+ {/* Hero Section */}
+
+
+
+
+ How can we help you?
+
+
+
+
+
+
setSearchQuery(e.target.value)}
+ className="w-full pl-12 pr-4 py-4 rounded-xl bg-secondary-50 dark:bg-secondary-800 border border-secondary-200 dark:border-secondary-700 focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 outline-none transition-all text-lg dark:text-white shadow-lg"
+ />
+
+
+
+
+
+ {/* Categories Section */}
+
+
+
+ {categories.map((category, index) => (
+
+
+
+
+ {category.title}
+
+ {category.description}
+
+
+ {category.articles.map((article, i) => (
+ -
+
+
+ {article}
+
+
+ ))}
+
+
+ View all articles
+
+
+ ))}
+
+
+
+
+ {/* Contact Support */}
+
+
+
Still need help?
+
+ Our support team is available 24/7 to assist you.
+
+
+
+ Contact Support
+
+
+ Join Community
+
+
+
+
+
+ );
+};
+
+const categories = [
+ {
+ icon: Book,
+ title: 'Getting Started',
+ description: 'Everything you need to know to get up and running.',
+ color: 'bg-blue-500',
+ articles: ['Quick start guide', 'Account setup', 'Importing data', 'First steps']
+ },
+ {
+ icon: Settings,
+ title: 'Account & Settings',
+ description: 'Manage your account, preferences, and team members.',
+ color: 'bg-purple-500',
+ articles: ['Profile settings', 'Notification preferences', 'Team management', 'Security settings']
+ },
+ {
+ icon: CreditCard,
+ title: 'Billing & Subscriptions',
+ description: 'Information about plans, payments, and invoices.',
+ color: 'bg-green-500',
+ articles: ['Pricing plans', 'Payment methods', 'Invoices', 'Upgrade/Downgrade']
+ },
+ {
+ icon: MessageCircle,
+ title: 'Using the Platform',
+ description: 'Detailed guides on all features and functionalities.',
+ color: 'bg-orange-500',
+ articles: ['Chat interface', 'AI configuration', 'Analytics dashboard', 'Integrations']
+ },
+ {
+ icon: Users,
+ title: 'Team Collaboration',
+ description: 'Best practices for working together with your team.',
+ color: 'bg-pink-500',
+ articles: ['Roles and permissions', 'Shared inboxes', 'Internal notes', 'Team reports']
+ },
+ {
+ icon: FileText,
+ title: 'API & Developers',
+ description: 'Technical documentation for custom integrations.',
+ color: 'bg-indigo-500',
+ articles: ['API reference', 'Webhooks', 'Authentication', 'SDKs']
+ }
+];
+
+export default HelpCenter;
diff --git a/client/src/pages/Home.css b/client/src/pages/Home.css
new file mode 100644
index 0000000000000000000000000000000000000000..88846db617dfbf70fac1357177c32dd8dce84419
--- /dev/null
+++ b/client/src/pages/Home.css
@@ -0,0 +1,490 @@
+/* Home Page Styles - Professional & Animated */
+
+/* Hero Section */
+.hero {
+ min-height: 100vh;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 80px 20px 60px;
+ position: relative;
+ overflow: hidden;
+}
+
+.hero::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: url('data:image/svg+xml,
');
+ opacity: 0.3;
+}
+
+.hero__container {
+ max-width: 1200px;
+ margin: 0 auto;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 60px;
+ align-items: center;
+ position: relative;
+ z-index: 1;
+}
+
+.hero__content {
+ padding: 20px 0;
+}
+
+.hero__title {
+ font-size: clamp(2.5rem, 5vw, 4rem);
+ font-weight: 800;
+ line-height: 1.1;
+ margin-bottom: 24px;
+ letter-spacing: -0.02em;
+}
+
+.hero__title--gradient {
+ background: linear-gradient(135deg, #ffd89b 0%, #19547b 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+
+.hero__subtitle {
+ font-size: 1.25rem;
+ line-height: 1.6;
+ margin-bottom: 40px;
+ opacity: 0.95;
+ max-width: 540px;
+}
+
+.hero__cta {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 48px;
+ flex-wrap: wrap;
+}
+
+.hero__stats {
+ display: flex;
+ gap: 48px;
+ flex-wrap: wrap;
+}
+
+.stat {
+ text-align: left;
+}
+
+.stat__number {
+ font-size: 2.5rem;
+ font-weight: 700;
+ margin-bottom: 4px;
+}
+
+.stat__label {
+ font-size: 0.875rem;
+ opacity: 0.8;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+/* Chat Demo */
+.hero__chat-demo {
+ background: white;
+ border-radius: 20px;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
+ overflow: hidden;
+ animation: float 6s ease-in-out infinite;
+}
+
+@keyframes float {
+
+ 0%,
+ 100% {
+ transform: translateY(0px);
+ }
+
+ 50% {
+ transform: translateY(-20px);
+ }
+}
+
+.chat-demo__header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 16px 20px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.chat-demo__dots {
+ display: flex;
+ gap: 6px;
+}
+
+.chat-demo__dots span {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.4);
+}
+
+.chat-demo__title {
+ font-weight: 600;
+ font-size: 1rem;
+}
+
+.chat-demo__messages {
+ padding: 24px;
+ min-height: 320px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.chat-demo__message {
+ max-width: 80%;
+ padding: 12px 16px;
+ border-radius: 18px;
+ font-size: 0.95rem;
+ line-height: 1.5;
+}
+
+.chat-demo__message--user {
+ background: #667eea;
+ color: white;
+ align-self: flex-end;
+ border-bottom-right-radius: 4px;
+}
+
+.chat-demo__message--bot {
+ background: #f5f5f5;
+ color: #333;
+ align-self: flex-start;
+ border-bottom-left-radius: 4px;
+}
+
+.typing-indicator {
+ display: inline-flex;
+ gap: 4px;
+ padding: 8px 0;
+}
+
+.typing-indicator span {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #999;
+ animation: typing 1.4s infinite;
+}
+
+.typing-indicator span:nth-child(2) {
+ animation-delay: 0.2s;
+}
+
+.typing-indicator span:nth-child(3) {
+ animation-delay: 0.4s;
+}
+
+@keyframes typing {
+
+ 0%,
+ 60%,
+ 100% {
+ transform: translateY(0);
+ opacity: 0.7;
+ }
+
+ 30% {
+ transform: translateY(-10px);
+ opacity: 1;
+ }
+}
+
+/* Features Section */
+.features {
+ padding: 100px 20px;
+ background: #f9fafb;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.section-header {
+ text-align: center;
+ margin-bottom: 60px;
+}
+
+.section-title {
+ font-size: clamp(2rem, 4vw, 3rem);
+ font-weight: 700;
+ margin-bottom: 16px;
+ color: #1a1a1a;
+}
+
+.section-subtitle {
+ font-size: 1.25rem;
+ color: #666;
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+.features__grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 32px;
+}
+
+.feature-card {
+ background: white;
+ padding: 32px;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ cursor: pointer;
+}
+
+.feature-card:hover {
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
+}
+
+.feature-card__icon {
+ font-size: 3rem;
+ margin-bottom: 20px;
+ width: 80px;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 16px;
+ background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
+}
+
+.feature-card__title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 12px;
+ color: #1a1a1a;
+}
+
+.feature-card__description {
+ font-size: 1rem;
+ line-height: 1.6;
+ color: #666;
+}
+
+/* How It Works */
+.how-it-works {
+ padding: 100px 20px;
+ background: linear-gradient(180deg, #ffffff 0%, #f9fafb 100%);
+}
+
+.steps {
+ max-width: 800px;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+}
+
+.step {
+ display: flex;
+ gap: 24px;
+ align-items: flex-start;
+}
+
+.step__number {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ font-size: 1.5rem;
+ font-weight: 700;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.step__content {
+ flex: 1;
+ padding-top: 8px;
+}
+
+.step__title {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: #1a1a1a;
+}
+
+.step__description {
+ font-size: 1.125rem;
+ line-height: 1.6;
+ color: #666;
+}
+
+/* Testimonials */
+.testimonials {
+ padding: 100px 20px;
+ background: #f9fafb;
+}
+
+.testimonials__grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: 32px;
+}
+
+.testimonial-card {
+ background: white;
+ padding: 32px;
+ border-radius: 16px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+}
+
+.testimonial-card__stars {
+ color: #fbbf24;
+ font-size: 1.25rem;
+ margin-bottom: 16px;
+}
+
+.testimonial-card__quote {
+ font-size: 1.125rem;
+ line-height: 1.6;
+ color: #333;
+ margin-bottom: 24px;
+ font-style: italic;
+}
+
+.testimonial-card__author {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.testimonial-card__author img {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+}
+
+.testimonial-card__name {
+ font-weight: 600;
+ color: #1a1a1a;
+}
+
+.testimonial-card__role {
+ font-size: 0.875rem;
+ color: #666;
+}
+
+/* CTA Section */
+.cta {
+ padding: 120px 20px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ text-align: center;
+}
+
+.cta__container {
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.cta__title {
+ font-size: clamp(2rem, 4vw, 3rem);
+ font-weight: 700;
+ margin-bottom: 20px;
+}
+
+.cta__subtitle {
+ font-size: 1.25rem;
+ margin-bottom: 40px;
+ opacity: 0.95;
+}
+
+.cta__note {
+ margin-top: 20px;
+ font-size: 0.875rem;
+ opacity: 0.8;
+}
+
+/* Buttons */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 28px;
+ border-radius: 8px;
+ font-size: 1rem;
+ font-weight: 600;
+ text-decoration: none;
+ transition: all 0.3s ease;
+ border: none;
+ cursor: pointer;
+}
+
+.btn--large {
+ padding: 16px 36px;
+ font-size: 1.125rem;
+}
+
+.btn--primary {
+ background: white;
+ color: #667eea;
+}
+
+.btn--primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+}
+
+.btn--secondary {
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+ backdrop-filter: blur(10px);
+}
+
+.btn--secondary:hover {
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.btn__icon {
+ font-size: 1.25rem;
+ transition: transform 0.3s ease;
+}
+
+.btn:hover .btn__icon {
+ transform: translateX(4px);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .hero__container {
+ grid-template-columns: 1fr;
+ text-align: center;
+ }
+
+ .hero__cta {
+ justify-content: center;
+ }
+
+ .hero__stats {
+ justify-content: center;
+ }
+
+ .hero__subtitle {
+ margin: 0 auto 40px;
+ }
+
+ .features__grid,
+ .testimonials__grid {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/client/src/pages/Home.jsx b/client/src/pages/Home.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..35d29105def44e4a9af7d433054639af28dbf3a4
--- /dev/null
+++ b/client/src/pages/Home.jsx
@@ -0,0 +1,374 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import {
+ Bot, Globe, Zap, TrendingUp, Shield, Target,
+ UserPlus, FileText, Settings, Activity,
+ ArrowRight, CheckCircle2, MessageSquare, Star
+} from 'lucide-react';
+
+const Home = () => {
+ return (
+
+ {/* Hero Section */}
+
+ {/* Background Pattern */}
+
+
+
+
+
+ {/*
+
+
+
+
+ New: GPT-4 Turbo Integration
+
*/}
+
+
+ Transform Support with
+ AI Intelligence
+
+
+
+ Intelligent chatbots that understand your customers in multiple languages,
+ respond instantly, and learn from every conversation.
+
+
+
+
+ Start Free Trial
+
+
+
+ View Pricing
+
+
+
+
+
+
+
+
+
+
+
+
+ What are your business hours?
+
+
+
+ We're open Monday-Friday, 9 AM - 5 PM EST. How can I help you today?
+
+
+ Do you offer international shipping?
+
+
+
+ Yes, we ship to over 50 countries worldwide! Shipping times vary by location.
+
+
+
+
+
+
+
+
+ {/* Features Section */}
+
+
+
+ Powerful Features
+
+ Everything you need to deliver exceptional customer support at scale
+
+
+
+
+ {features.map((feature, index) => (
+
+
+
+
+ {feature.title}
+ {feature.description}
+
+ ))}
+
+
+
+
+ {/* How It Works */}
+
+
+
+ How It Works
+ Get started in minutes, not hours
+
+
+
+ {steps.map((step, index) => (
+
+ {index !== steps.length - 1 && (
+
+ )}
+
+
+
+
+
{step.title}
+
{step.description}
+
+
+ ))}
+
+
+
+
+ {/* Testimonials */}
+
+
+
+ Loved by Teams Worldwide
+ See what our customers are saying
+
+
+
+ {testimonials.map((testimonial, index) => (
+
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+ "{testimonial.quote}"
+
+

+
+
{testimonial.name}
+
{testimonial.role}
+
+
+
+ ))}
+
+
+
+
+ {/* CTA Section */}
+
+
+
+
+
+
+ Ready to Transform Your Support?
+
+ Join thousands of businesses using AI to deliver better customer experiences.
+
+
+
+ Start Free Trial
+
+
+
+ No credit card required • Cancel anytime
+
+
+
+
+ );
+};
+
+// Features data
+const features = [
+ {
+ icon: MessageSquare,
+ title: 'AI-Powered Responses',
+ description: 'Advanced NLP understands context and intent for accurate, helpful responses.',
+ bgClass: 'bg-blue-100 dark:bg-blue-900/30',
+ textClass: 'text-blue-600 dark:text-blue-400'
+ },
+ {
+ icon: Globe,
+ title: 'Multi-Language Support',
+ description: 'Seamlessly handles English, Urdu, and Roman Urdu conversations.',
+ bgClass: 'bg-purple-100 dark:bg-purple-900/30',
+ textClass: 'text-purple-600 dark:text-purple-400'
+ },
+ {
+ icon: Zap,
+ title: 'Lightning Fast',
+ description: 'Sub-second response times with intelligent caching and pre-fetching.',
+ bgClass: 'bg-yellow-100 dark:bg-yellow-900/30',
+ textClass: 'text-yellow-600 dark:text-yellow-400'
+ },
+ {
+ icon: TrendingUp,
+ title: 'Advanced Analytics',
+ description: 'Real-time insights into customer sentiment, trends, and bot performance.',
+ bgClass: 'bg-green-100 dark:bg-green-900/30',
+ textClass: 'text-green-600 dark:text-green-400'
+ },
+ {
+ icon: Shield,
+ title: 'Enterprise Security',
+ description: 'Bank-level encryption, GDPR compliant, and SOC 2 certified.',
+ bgClass: 'bg-red-100 dark:bg-red-900/30',
+ textClass: 'text-red-600 dark:text-red-400'
+ },
+ {
+ icon: Target,
+ title: 'Smart Routing',
+ description: 'Automatically escalate complex queries to human agents when needed.',
+ bgClass: 'bg-orange-100 dark:bg-orange-900/30',
+ textClass: 'text-orange-600 dark:text-orange-400'
+ }
+];
+
+// Steps data
+const steps = [
+ {
+ icon: UserPlus,
+ title: 'Sign Up & Connect',
+ description: 'Create your account and connect your website in under 2 minutes.'
+ },
+ {
+ icon: FileText,
+ title: 'Train Your Bot',
+ description: 'Upload FAQs, documents, or let AI learn from your website automatically.'
+ },
+ {
+ icon: Settings,
+ title: 'Customize & Deploy',
+ description: 'Match your brand colors, add your logo, and go live with one click.'
+ },
+ {
+ icon: Activity,
+ title: 'Monitor & Improve',
+ description: 'Track performance, gather feedback, and watch your bot get smarter.'
+ }
+];
+
+// Testimonials data
+const testimonials = [
+ {
+ quote: 'Reduced our support tickets by 70% in the first month. The AI actually understands our customers!',
+ name: 'Sarah Johnson',
+ role: 'CEO, TechStart Inc',
+ avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=150&q=80'
+ },
+ {
+ quote: 'Best investment we made this year. 24/7 support without hiring more staff.',
+ name: 'Ahmed Khan',
+ role: 'Operations Manager, EduHub',
+ avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=150&q=80'
+ },
+ {
+ quote: 'The multi-language support is a game-changer for our global customer base.',
+ name: 'Maria Garcia',
+ role: 'Head of Support, GlobalCorp',
+ avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=150&q=80'
+ }
+];
+
+export default Home;
diff --git a/client/src/pages/Leads.jsx b/client/src/pages/Leads.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fc78724ea5c9ae376d0aae2fafeb1fadf1f37bc6
--- /dev/null
+++ b/client/src/pages/Leads.jsx
@@ -0,0 +1,240 @@
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { getLeadProfiles } from '../api/leadsApi';
+import { UserIcon, ClockIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
+
+export default function Leads() {
+ const navigate = useNavigate();
+ const [leads, setLeads] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ useEffect(() => {
+ fetchLeads();
+ }, []);
+
+ const fetchLeads = async () => {
+ try {
+ setLoading(true);
+ const data = await getLeadProfiles();
+ setLeads(data);
+ setError(null);
+ } catch (err) {
+ setError('Failed to load patient profiles. Please try again.');
+ console.error('Error loading leads:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const filteredLeads = leads.filter(lead =>
+ (lead.email?.toLowerCase().includes(searchTerm.toLowerCase())) ||
+ (lead.name?.toLowerCase().includes(searchTerm.toLowerCase()))
+ );
+
+ const formatDate = (dateString) => {
+ if (!dateString) return 'Never';
+ const date = new Date(dateString);
+ const now = new Date();
+ const diffTime = Math.abs(now - date);
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+
+ if (diffDays === 0) return 'Today';
+ if (diffDays === 1) return 'Yesterday';
+ if (diffDays < 7) return `${diffDays} days ago`;
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
+ return date.toLocaleDateString();
+ };
+
+ const truncateText = (text, maxLength = 60) => {
+ if (!text) return 'No summary available';
+ if (text.length <= maxLength) return text;
+ return text.substring(0, maxLength) + '...';
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
Patient Profiles
+
+ View and manage patient health records and conversation history
+
+
+
+
+
+ {leads.length} {leads.length === 1 ? 'Patient' : 'Patients'}
+
+
+
+
+ {/* Search */}
+
+ setSearchTerm(e.target.value)}
+ className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
+ />
+
+
+ {/* Patients Table */}
+ {filteredLeads.length === 0 ? (
+
+
+
No patients found
+
+ {searchTerm ? 'Try adjusting your search criteria.' : 'Patient profiles will appear here once visitors provide their email.'}
+
+
+ ) : (
+
+ {/* Desktop Table */}
+
+
+
+
+ |
+ Patient
+ |
+
+ Sessions
+ |
+
+ Last Active
+ |
+
+ Health Summary
+ |
+
+
+
+ {filteredLeads.map((lead) => (
+ navigate(`/leads/${encodeURIComponent(lead.email)}`)}
+ className="hover:bg-gray-50 cursor-pointer transition-colors"
+ >
+
+
+
+
+
+
+ {lead.name || 'Anonymous'}
+ {lead.email}
+
+
+ |
+
+
+ {lead.sessions || 0} {lead.sessions === 1 ? 'session' : 'sessions'}
+
+ |
+
+
+
+ {formatDate(lead.last_active)}
+
+ |
+
+
+ {truncateText(lead.summary)}
+
+ {lead.conditions && lead.conditions.length > 0 && (
+
+ {lead.conditions.slice(0, 3).map((condition, idx) => (
+
+ {condition}
+
+ ))}
+ {lead.conditions.length > 3 && (
+
+ +{lead.conditions.length - 3} more
+
+ )}
+
+ )}
+ |
+
+ ))}
+
+
+
+
+ {/* Mobile Cards */}
+
+ {filteredLeads.map((lead) => (
+
navigate(`/leads/${encodeURIComponent(lead.email)}`)}
+ className="p-4 hover:bg-gray-50 cursor-pointer transition-colors"
+ >
+
+
+
+
+
+
+
{lead.name || 'Anonymous'}
+
{lead.email}
+
+
+
+ {lead.sessions || 0}
+
+
+
+
+ {formatDate(lead.last_active)}
+
+
{truncateText(lead.summary, 80)}
+ {lead.conditions && lead.conditions.length > 0 && (
+
+ {lead.conditions.slice(0, 2).map((condition, idx) => (
+
+ {condition}
+
+ ))}
+ {lead.conditions.length > 2 && (
+
+ +{lead.conditions.length - 2}
+
+ )}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/client/src/pages/Legal.css b/client/src/pages/Legal.css
new file mode 100644
index 0000000000000000000000000000000000000000..cd30333cc0c9bd5947ff837d35dd7b78db55eea2
--- /dev/null
+++ b/client/src/pages/Legal.css
@@ -0,0 +1,79 @@
+/* Legal Pages (Privacy & Terms) */
+.legal-page {
+ min-height: 100vh;
+}
+
+.legal-hero {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 100px 20px 60px;
+ text-align: center;
+}
+
+.legal-hero h1 {
+ font-size: clamp(2rem, 4vw, 3rem);
+ font-weight: 700;
+ margin-bottom: 12px;
+}
+
+.legal-hero p {
+ font-size: 1rem;
+ opacity: 0.9;
+}
+
+.legal-content {
+ padding: 80px 20px;
+ background: white;
+}
+
+.container-narrow {
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.legal-section {
+ margin-bottom: 48px;
+}
+
+.legal-section h2 {
+ font-size: 1.75rem;
+ font-weight: 600;
+ margin-bottom: 16px;
+ color: #1a1a1a;
+}
+
+.legal-section p {
+ font-size: 1rem;
+ line-height: 1.8;
+ color: #333;
+ margin-bottom: 16px;
+}
+
+.legal-section ul {
+ margin-left: 24px;
+ margin-bottom: 16px;
+}
+
+.legal-section li {
+ font-size: 1rem;
+ line-height: 1.8;
+ color: #333;
+ margin-bottom: 8px;
+}
+
+.legal-contact {
+ background: #f9fafb;
+ padding: 32px;
+ border-radius: 12px;
+ margin-top: 60px;
+}
+
+.legal-contact h2 {
+ font-size: 1.5rem;
+ margin-bottom: 16px;
+}
+
+.legal-contact p {
+ margin-bottom: 8px;
+ color: #333;
+}
diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b419a0224c0e0a80d45c9f8b83da414752db2b91
--- /dev/null
+++ b/client/src/pages/Login.jsx
@@ -0,0 +1,175 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '../context/AuthContext';
+import { Mail, Lock, ArrowRight, Loader2, AlertCircle, Eye, EyeOff } from 'lucide-react';
+import { motion } from 'framer-motion';
+import { GoogleLogin } from '@react-oauth/google';
+
+const Login = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [error, setError] = useState('');
+ const { login, googleLogin, loading } = useAuth();
+ const navigate = useNavigate();
+
+ const handleGoogleSuccess = async (credentialResponse) => {
+ try {
+ const data = await googleLogin(credentialResponse.credential);
+ if (data.is_new) {
+ navigate('/onboarding-success');
+ } else {
+ navigate('/websites');
+ }
+ } catch (err) {
+ setError(err || 'Google login failed');
+ }
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+ try {
+ await login(email, password);
+ navigate('/websites');
+ } catch (error) {
+ setError(error || 'Login failed. Please try again.');
+ }
+ };
+
+ return (
+
+ {/* Background Elements */}
+
+
+
+
+
+
+
+
+
+
+
Welcome back
+
Sign in to your account to continue
+
+
+
+
+
+ Don't have an account?{' '}
+
+ Sign up for free
+
+
+
+
+
+ );
+};
+
+export default Login;
diff --git a/client/src/pages/NotificationCenter.jsx b/client/src/pages/NotificationCenter.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d03d4697dc86ec1e5318a980b780a53c87603b88
--- /dev/null
+++ b/client/src/pages/NotificationCenter.jsx
@@ -0,0 +1,172 @@
+import React, { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import {
+ Bell,
+ MessageSquare,
+ UserPlus,
+ Clock,
+ ChevronRight,
+ CheckCircle,
+ Search,
+ Filter,
+ ArrowRight,
+ Settings
+} from 'lucide-react';
+import { Link } from 'react-router-dom';
+import api from '../api/axiosConfig';
+import { formatDistanceToNow } from 'date-fns';
+
+export default function NotificationCenter() {
+ const [notifications, setNotifications] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [filter, setFilter] = useState('all');
+
+ useEffect(() => {
+ fetchNotifications();
+ }, []);
+
+ const fetchNotifications = async () => {
+ try {
+ const response = await api.get('/notifications/all');
+ setNotifications(response.data);
+ } catch (error) {
+ console.error('Failed to fetch notifications:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getTypeIcon = (type) => {
+ switch (type) {
+ case 'unanswered_question': return MessageSquare;
+ case 'new_lead': return UserPlus;
+ default: return Bell;
+ }
+ };
+
+ const getTypeColor = (type) => {
+ switch (type) {
+ case 'unanswered_question': return 'text-primary-600 bg-primary-50';
+ case 'new_lead': return 'text-blue-600 bg-blue-50';
+ default: return 'text-secondary-600 bg-secondary-50';
+ }
+ };
+
+ const filteredNotifications = notifications.filter(n => {
+ if (filter === 'all') return true;
+ return n.type === filter;
+ });
+
+ return (
+
+
+
+
Notification Center
+
Manage and respond to all platform alerts in one place.
+
+
+
+
+ Settings
+
+
+
+
+ {/* Filters */}
+
+ {[
+ { id: 'all', label: 'All Notifications', icon: Bell },
+ { id: 'unanswered_question', label: 'Questions', icon: MessageSquare },
+ { id: 'new_lead', label: 'Leads', icon: UserPlus }
+ ].map((f) => (
+
+ ))}
+
+
+ {/* List */}
+
+ {loading ? (
+
+
+
Loading notifications...
+
+ ) : filteredNotifications.length > 0 ? (
+
+
+ {filteredNotifications.map((notif, index) => {
+ const Icon = getTypeIcon(notif.type);
+ return (
+
+
+
+
+
+
+
+
+ {notif.title}
+
+
+
+ {formatDistanceToNow(new Date(notif.timestamp), { addSuffix: true })}
+
+
+
+ {notif.message}
+
+
+
+ Take Action
+
+
+ {notif.priority === 'high' && (
+
+ High Priority
+
+ )}
+
+
+
+
+
+ );
+ })}
+
+
+ ) : (
+
+
+
+
+
You're All Caught Up!
+
+ No new notifications at the moment. We'll alert you when there's something to attend to.
+
+
+ )}
+
+
+ );
+}
diff --git a/client/src/pages/NotificationSettings.jsx b/client/src/pages/NotificationSettings.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..dae70729982f9081009aff37dc0232fa5c7dd197
--- /dev/null
+++ b/client/src/pages/NotificationSettings.jsx
@@ -0,0 +1,256 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import { Bell, Shield, Clock, Zap, Target, Save, Loader, AlertCircle } from 'lucide-react';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+const SettingsCard = ({ title, subtitle, icon: Icon, children }) => (
+
+);
+
+const SLABox = ({ label, hours, onChange, color, icon: Icon }) => (
+
+
+
+
+ onChange(parseInt(e.target.value) || 1)}
+ className="w-16 px-2 py-1 bg-secondary-50 border border-secondary-200 rounded-lg text-center font-bold text-primary-600 outline-none focus:ring-2 focus:ring-primary-500"
+ />
+ hrs
+
+
+
+
+
+
+);
+
+const NotificationSettings = () => {
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [settings, setSettings] = useState({
+ notifications: true,
+ autoResponse: true,
+ slaUrgentHours: 1,
+ slaMediumHours: 4,
+ slaLowHours: 24,
+ confidenceThreshold: 0.6
+ });
+
+ useEffect(() => {
+ fetchSettings();
+ }, []);
+
+ const fetchSettings = async () => {
+ try {
+ const response = await api.get('/settings/get');
+ setSettings({
+ notifications: response.data.notifications !== false,
+ autoResponse: response.data.autoResponse !== false,
+ slaUrgentHours: response.data.slaUrgentHours || 1,
+ slaMediumHours: response.data.slaMediumHours || 4,
+ slaLowHours: response.data.slaLowHours || 24,
+ confidenceThreshold: response.data.confidenceThreshold || 0.6
+ });
+ } catch (error) {
+ console.error('Failed to fetch settings:', error);
+ toast.error('Could not load settings');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSave = async () => {
+ setSaving(true);
+ try {
+ await api.post('/settings/update', settings);
+ toast.success('Notification preferences updated!');
+ } catch (error) {
+ toast.error('Failed to save changes');
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ {/* Core Channels */}
+
+
+
setSettings({ ...settings, notifications: !settings.notifications })}
+ className={`p-6 rounded-3xl border-2 transition-all cursor-pointer flex flex-col gap-4 ${settings.notifications ? 'border-primary-500 bg-primary-50/20' : 'border-secondary-100 hover:border-secondary-200'}`}
+ >
+
+
+
Email Alerts
+
Receive high-priority handoff emails and lead details.
+
+
+
+
setSettings({ ...settings, autoResponse: !settings.autoResponse })}
+ className={`p-6 rounded-3xl border-2 transition-all cursor-pointer flex flex-col gap-4 ${settings.autoResponse ? 'border-primary-500 bg-primary-50/20' : 'border-secondary-100 hover:border-secondary-200'}`}
+ >
+
+
+
Auto-Escalation
+
Automatically create tickets when AI confidence is low.
+
+
+
+
+
+ {/* SLA Management */}
+
+
+ setSettings({ ...settings, slaUrgentHours: v })}
+ />
+ setSettings({ ...settings, slaMediumHours: v })}
+ />
+ setSettings({ ...settings, slaLowHours: v })}
+ />
+
+
+
+ {/* Confidence Threshold */}
+
+
+
+ Confidence Threshold
+ {Math.round(settings.confidenceThreshold * 100)}%
+
+
+
setSettings({ ...settings, confidenceThreshold: parseFloat(e.target.value) })}
+ className="w-full h-4 bg-secondary-100 rounded-full appearance-none cursor-pointer accent-primary-600"
+ />
+
+
+ Very Sensitive
+ Balanced
+ Risk-Tolerant
+
+
+
+
+
+ {settings.confidenceThreshold > 0.7
+ ? "High Threshold: The AI will ask for help more often to ensure 100% accuracy."
+ : settings.confidenceThreshold < 0.4
+ ? "Low Threshold: The AI will try to answer even if it's not very sure. Use with caution!"
+ : "Balanced: Moderate sensitivity. Good for most general-purpose chatbots."}
+
+
+
+
+
+
+
+ {saving ? (
+
+ ) : (
+
+ )}
+ {saving ? 'UPDATING...' : 'SAVE SETTINGS'}
+
+
+
+
+ );
+};
+
+export default NotificationSettings;
diff --git a/client/src/pages/OnboardingSuccess.jsx b/client/src/pages/OnboardingSuccess.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..50882e3e40ea259b41152ea3ea7ba4adfc0f5da4
--- /dev/null
+++ b/client/src/pages/OnboardingSuccess.jsx
@@ -0,0 +1,103 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { CheckCircle2, ArrowRight, Rocket, Building2, Zap, ShieldCheck } from 'lucide-react';
+import { useNavigate } from 'react-router-dom';
+
+const OnboardingSuccess = () => {
+ const navigate = useNavigate();
+
+ const steps = [
+ {
+ icon:
,
+ title: "Add your website",
+ description: "Connect your domain to start training your AI."
+ },
+ {
+ icon:
,
+ title: "Pick your industry",
+ description: "Customize the agent's tone and expertise."
+ },
+ {
+ icon:
,
+ title: "Go live",
+ description: "Embed the widget and start helping customers."
+ }
+ ];
+
+ return (
+
+ {/* Background Elements */}
+
+
+
+
+
+
+
+
+
+ Welcome aboard!
+
+
+ Your account has been created successfully. Let's get your first AI agent up and running in minutes.
+
+
+
+
+ {steps.map((step, index) => (
+
+
+ {step.icon}
+
+ {step.title}
+
+ {step.description}
+
+
+ ))}
+
+
+
navigate('/websites')}
+ className="w-full md:w-auto px-12 py-5 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-700 hover:to-primary-600 text-white font-black text-xl rounded-2xl transition-all duration-300 shadow-xl shadow-primary-500/40 flex items-center justify-center mx-auto gap-3"
+ >
+ Get Started
+
+
+
+
+
+
+ Secure Account
+
+
+
No credit card required
+
+
+
+
+ );
+};
+
+export default OnboardingSuccess;
diff --git a/client/src/pages/PatientTimeline.jsx b/client/src/pages/PatientTimeline.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e65cbfed13f2dcde4193911fa1982b8b2f22955b
--- /dev/null
+++ b/client/src/pages/PatientTimeline.jsx
@@ -0,0 +1,235 @@
+import { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { getPatientTimeline } from '../api/leadsApi';
+import {
+ ArrowLeftIcon,
+ UserIcon,
+ ClockIcon,
+ ChatBubbleLeftRightIcon,
+ BeakerIcon,
+ ExclamationTriangleIcon
+} from '@heroicons/react/24/outline';
+import { CheckCircleIcon } from '@heroicons/react/24/solid';
+
+export default function PatientTimeline() {
+ const { email } = useParams();
+ const navigate = useNavigate();
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetchTimeline();
+ }, [email]);
+
+ const fetchTimeline = async () => {
+ try {
+ setLoading(true);
+ const timeline = await getPatientTimeline(email);
+ setData(timeline);
+ setError(null);
+ } catch (err) {
+ setError('Failed to load patient timeline. Please try again.');
+ console.error('Error loading timeline:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const formatDate = (dateString) => {
+ if (!dateString) return 'Unknown';
+ const date = new Date(dateString);
+ return date.toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ };
+
+ const getTriageColor = (level) => {
+ switch (level?.toLowerCase()) {
+ case 'high':
+ return 'bg-red-100 text-red-800 border-red-200';
+ case 'medium':
+ return 'bg-yellow-100 text-yellow-800 border-yellow-200';
+ case 'low':
+ default:
+ return 'bg-green-100 text-green-800 border-green-200';
+ }
+ };
+
+ const getTriageIcon = (level) => {
+ switch (level?.toLowerCase()) {
+ case 'high':
+ return
;
+ case 'low':
+ return
;
+ default:
+ return
;
+ }
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error || !data) {
+ return (
+
+
+
+
{error || 'Patient not found'}
+
+
+ );
+ }
+
+ const { profile, timeline } = data;
+
+ return (
+
+ {/* Back Button */}
+
+
+ {/* Patient Header Card */}
+
+
+
+
+
+
+
+
{profile.name || 'Anonymous Patient'}
+
{profile.email}
+
+
+
+
{profile.total_sessions || 0}
+
Total Sessions
+
+
+
+ {/* Health Summary */}
+ {profile.health_summary && (
+
+
Health Summary
+
{profile.health_summary}
+
+ )}
+
+ {/* Known Conditions */}
+ {profile.conditions && profile.conditions.length > 0 && (
+
+
Known Conditions
+
+ {profile.conditions.map((condition, idx) => (
+
+
+ {condition}
+
+ ))}
+
+
+ )}
+
+
+ {/* Timeline Header */}
+
+
Consultation Timeline
+
+ {timeline?.length || 0} {timeline?.length === 1 ? 'session' : 'sessions'}
+
+
+
+ {/* Timeline */}
+ {!timeline || timeline.length === 0 ? (
+
+
+
No sessions yet
+
+ Session history will appear here once the patient starts chatting.
+
+
+ ) : (
+
+ {timeline.map((session, idx) => (
+
+ {/* Session Header */}
+
+
+
+ #{timeline.length - idx}
+
+
+
+ {formatDate(session.date)}
+
+
+
+ {session.message_count || 0} messages
+
+
+
+
+ {/* Triage Badge */}
+ {session.triage && (
+
+ {getTriageIcon(session.triage)}
+ {session.triage} Risk
+
+ )}
+
+
+ {/* Summary */}
+
+
Session Summary
+
{session.summary}
+
+
+ {/* Symptoms */}
+ {session.symptoms && session.symptoms.length > 0 && (
+
+
Symptoms Discussed
+
+ {session.symptoms.map((symptom, sidx) => (
+
+ {symptom}
+
+ ))}
+
+
+ )}
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/pages/Pricing.css b/client/src/pages/Pricing.css
new file mode 100644
index 0000000000000000000000000000000000000000..3d31043792887993def3bd2bbd1e3e16bd043b60
--- /dev/null
+++ b/client/src/pages/Pricing.css
@@ -0,0 +1,213 @@
+/* Pricing Page */
+.pricing-page {
+ min-height: 100vh;
+}
+
+.pricing-header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 100px 20px 80px;
+ text-align: center;
+}
+
+.pricing-header__title {
+ font-size: clamp(2.5rem, 5vw, 4rem);
+ font-weight: 800;
+ margin-bottom: 20px;
+}
+
+.pricing-header__subtitle {
+ font-size: 1.25rem;
+ opacity: 0.95;
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+.pricing-section {
+ padding: 80px 20px;
+ background: #f9fafb;
+ margin-top: -40px;
+}
+
+.pricing-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+ gap: 32px;
+ margin-bottom: 60px;
+}
+
+.pricing-card {
+ background: white;
+ border-radius: 20px;
+ padding: 40px 32px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ transition: all 0.3s ease;
+ position: relative;
+}
+
+.pricing-card--popular {
+ border: 3px solid #667eea;
+ transform: scale(1.05);
+}
+
+.pricing-card:hover {
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
+}
+
+.pricing-card__badge {
+ position: absolute;
+ top: -12px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 6px 20px;
+ border-radius: 20px;
+ font-size: 0.875rem;
+ font-weight: 600;
+}
+
+.pricing-card__name {
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-bottom: 16px;
+ color: #1a1a1a;
+}
+
+.pricing-card__price {
+ margin-bottom: 16px;
+}
+
+.pricing-card__currency {
+ font-size: 2rem;
+ font-weight: 600;
+ vertical-align: super;
+}
+
+.pricing-card__amount {
+ font-size: 4rem;
+ font-weight: 800;
+ color: #667eea;
+}
+
+.pricing-card__period {
+ font-size: 1.125rem;
+ color: #666;
+}
+
+.pricing-card__description {
+ color: #666;
+ margin-bottom: 32px;
+ font-size: 1rem;
+}
+
+.pricing-card__features {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 32px 0;
+}
+
+.pricing-card__feature {
+ padding: 12px 0;
+ display: flex;
+ align-items: start;
+ gap: 12px;
+ font-size: 1rem;
+ color: #333;
+}
+
+.pricing-card__check {
+ color: #10b981;
+ font-weight: 700;
+ font-size: 1.25rem;
+}
+
+.btn--full {
+ width: 100%;
+ justify-content: center;
+ padding: 16px;
+ font-size: 1.125rem;
+}
+
+.enterprise-card {
+ background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
+ color: white;
+ border-radius: 20px;
+ padding: 48px 40px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 40px;
+ flex-wrap: wrap;
+}
+
+.enterprise-card__title {
+ font-size: 2rem;
+ margin-bottom: 12px;
+}
+
+.enterprise-card__description {
+ font-size: 1.125rem;
+ opacity: 0.9;
+ margin-bottom: 24px;
+}
+
+.enterprise-card__features {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16px;
+}
+
+.enterprise-card__features li {
+ background: rgba(255, 255, 255, 0.1);
+ padding: 8px 16px;
+ border-radius: 8px;
+ font-size: 0.95rem;
+}
+
+.pricing-faq {
+ padding: 100px 20px;
+ background: white;
+}
+
+.faq-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
+ gap: 32px;
+ max-width: 1000px;
+ margin: 0 auto;
+}
+
+.faq-item__question {
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 12px;
+ color: #1a1a1a;
+}
+
+.faq-item__answer {
+ font-size: 1rem;
+ line-height: 1.6;
+ color: #666;
+}
+
+@media (max-width: 768px) {
+ .pricing-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .pricing-card--popular {
+ transform: scale(1);
+ }
+
+ .enterprise-card {
+ text-align: center;
+ justify-content: center;
+ }
+
+ .faq-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/client/src/pages/Pricing.jsx b/client/src/pages/Pricing.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..20088c0ce6624308f952c0f7b28ce358a916a787
--- /dev/null
+++ b/client/src/pages/Pricing.jsx
@@ -0,0 +1,260 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { Check, HelpCircle, ArrowRight, Zap, Shield, Globe } from 'lucide-react';
+import { Link } from 'react-router-dom';
+
+const Pricing = () => {
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ Simple, Transparent Pricing
+
+
+ Choose the perfect plan for your business. No hidden fees.
+
+
+ {/* Toggle (Monthly/Yearly) placeholder if needed later */}
+ {/* ...
*/}
+
+
+
+
+ {/* Pricing Cards */}
+
+
+
+ {pricingPlans.map((plan, index) => (
+
+ {plan.popular && (
+
+ Most Popular
+
+ )}
+
+
+
{plan.name}
+
{plan.description}
+
+
+
+
+ ${plan.price}
+ /month
+
+
+
+
+ {plan.features.map((feature, i) => (
+ -
+
+ {feature}
+
+ ))}
+
+
+
+ {plan.cta}
+
+
+ ))}
+
+
+ {/* Enterprise */}
+
+
+
+
+
+
+
+
+
+ Enterprise
+
+
+ Custom solutions for large organizations with advanced security, compliance, and dedicated support needs.
+
+
+ -
+ Unlimited everything
+
+ -
+ Dedicated support team
+
+ -
+ Custom integrations
+
+ -
+ SLA guarantees
+
+
+
+
+ Contact Sales
+
+
+
+
+
+
+ {/* FAQ */}
+
+
+
+
Frequently Asked Questions
+
+ Have questions? We're here to help.
+
+
+
+
+ {faqs.map((faq, index) => (
+
+
+
+ {faq.question}
+
+
+ {faq.answer}
+
+
+ ))}
+
+
+
+
+ );
+};
+
+const pricingPlans = [
+ {
+ name: 'Free',
+ price: '0',
+ description: 'For individuals and hobbyists exploring AI support',
+ features: [
+ '100 messages/month',
+ '1 website',
+ 'Community support',
+ 'Basic analytics',
+ 'Standard response time'
+ ],
+ cta: 'Get Started',
+ popular: false
+ },
+ {
+ name: 'Starter',
+ price: '29',
+ description: 'Perfect for small businesses getting started',
+ features: [
+ '1,000 messages/month',
+ '1 website',
+ 'Email support',
+ 'Basic analytics',
+ 'Multi-language support',
+ '99.5% uptime SLA'
+ ],
+ cta: 'Start Free Trial',
+ popular: false
+ },
+ {
+ name: 'Professional',
+ price: '99',
+ description: 'Best for growing businesses requiring more power',
+ features: [
+ '10,000 messages/month',
+ '5 websites',
+ 'Priority support',
+ 'Advanced analytics',
+ 'Custom branding',
+ 'API access',
+ 'Sentiment analysis',
+ '99.9% uptime SLA'
+ ],
+ cta: 'Start Free Trial',
+ popular: true
+ },
+ {
+ name: 'Business',
+ price: '299',
+ description: 'For established businesses with high volume',
+ features: [
+ '50,000 messages/month',
+ 'Unlimited websites',
+ '24/7 phone support',
+ 'Custom integrations',
+ 'White label option',
+ 'Dedicated account manager',
+ 'Advanced AI features',
+ '99.95% uptime SLA'
+ ],
+ cta: 'Start Free Trial',
+ popular: false
+ }
+];
+
+const faqs = [
+ {
+ question: 'Can I change my plan later?',
+ answer: 'Yes! You can upgrade or downgrade your plan at any time. Changes take effect immediately and we\'ll prorate the difference.'
+ },
+ {
+ question: 'What happens after the free trial?',
+ answer: 'After your 14-day free trial, you\'ll be automatically moved to the plan you selected. You can cancel anytime before the trial ends without being charged.'
+ },
+ {
+ question: 'Do you offer refunds?',
+ answer: 'Yes, we offer a 30-day money-back guarantee. If you\'re not satisfied with our service, contact us for a full refund within 30 days of purchase.'
+ },
+ {
+ question: 'What payment methods do you accept?',
+ answer: 'We accept all major credit cards (Visa, MasterCard, American Express), PayPal, and bank transfers for Enterprise plans.'
+ },
+ {
+ question: 'Is there a setup fee?',
+ answer: 'No setup fees ever. You only pay the monthly subscription price for the plan you choose.'
+ },
+ {
+ question: 'Can I get a custom plan?',
+ answer: 'Absolutely! If our standard plans don\'t fit your needs, contact our sales team for a custom quote tailored to your requirements.'
+ }
+];
+
+export default Pricing;
diff --git a/client/src/pages/Privacy.jsx b/client/src/pages/Privacy.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..77a3625cd41387a89ee8dc633761af44cf796f01
--- /dev/null
+++ b/client/src/pages/Privacy.jsx
@@ -0,0 +1,171 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+
+const Privacy = () => {
+ return (
+
+
+
+
+
+ Privacy Policy
+
+
+ Last updated: December 7, 2024
+
+
+
+
+
+
+
+
+
+
1. Information We Collect
+
+ We collect information that you provide directly to us, including:
+
+
+ - Name, email address, and contact information
+ - Website URL and business information
+ - Chat conversations and customer interactions
+ - Usage data and analytics
+ - Payment and billing information
+
+
+
+
+
2. How We Use Your Information
+
+ We use the information we collect to:
+
+
+ - Provide, maintain, and improve our services
+ - Process transactions and send related information
+ - Send technical notices and support messages
+ - Respond to your comments and questions
+ - Analyze usage patterns and optimize performance
+ - Detect, prevent, and address fraud and security issues
+
+
+
+
+
3. Data Security
+
+ We implement industry-standard security measures to protect your data:
+
+
+ - End-to-end encryption for all chat data
+ - SOC 2 Type II compliance
+ - Regular security audits and penetration testing
+ - GDPR and CCPA compliant data handling
+ - Secure data centers with 24/7 monitoring
+
+
+
+
+
4. Data Sharing
+
+ We do not sell your personal information. We may share your information only in the following circumstances:
+
+
+ - With your explicit consent
+ - With service providers who assist in our operations
+ - To comply with legal obligations
+ - To protect our rights and prevent fraud
+
+
+
+
+
5. Your Rights
+
+ You have the right to:
+
+
+ - Access your personal data
+ - Correct inaccurate data
+ - Request deletion of your data
+ - Export your data
+ - Opt-out of marketing communications
+ - Withdraw consent at any time
+
+
+
+
+
6. Cookies and Tracking
+
+ We use cookies and similar technologies to:
+
+
+ - Maintain your session and preferences
+ - Understand how you use our service
+ - Improve user experience
+ - Provide personalized content
+
+
+ You can control cookies through your browser settings.
+
+
+
+
+
7. Data Retention
+
+ We retain your information for as long as your account is active or as needed to provide services. We may retain certain information after account closure for:
+
+
+ - Legal compliance
+ - Dispute resolution
+ - Fraud prevention
+ - Enforcing our agreements
+
+
+
+
+
8. Children's Privacy
+
+ Our service is not intended for children under 13. We do not knowingly collect information from children under 13.
+
+
+
+
+
9. International Data Transfers
+
+ Your information may be transferred to and processed in countries other than your country of residence. We ensure appropriate safeguards are in place.
+
+
+
+
+
10. Changes to This Policy
+
+ We may update this privacy policy from time to time. We will notify you of any changes by posting the new policy on this page and updating the "Last updated" date.
+
+
+
+
+
Contact Us
+
+ If you have questions about this Privacy Policy, please contact us at:
+
+
+
Email: privacy@customeragent.com
+
Address: 123 Tech Street, San Francisco, CA 94105
+
+
+
+
+
+
+ );
+};
+
+export default Privacy;
diff --git a/client/src/pages/Register.jsx b/client/src/pages/Register.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..209e4826d70b1cb4ef102a9340c53b63a80d61ee
--- /dev/null
+++ b/client/src/pages/Register.jsx
@@ -0,0 +1,268 @@
+import React, { useState } from 'react';
+import { Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '../context/AuthContext';
+import { Mail, Lock, ArrowRight, Loader2, UserPlus, Check, X, AlertCircle, Eye, EyeOff } from 'lucide-react';
+import { motion } from 'framer-motion';
+import { GoogleLogin } from '@react-oauth/google';
+
+const Register = () => {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const [error, setError] = useState('');
+ const { register, googleLogin, loading } = useAuth();
+ const navigate = useNavigate();
+
+ const handleGoogleSuccess = async (credentialResponse) => {
+ try {
+ const data = await googleLogin(credentialResponse.credential);
+ if (data.is_new) {
+ navigate('/onboarding-success');
+ } else {
+ navigate('/websites');
+ }
+ } catch (err) {
+ setError(err || 'Google registration failed');
+ }
+ };
+
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ const isEmailValid = email && emailRegex.test(email);
+ const isEmailEmpty = !email;
+
+ const passwordMinLength = password.length >= 8;
+ const passwordHasUppercase = /[A-Z]/.test(password);
+ const passwordHasLowercase = /[a-z]/.test(password);
+ const passwordHasNumber = /\d/.test(password);
+ const isPasswordStrong = passwordMinLength && passwordHasUppercase && passwordHasLowercase && passwordHasNumber;
+
+ const passwordsMatch = password && confirmPassword && password === confirmPassword;
+ const passwordsEmpty = !password || !confirmPassword;
+
+ const isFormValid = isEmailValid && isPasswordStrong && passwordsMatch;
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ if (!isEmailValid) {
+ setError('Please enter a valid email address');
+ return;
+ }
+
+ if (!isPasswordStrong) {
+ setError('Password must be at least 8 characters with uppercase, lowercase, and number');
+ return;
+ }
+
+ if (password !== confirmPassword) {
+ setError('Passwords do not match');
+ return;
+ }
+
+ try {
+ await register(email, password);
+ // Automatically log them in or redirect to success page
+ navigate('/onboarding-success');
+ } catch (error) {
+ setError('Registration failed. Please try again.');
+ }
+ };
+
+ return (
+
+ {/* Background Elements */}
+
+
+
+
+
+
+
+
+
+
+
Create account
+
Get started with your free account today
+
+
+
+
+
+ Already have an account?{' '}
+
+ Sign in
+
+
+
+
+
+ );
+};
+
+export default Register;
diff --git a/client/src/pages/Security.jsx b/client/src/pages/Security.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b20c5ab3e743ff92d8cd11997a6fe1bbbf138e83
--- /dev/null
+++ b/client/src/pages/Security.jsx
@@ -0,0 +1,148 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { Shield, Lock, Server, Eye, CheckCircle } from 'lucide-react';
+
+const Security = () => {
+ return (
+
+
+
+
+
+ Security at CustomerAgent
+
+
+ Enterprise-grade security to protect your data and your customers.
+
+
+
+
+
+
+
+
+ {securityFeatures.map((feature, index) => (
+
+
+
+
+ {feature.title}
+
+ {feature.description}
+
+
+ ))}
+
+
+
+
+
Compliance & Certifications
+
+
+
+
+
SOC 2 Type II
+
+ We are SOC 2 Type II compliant, demonstrating our commitment to the highest standards of security, availability, and confidentiality.
+
+
+
+
+
+
+
GDPR Compliant
+
+ We are fully compliant with GDPR regulations, ensuring the protection of personal data for our European users.
+
+
+
+
+
+
+
CCPA Compliant
+
+ We adhere to the California Consumer Privacy Act (CCPA) to protect the privacy rights of California residents.
+
+
+
+
+
+
+
ISO 27001
+
+ Our security management systems are aligned with ISO 27001 standards for information security.
+
+
+
+
+
+
+
+
+
Report a Vulnerability
+
+ We take security seriously. If you believe you have found a security vulnerability in our service, please let us know immediately.
+
+
+ Contact Security Team
+
+
+
+
+
+ );
+};
+
+const securityFeatures = [
+ {
+ icon: Lock,
+ title: 'Data Encryption',
+ description: 'All data is encrypted in transit using TLS 1.2+ and at rest using AES-256 encryption standards.'
+ },
+ {
+ icon: Shield,
+ title: 'Access Control',
+ description: 'Strict role-based access control (RBAC) and multi-factor authentication (MFA) ensure only authorized personnel can access data.'
+ },
+ {
+ icon: Server,
+ title: 'Secure Infrastructure',
+ description: 'Hosted on top-tier cloud providers with 24/7 monitoring, automated backups, and disaster recovery plans.'
+ },
+ {
+ icon: Eye,
+ title: 'Continuous Monitoring',
+ description: 'Real-time threat detection and automated security scanning to identify and mitigate potential risks.'
+ },
+ {
+ icon: CheckCircle,
+ title: 'Regular Audits',
+ description: 'We conduct regular third-party security audits and penetration testing to validate our security posture.'
+ },
+ {
+ icon: Lock,
+ title: 'Privacy First',
+ description: 'Designed with privacy by default. We never sell your data and give you full control over your information.'
+ }
+];
+
+export default Security;
diff --git a/client/src/pages/Settings.jsx b/client/src/pages/Settings.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..ddb264a3d690d6c0b5992f9174b6994f7e4310a1
--- /dev/null
+++ b/client/src/pages/Settings.jsx
@@ -0,0 +1,214 @@
+import React, { useState, useEffect } from 'react';
+import { motion } from 'framer-motion';
+import { useAuth } from '../context/AuthContext';
+import { Save, User, Bell, Shield, Building, Mail, MessageSquare, Loader } from 'lucide-react';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+const SettingsSection = ({ title, icon: Icon, children }) => (
+
+);
+
+const Settings = () => {
+ const { user } = useAuth();
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [settings, setSettings] = useState({
+ businessName: '',
+ businessEmail: '',
+ aiTone: 'friendly',
+ notifications: true,
+ autoResponse: true
+ });
+
+ useEffect(() => {
+ fetchSettings();
+ }, []);
+
+ const fetchSettings = async () => {
+ try {
+ const response = await api.get('/settings/get');
+ setSettings({
+ businessName: response.data.businessName || '',
+ businessEmail: response.data.businessEmail || '',
+ aiTone: response.data.aiTone || 'friendly',
+ notifications: response.data.notifications !== false,
+ autoResponse: response.data.autoResponse !== false
+ });
+ } catch (error) {
+ console.error('Failed to fetch settings:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSave = async () => {
+ setSaving(true);
+ try {
+ await api.post('/settings/update', {
+ businessName: settings.businessName,
+ businessEmail: settings.businessEmail,
+ aiTone: settings.aiTone,
+ notifications: settings.notifications,
+ autoResponse: settings.autoResponse
+ });
+ toast.success('Settings saved successfully!');
+ await fetchSettings();
+ } catch (error) {
+ console.error('Failed to save settings:', error);
+ toast.error('Failed to save settings');
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
Settings
+
Manage your account and AI agent preferences
+
+
+
+ {/* Business Information */}
+
+
+
+
+
+
+ setSettings({ ...settings, businessName: e.target.value })}
+ className="w-full pl-10 pr-4 py-2.5 bg-secondary-50 border border-secondary-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all outline-none"
+ placeholder="Your Business Name"
+ />
+
+
+
+
+
+
+
+ setSettings({ ...settings, businessEmail: e.target.value })}
+ className="w-full pl-10 pr-4 py-2.5 bg-secondary-50 border border-secondary-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all outline-none"
+ placeholder="contact@yourbusiness.com"
+ />
+
+
+
+
+
+ {/* AI Configuration */}
+
+
+
+
+
+
+
+
+
This determines how your AI agent communicates with customers.
+
+
+
+
+ {/* Notifications */}
+
+
+
+
+
+
+
+
+ {/* Save Button */}
+
+
+ {saving ? (
+
+ ) : (
+
+ )}
+ {saving ? 'Saving...' : 'Save Changes'}
+
+
+
+
+ );
+};
+
+export default Settings;
diff --git a/client/src/pages/Terms.jsx b/client/src/pages/Terms.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..dc8dd9e6ab8d079b93bf8546eaad7495bae13665
--- /dev/null
+++ b/client/src/pages/Terms.jsx
@@ -0,0 +1,198 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+
+const Terms = () => {
+ return (
+
+
+
+
+
+ Terms & Conditions
+
+
+ Last updated: December 7, 2024
+
+
+
+
+
+
+
+
+
+
1. Acceptance of Terms
+
+ By accessing and using Customer Agent, you accept and agree to be bound by these Terms and Conditions. If you disagree with any part of these terms, you may not access the service.
+
+
+
+
+
2. Use License
+
+ We grant you a limited, non-exclusive, non-transferable license to use our service for your business purposes, subject to these terms.
+
+
+ - You may not modify or copy the materials
+ - You may not use the materials for commercial purposes beyond your subscription
+ - You may not attempt to reverse engineer any software
+ - You may not remove any copyright or proprietary notations
+
+
+
+
+
3. User Accounts
+
+ When you create an account, you agree to:
+
+
+ - Provide accurate and complete information
+ - Maintain the security of your account credentials
+ - Promptly update your information if it changes
+ - Accept responsibility for all activities under your account
+ - Notify us immediately of any unauthorized use
+
+
+
+
+
4. Acceptable Use
+
+ You agree not to use the service to:
+
+
+ - Violate any laws or regulations
+ - Infringe on intellectual property rights
+ - Transmit harmful or malicious code
+ - Harass, abuse, or harm others
+ - Spam or send unsolicited messages
+ - Attempt to gain unauthorized access
+ - Interfere with service operation
+
+
+
+
+
5. Payment and Billing
+
+ Subscription terms:
+
+
+ - Fees are billed in advance on a recurring basis
+ - No refunds for partial months
+ - Prices subject to change with 30 days notice
+ - Failed payments may result in service suspension
+ - You must provide current payment information
+
+
+
+
+
6. Service Availability
+
+ We strive for 99.9% uptime but cannot guarantee uninterrupted service. We are not liable for:
+
+
+ - Scheduled maintenance
+ - Third-party service failures
+ - Force majeure events
+ - Network or infrastructure issues
+
+
+
+
+
7. Data and Privacy
+
+ Your use of the service is subject to our Privacy Policy. We will:
+
+
+ - Process your data according to our Privacy Policy
+ - Implement reasonable security measures
+ - Not sell your personal information
+ - Comply with applicable data protection laws
+
+
+
+
+
8. Intellectual Property
+
+ The service and its original content, features, and functionality are owned by Customer Agent and protected by international copyright, trademark, and other intellectual property laws.
+
+
+
+
+
9. Termination
+
+ We may terminate or suspend your account immediately if you:
+
+
+ - Breach these Terms
+ - Engage in fraudulent activity
+ - Violate applicable laws
+ - Harm our service or users
+
+
+ You may cancel your subscription at any time through your account settings.
+
+
+
+
+
10. Limitation of Liability
+
+ To the maximum extent permitted by law, Customer Agent shall not be liable for any indirect, incidental, special, consequential, or punitive damages resulting from your use or inability to use the service.
+
+
+
+
+
11. Disclaimer of Warranties
+
+ The service is provided "as is" and "as available" without warranties of any kind, either express or implied, including but not limited to merchantability, fitness for a particular purpose, non-infringement, or course of performance.
+
+
+
+
+
12. Governing Law
+
+ These Terms shall be governed by and construed in accordance with the laws of California, United States, without regard to its conflict of law provisions.
+
+
+
+
+
13. Dispute Resolution
+
+ Any disputes arising from these terms will be resolved through binding arbitration in San Francisco, California, except where prohibited by law.
+
+
+
+
+
14. Changes to Terms
+
+ We reserve the right to modify these terms at any time. We will notify you of any changes by posting the new terms and updating the "Last updated" date. Your continued use after changes constitutes acceptance.
+
+
+
+
+
Contact Us
+
+ Questions about these Terms? Contact us at:
+
+
+
Email: legal@customeragent.com
+
Address: 123 Tech Street, San Francisco, CA 94105
+
+
+
+
+
+
+ );
+};
+
+export default Terms;
diff --git a/client/src/pages/UnansweredQuestions.jsx b/client/src/pages/UnansweredQuestions.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..18604b06e8018545e455bffbe0eacd1b9bc36eaa
--- /dev/null
+++ b/client/src/pages/UnansweredQuestions.jsx
@@ -0,0 +1,329 @@
+import React, { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { MessageSquare, AlertCircle, CheckCircle, Trash2, Send, HelpCircle, ChevronDown, ChevronUp, Search, X } from 'lucide-react';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+
+const StatCard = ({ title, value, icon: Icon, color }) => (
+
+);
+
+const UnansweredQuestions = () => {
+ const [questions, setQuestions] = useState([]);
+ const [stats, setStats] = useState({ total: 0, unresolved: 0, low_confidence: 0 });
+ const [loading, setLoading] = useState(true);
+ const [selectedQuestion, setSelectedQuestion] = useState(null);
+ const [manualAnswer, setManualAnswer] = useState('');
+ const [expandedId, setExpandedId] = useState(null);
+ const [showResolved, setShowResolved] = useState(false);
+
+ useEffect(() => {
+ fetchQuestions();
+ fetchStats();
+ }, [showResolved]);
+
+ const fetchQuestions = async () => {
+ try {
+ const response = await api.get('/unanswered-questions/', {
+ params: { include_resolved: showResolved }
+ });
+ setQuestions(response.data);
+ } catch (error) {
+ toast.error('Failed to fetch questions');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const fetchStats = async () => {
+ try {
+ const response = await api.get('/unanswered-questions/stats');
+ setStats(response.data);
+ } catch (error) {
+ console.error('Failed to fetch stats');
+ }
+ };
+
+ const handleAnswerQuestion = async (questionId) => {
+ if (!manualAnswer.trim()) {
+ toast.error('Please enter an answer');
+ return;
+ }
+
+ try {
+ await api.put(`/unanswered-questions/${questionId}/answer`, {
+ manual_answer: manualAnswer
+ });
+ toast.success('Answer saved successfully');
+ setSelectedQuestion(null);
+ setManualAnswer('');
+ fetchQuestions();
+ fetchStats();
+ } catch (error) {
+ toast.error('Failed to save answer');
+ }
+ };
+
+ const handleDeleteQuestion = async (questionId) => {
+ if (!confirm('Are you sure you want to delete this question?')) return;
+
+ try {
+ await api.delete(`/unanswered-questions/${questionId}`);
+ toast.success('Question deleted');
+ fetchQuestions();
+ fetchStats();
+ } catch (error) {
+ toast.error('Failed to delete question');
+ }
+ };
+
+ const getConfidenceColor = (score) => {
+ if (score < 0.3) return 'text-red-600 bg-red-50 border-red-100';
+ if (score < 0.6) return 'text-yellow-600 bg-yellow-50 border-yellow-100';
+ return 'text-green-600 bg-green-50 border-green-100';
+ };
+
+ if (loading) {
+ return (
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
+ );
+ }
+
+ return (
+
+
+
Q&A Review
+
Review and improve AI responses to customer questions
+
+
+ {/* Stats Cards */}
+
+
+
+
+
+
+ {/* Questions List */}
+
+
+
Questions Needing Review
+
+
+
+ {questions.length === 0 ? (
+
+
+
+
+
All Caught Up!
+
No unanswered questions requiring your attention.
+
+ ) : (
+
+ {questions.map((question) => (
+
+
+
+
+
+
+ {Math.round(question.confidence_score * 100)}% confidence
+
+
+ {new Date(question.created_at).toLocaleDateString()}
+
+ {question.is_resolved && (
+
+ Resolved
+
+ )}
+
+
+
+ {question.question}
+
+
+
+ {question.website_name}
+ •
+ {question.visitor_name || 'Anonymous Visitor'}
+
+
+
+
+ {(!question.is_resolved || showResolved) && (
+
+ )}
+
+
+
+
+
+
+ {expandedId === question.id && (
+
+
+
+
Current AI Response
+
{question.ai_response}
+
+
+ {question.manual_answer && (
+
+
Manual Answer
+
{question.manual_answer}
+
+ )}
+
+
+ )}
+
+
+
+ ))}
+
+ )}
+
+
+ {/* Answer Modal */}
+
+ {selectedQuestion && (
+
+
+
+
Provide Manual Answer
+
+
+
+
+
+
+
+ {selectedQuestion.question}
+
+
+
+
+
+
+ {selectedQuestion.ai_response}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default UnansweredQuestions;
\ No newline at end of file
diff --git a/client/src/pages/UserManagement.jsx b/client/src/pages/UserManagement.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..150e5914c4866a1d407ed8a0017a7b05fbad5234
--- /dev/null
+++ b/client/src/pages/UserManagement.jsx
@@ -0,0 +1,432 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useQuery } from '@tanstack/react-query';
+import api from '../api/axiosConfig';
+import { getLeadProfiles } from '../api/leadsApi';
+import {
+ User,
+ MessageCircle,
+ Search,
+ Globe,
+ ChevronDown,
+ Send,
+ X,
+ MapPin,
+ CheckCheck,
+ Clock,
+ Info,
+ Users
+} from 'lucide-react';
+import toast from 'react-hot-toast';
+
+export default function UserManagement() {
+ const navigate = useNavigate();
+ const [activeTab, setActiveTab] = useState('patients'); // 'patients' or 'anonymous'
+
+ // Patients State
+ const [leads, setLeads] = useState([]);
+ const [leadsLoading, setLeadsLoading] = useState(true);
+ const [leadsSearch, setLeadsSearch] = useState('');
+
+ // Anonymous State
+ const [selectedWebsiteId, setSelectedWebsiteId] = useState(null);
+ const [showDropdown, setShowDropdown] = useState(false);
+ const [sessions, setSessions] = useState([]);
+ const [selectedSession, setSelectedSession] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [newMessage, setNewMessage] = useState('');
+ const [sendingMessage, setSendingMessage] = useState(false);
+ const [anonymousSearch, setAnonymousSearch] = useState('');
+ const messagesEndRef = useRef(null);
+
+ // Fetch Websites for Anonymous Tab
+ const { data: websites, isLoading: websitesLoading } = useQuery({
+ queryKey: ['websites'],
+ queryFn: async () => {
+ const response = await api.get('/websites/');
+ return response.data;
+ },
+ });
+
+ const selectedWebsite = websites?.find(w => w.id === selectedWebsiteId);
+
+ // Initial Data Fetch
+ useEffect(() => {
+ fetchLeads();
+ }, []);
+
+ useEffect(() => {
+ if (activeTab === 'anonymous' && selectedWebsiteId) {
+ fetchSessions();
+ const interval = setInterval(fetchSessions, 10000);
+ return () => clearInterval(interval);
+ }
+ }, [activeTab, selectedWebsiteId, selectedWebsite]);
+
+ useEffect(() => {
+ if (selectedSession) {
+ fetchMessages(selectedSession.id);
+ }
+ }, [selectedSession]);
+
+ useEffect(() => {
+ scrollToBottom();
+ }, [messages]);
+
+ const scrollToBottom = () => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ // Leads Logic
+ const fetchLeads = async () => {
+ try {
+ setLeadsLoading(true);
+ const data = await getLeadProfiles();
+ setLeads(data);
+ } catch (err) {
+ console.error('Error loading leads:', err);
+ toast.error('Failed to load patients');
+ } finally {
+ setLeadsLoading(false);
+ }
+ };
+
+ const filteredLeads = leads.filter(lead =>
+ (lead.email?.toLowerCase().includes(leadsSearch.toLowerCase())) ||
+ (lead.name?.toLowerCase().includes(leadsSearch.toLowerCase()))
+ );
+
+ // Anonymous Logic
+ const fetchSessions = async () => {
+ if (!selectedWebsiteId) return;
+ try {
+ const response = await api.get('/manual-chat/sessions');
+ const filtered = response.data.filter(s => s.website_name === selectedWebsite?.name);
+ setSessions(filtered);
+ } catch (error) {
+ console.error('Failed to fetch sessions:', error);
+ }
+ };
+
+ const fetchMessages = async (sessionId) => {
+ try {
+ const response = await api.get(`/manual-chat/sessions/${sessionId}/messages`);
+ setMessages(response.data);
+ } catch (error) {
+ console.error('Failed to fetch messages:', error);
+ }
+ };
+
+ const sendMessage = async () => {
+ if (!newMessage.trim() || !selectedSession) return;
+ setSendingMessage(true);
+ try {
+ await api.post(`/manual-chat/sessions/${selectedSession.id}/send`, {
+ message: newMessage
+ });
+ setNewMessage('');
+ fetchMessages(selectedSession.id);
+ } catch (error) {
+ toast.error('Failed to send message');
+ } finally {
+ setSendingMessage(false);
+ }
+ };
+
+ const filteredSessions = sessions.filter(session => {
+ const query = anonymousSearch.toLowerCase();
+ const name = session.visitor_name?.toLowerCase() || '';
+ const email = session.visitor_email?.toLowerCase() || '';
+ const ip = session.ip_address?.toLowerCase() || '';
+ return name.includes(query) || email.includes(query) || ip.includes(query);
+ });
+
+ const formatDate = (dateString) => {
+ if (!dateString) return 'Never';
+ const date = new Date(dateString);
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
+ };
+
+ return (
+
+ {/* Header Area */}
+
+
+
User Management
+
Manage physical patients and anonymous visitors
+
+
+ {/* Tab Switcher */}
+
+
+
+
+
+
+ {/* Content Area */}
+
+
+ {activeTab === 'patients' ? (
+
+ {/* Patients Filter */}
+
+
+
+ setLeadsSearch(e.target.value)}
+ className="w-full pl-12 pr-4 py-3 bg-secondary-50 border border-secondary-200 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-primary-500/20 focus:border-primary-500 transition-all"
+ />
+
+
+
+ {leads.length} Total
+
+
+
+ {/* Patients Table */}
+
+ {leadsLoading ? (
+
+ ) : filteredLeads.length === 0 ? (
+
+ ) : (
+
+ {filteredLeads.map((lead) => (
+
navigate(`/leads/${encodeURIComponent(lead.email)}`)}
+ className="group p-4 bg-secondary-50/50 hover:bg-white border border-transparent hover:border-primary-100 rounded-2xl transition-all cursor-pointer flex items-center justify-between"
+ >
+
+
+
+
+
+
{lead.name || 'Anonymous'}
+
{lead.email}
+
+
+
+
+
Sessions
+
+ {lead.sessions || 0}
+
+
+
+
Last Active
+
+
+ {formatDate(lead.last_active)}
+
+
+
+
+ ))}
+
+ )}
+
+
+ ) : (
+
+ {/* Left Sidebar: Sessions */}
+
+
+ {/* Website Selector */}
+
+
+
+ {showDropdown && (
+
+ {websites?.map((website) => (
+
+ ))}
+
+ )}
+
+
+
+
+ setAnonymousSearch(e.target.value)}
+ className="w-full pl-9 pr-4 py-2 bg-white border border-secondary-200 rounded-xl text-sm focus:outline-none"
+ />
+
+
+
+
+ {filteredSessions.map((session) => (
+
+ ))}
+
+
+
+ {/* Right Area: Chat */}
+
+ {selectedSession ? (
+ <>
+
+
+
+
+
+
+
{selectedSession.ip_address}
+
+
+ Active Session
+
+
+
+
+
+
+
+ {messages.map((msg) => (
+
+
+ {msg.message}
+
+ {new Date(msg.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+ {!msg.is_from_visitor && }
+
+
+
+ ))}
+
+
+
+
+
+ setNewMessage(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
+ placeholder="Type a reply..."
+ className="flex-1 px-5 py-3 bg-secondary-50 border border-secondary-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-primary-500/20 focus:border-primary-500 transition-all"
+ />
+
+
+
+ >
+ ) : (
+
+
+
+
+
Live Chat Area
+
Select a session to start talking to your visitors
+
+ )}
+
+
+ )}
+
+
+
+ );
+}
diff --git a/client/src/pages/WebsiteManagement.jsx b/client/src/pages/WebsiteManagement.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..70c0f286b85eb6ba2f821d2c5872c15b76c60e98
--- /dev/null
+++ b/client/src/pages/WebsiteManagement.jsx
@@ -0,0 +1,557 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { Globe, Trash2, Plus, ExternalLink, Copy, Check, Code, X } from 'lucide-react';
+import api from '../api/axiosConfig';
+import toast from 'react-hot-toast';
+import VerifyButton from '../components/Dashboard/VerifyButton';
+import HealthcareConfigModal from '../components/Website/HealthcareConfigModal';
+import { useWebsites } from '../context/WebsiteContext';
+import { Settings, Loader2 } from 'lucide-react';
+
+export default function WebsiteManagement() {
+ const { websites, refreshWebsites } = useWebsites();
+ const [showAddModal, setShowAddModal] = useState(false);
+ const [showIntegrationModal, setShowIntegrationModal] = useState(false);
+ const [selectedWebsiteForIntegration, setSelectedWebsiteForIntegration] = useState(null);
+ const [integrationTab, setIntegrationTab] = useState('html');
+ const [newWebsite, setNewWebsite] = useState({ name: '', url: '', industry: 'healthcare', tone: 'friendly' });
+ const [loading, setLoading] = useState(false);
+ const [copied, setCopied] = useState(false);
+ const [showHealthcareModal, setShowHealthcareModal] = useState(false);
+ const [selectedWebsiteForHealthcare, setSelectedWebsiteForHealthcare] = useState(null);
+ const [fetchingMetadata, setFetchingMetadata] = useState(false);
+ const metadataTimerRef = useRef(null);
+
+ const getNameFromUrl = (url) => {
+ try {
+ // Remove protocol and www
+ let domain = url.replace(/^(https?:\/\/)?(www\.)?/, '');
+ // Get the first part of the domain
+ domain = domain.split('.')[0];
+ // Capitalize and remove special characters
+ return domain.charAt(0).toUpperCase() + domain.slice(1).replace(/[-_]/g, ' ');
+ } catch (e) {
+ return '';
+ }
+ };
+
+ const fetchMetadata = async (url) => {
+ if (!url || !url.includes('.')) return;
+ setFetchingMetadata(true);
+ try {
+ const response = await api.get(`/metadata/fetch?url=${encodeURIComponent(url)}`);
+ if (response.data.title) {
+ setNewWebsite(prev => ({
+ ...prev,
+ name: prev.name && prev.name !== getNameFromUrl(prev.url) ? prev.name : response.data.title
+ }));
+ }
+ } catch (error) {
+ console.error('Failed to fetch metadata', error);
+ } finally {
+ setFetchingMetadata(false);
+ }
+ };
+
+ const handleUrlChange = (url) => {
+ const generatedName = getNameFromUrl(url);
+ setNewWebsite(prev => {
+ const prevGeneratedName = getNameFromUrl(prev.url);
+ const shouldUpdateName = !prev.name || prev.name === prevGeneratedName;
+
+ return {
+ ...prev,
+ url,
+ name: shouldUpdateName ? generatedName : prev.name
+ };
+ });
+
+ if (metadataTimerRef.current) {
+ clearTimeout(metadataTimerRef.current);
+ }
+ metadataTimerRef.current = setTimeout(() => fetchMetadata(url), 1000);
+ };
+
+ const handleAddWebsite = async () => {
+ if (!newWebsite.name || !newWebsite.url) {
+ toast.error('Please fill in all fields');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ await api.post('/websites/', newWebsite);
+ toast.success('Website added successfully!');
+ setShowAddModal(false);
+ setNewWebsite({ name: '', url: '', industry: 'healthcare', tone: 'friendly' });
+ refreshWebsites();
+ } catch (error) {
+ toast.error(error.response?.data?.detail || 'Failed to add website');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleDeleteWebsite = async (websiteId, websiteName) => {
+ if (!confirm(`Are you sure you want to delete "${websiteName}"? This will delete all associated chat history and content.`)) {
+ return;
+ }
+
+ try {
+ await api.delete(`/websites/${websiteId}`);
+ toast.success('Website deleted successfully');
+ refreshWebsites();
+ } catch (error) {
+ toast.error(error.response?.data?.detail || 'Failed to delete website');
+ }
+ };
+
+ const handleShowIntegration = (website) => {
+ setSelectedWebsiteForIntegration(website);
+ setShowIntegrationModal(true);
+ setIntegrationTab('html');
+ setCopied(false);
+ };
+
+ const handleShowHealthcareConfig = (website) => {
+ setSelectedWebsiteForHealthcare(website);
+ setShowHealthcareModal(true);
+ };
+
+ const getIntegrationCode = (website, type) => {
+ if (!website) return '';
+
+ const apiUrl = "http://localhost:8000";
+ const widgetUrl = `${apiUrl}/static/widget.js`;
+
+ // Default config
+ const config = {
+ theme: "orange",
+ position: "bottom-right",
+ size: "medium",
+ primaryColor: "#3B82F6",
+ textColor: "#FFFFFF",
+ backgroundColor: "#FFFFFF"
+ };
+
+ switch (type) {
+ case 'html':
+ return `
+`;
+
+ case 'react':
+ return `import { useEffect } from 'react';
+
+// Inside your component
+useEffect(() => {
+ const script = document.createElement('script');
+ script.src = "${widgetUrl}";
+ script.async = true;
+ script.onload = () => {
+ if (window.CustomerAgentWidget) {
+ window.CustomerAgentWidget.init({
+ websiteId: ${website.id},
+ apiUrl: "${apiUrl}"
+ });
+ }
+ };
+ document.body.appendChild(script);
+
+ return () => {
+ document.body.removeChild(script);
+ };
+}, []);`;
+
+ case 'next':
+ return `import Script from 'next/script';
+
+// Inside your layout or page
+
+
+