'use client'; import React, { useState, useEffect, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import { Shield, Users, Settings, BarChart2, DollarSign, AlertTriangle, Clock, Search, ChevronUp, ChevronDown, CheckCircle, XCircle, Bell, Eye, EyeOff, Check, X, Loader2, Save, RefreshCw, UserPlus, } from 'lucide-react'; import { api } from '@/lib/api/client'; import { Role, AdminDashboardStats, AdminUserView, SystemSettings } from '@/types'; import { ProtectedRoute } from '@/components/auth/ProtectedRoute'; import { PERMISSIONS } from '@/lib/auth/roles'; // ============================================================================= // Types // ============================================================================= type TabType = 'overview' | 'users' | 'settings'; type SortField = 'name' | 'email' | 'brokerage' | 'gci' | 'createdAt' | 'setupCompletion'; type SortDirection = 'asc' | 'desc'; type FilterType = 'all' | 'highGCI' | 'incompleteSetup' | 'completeSetup' | 'pendingDFY'; interface RecentSignup { id: string; firstName: string; lastName: string; email: string; createdAt: Date; gciRange?: string; } // ============================================================================= // Helper Functions // ============================================================================= const parseGCI = (gciString?: string): number => { if (!gciString) return 0; const cleaned = gciString.replace(/[$,]/g, ''); return parseFloat(cleaned) || 0; }; const isHighGCI = (gciRange?: string): boolean => { return parseGCI(gciRange) >= 100000; }; const getSetupCompletionCount = (status?: AdminUserView['setupStatus']): number => { if (!status) return 0; return [ status.smsConfigured, status.emailConfigured, status.contactsImported, status.campaignsSetup ].filter(Boolean).length; }; const hasIncompleteSetup = (status?: AdminUserView['setupStatus']): boolean => { return getSetupCompletionCount(status) < 4; }; // ============================================================================= // Tab Navigation Component // ============================================================================= function TabNavigation({ activeTab, onTabChange }: { activeTab: TabType; onTabChange: (tab: TabType) => void; }) { const tabs = [ { id: 'overview' as TabType, label: 'Overview', icon: BarChart2 }, { id: 'users' as TabType, label: 'Users', icon: Users }, { id: 'settings' as TabType, label: 'Settings', icon: Settings }, ]; return (
{tabs.map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; return ( ); })}
); } // ============================================================================= // Stats Card Component // ============================================================================= function StatCard({ icon, label, value, iconBgClass = 'bg-primary-100', iconColorClass = 'text-primary-600', isLoading = false }: { icon: React.ReactNode; label: string; value: string | number; iconBgClass?: string; iconColorClass?: string; isLoading?: boolean; }) { return (
{icon}

{label}

{isLoading ? (
) : (

{value}

)}
); } // ============================================================================= // Overview Tab Component // ============================================================================= function OverviewTab({ stats, recentSignups, isLoading }: { stats: AdminDashboardStats | null; recentSignups: RecentSignup[]; isLoading: boolean; }) { return (
{/* Stats Grid */}
} label="Total Users" value={stats?.totalUsers ?? 0} iconBgClass="bg-primary-100" iconColorClass="text-primary-600" isLoading={isLoading} /> } label="High GCI Users ($100K+)" value={stats?.highGCIUsers ?? 0} iconBgClass="bg-yellow-100" iconColorClass="text-yellow-600" isLoading={isLoading} /> } label="Incomplete Setup" value={stats?.incompleteSetup ?? 0} iconBgClass="bg-red-100" iconColorClass="text-red-600" isLoading={isLoading} /> } label="Pending DFY" value={stats?.dfyRequestsPending ?? 0} iconBgClass="bg-purple-100" iconColorClass="text-purple-600" isLoading={isLoading} />
{/* Recent Signups */}

Recent Signups

Last 7 days
{isLoading ? (
{[1, 2, 3].map((i) => (
))}
) : recentSignups.length === 0 ? (

No recent signups

) : (
{recentSignups.map((user) => (
{user.firstName?.[0]?.toUpperCase() || user.email[0].toUpperCase()}

{user.firstName} {user.lastName}

{user.email}

{new Date(user.createdAt).toLocaleDateString()}

{user.gciRange && isHighGCI(user.gciRange) && ( High GCI )}
))}
)}
); } // ============================================================================= // Users Tab Component // ============================================================================= function UsersTab({ users, isLoading, onRefresh }: { users: AdminUserView[]; isLoading: boolean; onRefresh: () => void; }) { const [searchQuery, setSearchQuery] = useState(''); const [sortField, setSortField] = useState('createdAt'); const [sortDirection, setSortDirection] = useState('desc'); const [filterType, setFilterType] = useState('all'); const handleSort = (field: SortField) => { if (sortField === field) { setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc')); } else { setSortField(field); setSortDirection('asc'); } }; const filteredAndSortedUsers = useMemo(() => { let result = [...users]; // Apply search filter if (searchQuery) { const query = searchQuery.toLowerCase(); result = result.filter( (user) => user.firstName?.toLowerCase().includes(query) || user.lastName?.toLowerCase().includes(query) || user.email.toLowerCase().includes(query) || user.brokerage?.toLowerCase().includes(query) ); } // Apply type filter switch (filterType) { case 'highGCI': result = result.filter((user) => isHighGCI(user.gciRange)); break; case 'incompleteSetup': result = result.filter((user) => hasIncompleteSetup(user.setupStatus)); break; case 'completeSetup': result = result.filter((user) => !hasIncompleteSetup(user.setupStatus)); break; } // Apply sorting result.sort((a, b) => { let comparison = 0; switch (sortField) { case 'name': comparison = `${a.firstName} ${a.lastName}`.localeCompare( `${b.firstName} ${b.lastName}` ); break; case 'email': comparison = a.email.localeCompare(b.email); break; case 'brokerage': comparison = (a.brokerage || '').localeCompare(b.brokerage || ''); break; case 'gci': comparison = parseGCI(a.gciRange) - parseGCI(b.gciRange); break; case 'createdAt': comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); break; case 'setupCompletion': comparison = getSetupCompletionCount(a.setupStatus) - getSetupCompletionCount(b.setupStatus); break; } return sortDirection === 'asc' ? comparison : -comparison; }); return result; }, [users, searchQuery, sortField, sortDirection, filterType]); const SortIcon = ({ field }: { field: SortField }) => { if (sortField !== field) return null; return sortDirection === 'asc' ? ( ) : ( ); }; const StatusBadge = ({ configured, label }: { configured: boolean; label: string; }) => ( {configured ? : } {label} ); return (
{/* Filters and Search */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-3 rounded-xl bg-slate-50 border-none shadow-inner focus:ring-2 focus:ring-primary-500 outline-none" />
{/* Filter Buttons */}
{/* Users Table */}
{isLoading ? (
{[1, 2, 3, 4, 5].map((i) => (
))}
) : (
{filteredAndSortedUsers.map((user) => { const userIsHighGCI = isHighGCI(user.gciRange); const userHasIncompleteSetup = hasIncompleteSetup(user.setupStatus); return ( ); })}
handleSort('name')} > Name handleSort('email')} > Email handleSort('brokerage')} > Brokerage handleSort('gci')} > GCI Range handleSort('setupCompletion')} > Setup Status handleSort('createdAt')} > Joined
{user.firstName?.[0]?.toUpperCase() || user.email[0].toUpperCase()}
{user.firstName} {user.lastName} {userIsHighGCI && ( High GCI )}
{user.email} {user.brokerage || ( Not set )} {user.gciRange || 'N/A'}
{new Date(user.createdAt).toLocaleDateString()}
{filteredAndSortedUsers.length === 0 && (

No users found

Try adjusting your search or filter criteria

)}
)}
{/* Results Summary */}
Showing {filteredAndSortedUsers.length} of {users.length} users
); } // ============================================================================= // Settings Tab Component // ============================================================================= function SettingsTab({ initialSettings, onSave, onTestGHL, onTestStripe, isLoading }: { initialSettings: Record; onSave: (settings: Record) => Promise; onTestGHL: () => Promise<{ success: boolean; error?: string }>; onTestStripe: () => Promise<{ success: boolean; error?: string }>; isLoading: boolean; }) { const [settings, setSettings] = useState(initialSettings); const [showSecrets, setShowSecrets] = useState>({}); const [saving, setSaving] = useState(false); const [testingGHL, setTestingGHL] = useState(false); const [testingStripe, setTestingStripe] = useState(false); const [ghlStatus, setGhlStatus] = useState<'idle' | 'success' | 'error'>('idle'); const [stripeStatus, setStripeStatus] = useState<'idle' | 'success' | 'error'>('idle'); useEffect(() => { setSettings(initialSettings); }, [initialSettings]); const handleChange = (key: string, value: string) => { setSettings((prev) => ({ ...prev, [key]: value })); }; const handleSave = async () => { setSaving(true); try { await onSave(settings); } finally { setSaving(false); } }; const handleTestGHL = async () => { setTestingGHL(true); setGhlStatus('idle'); try { const result = await onTestGHL(); setGhlStatus(result.success ? 'success' : 'error'); } finally { setTestingGHL(false); } }; const handleTestStripe = async () => { setTestingStripe(true); setStripeStatus('idle'); try { const result = await onTestStripe(); setStripeStatus(result.success ? 'success' : 'error'); } finally { setTestingStripe(false); } }; const toggleSecret = (key: string) => { setShowSecrets((prev) => ({ ...prev, [key]: !prev[key] })); }; const renderSecretInput = (key: string, label: string, placeholder: string) => (
handleChange(key, e.target.value)} placeholder={placeholder} className="w-full p-3 pr-10 rounded-xl bg-slate-50 border-none shadow-inner focus:ring-2 focus:ring-primary-500 outline-none" />
); const renderTextInput = (key: string, label: string, placeholder: string) => (
handleChange(key, e.target.value)} placeholder={placeholder} className="w-full p-3 rounded-xl bg-slate-50 border-none shadow-inner focus:ring-2 focus:ring-primary-500 outline-none" />
); if (isLoading) { return (
{[1, 2, 3].map((i) => (
))}
); } return (
{/* GHL Configuration */}

GoHighLevel Configuration

Configure your GHL Private Integration OAuth credentials. Create a Private Integration in GHL under Settings → Integrations → Private Integrations.

{renderTextInput( 'ghlClientId', 'Client ID', 'OAuth Client ID from Private Integration' )} {renderSecretInput( 'ghlClientSecret', 'Client Secret', 'OAuth Client Secret from Private Integration' )} {renderSecretInput( 'ghlAccessToken', 'Access Token', 'OAuth Access Token (from authorization)' )} {renderSecretInput( 'ghlRefreshToken', 'Refresh Token', 'OAuth Refresh Token (for auto-renewal)' )} {renderTextInput( 'ghlLocationId', 'Location ID', 'Your GHL Location/Sub-account ID' )} {renderSecretInput( 'ghlWebhookSecret', 'Webhook Secret', 'Secret for validating webhooks (optional)' )}
{/* Legacy/Agency Settings (collapsible or secondary) */}
Agency Settings (for user provisioning)
{renderSecretInput( 'ghlAgencyApiKey', 'Agency API Key', 'For creating sub-accounts' )} {renderTextInput('ghlAgencyId', 'Agency ID', 'Your GHL Agency/Company ID')}
{/* Tag Configuration */}

Tag Configuration

These tags will be applied to contacts in the owner's GHL account to trigger automations.

{renderTextInput('tagHighGCI', 'High GCI Tag', 'e.g., high-gci-lead')} {renderTextInput( 'tagOnboardingComplete', 'Onboarding Complete Tag', 'e.g., onboarding-done' )} {renderTextInput( 'tagDFYRequested', 'DFY Requested Tag', 'e.g., dfy-requested' )}
{/* Stripe Configuration */}

Stripe Configuration

{renderSecretInput( 'stripeSecretKey', 'Stripe Secret Key', 'sk_live_... or sk_test_...' )} {renderSecretInput( 'stripeWebhookSecret', 'Stripe Webhook Secret', 'whsec_...' )}
{/* DFY Pricing Configuration */}

DFY Pricing (in cents)

{renderTextInput( 'dfyPriceFullSetup', 'Full Setup Price', 'e.g., 29700 for $297' )} {renderTextInput( 'dfyPriceSmsSetup', 'SMS Setup Price', 'e.g., 9900 for $99' )} {renderTextInput( 'dfyPriceEmailSetup', 'Email Setup Price', 'e.g., 9900 for $99' )}
{/* Calendly Links */}

Calendly Links

{renderTextInput( 'calendlyCoachingLink', 'Coaching Calendly Link', 'https://calendly.com/...' )} {renderTextInput( 'calendlyTeamLink', 'Join Team Calendly Link', 'https://calendly.com/...' )}
{/* Notifications */}

Notifications

{renderTextInput( 'notificationEmail', 'Notification Email', 'Email for high-GCI alerts' )} {renderTextInput( 'slackWebhookUrl', 'Slack Webhook URL', 'Optional: Slack notifications' )}
{/* ClickUp Integration */}

ClickUp Integration (for DFY tasks)

{renderSecretInput( 'clickupApiKey', 'ClickUp API Key', 'Enter your ClickUp API key' )} {renderTextInput('clickupListId', 'ClickUp List ID', 'List ID for DFY tasks')}
{/* AI Configuration */}

AI Configuration

Configure AI service API keys for intelligent features and automations.

{renderSecretInput( 'claudeApiKey', 'Claude API Key', 'sk-ant-... (Anthropic API key)' )} {renderSecretInput( 'openaiApiKey', 'OpenAI API Key', 'sk-... (OpenAI API key)' )} {renderTextInput( 'mcpServerUrl', 'MCP Server URL', 'https://your-mcp-server.com' )}
{/* Save Button */}
); } // ============================================================================= // Main Admin Page Component // ============================================================================= export default function AdminPage() { const router = useRouter(); const [activeTab, setActiveTab] = useState('overview'); const [isLoading, setIsLoading] = useState(true); // Data states const [stats, setStats] = useState(null); const [users, setUsers] = useState([]); const [recentSignups, setRecentSignups] = useState([]); const [settings, setSettings] = useState>({}); // Fetch data useEffect(() => { const fetchData = async () => { setIsLoading(true); try { const [statsResponse, usersResponse, settingsResponse] = await Promise.all([ api.admin.getStats(), api.admin.getUsers({ limit: 100 }), api.admin.getSettings(), ]); if (statsResponse?.stats) { setStats(statsResponse.stats); } if (usersResponse?.users) { setUsers(usersResponse.users); // Extract recent signups (last 7 days) const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); const recent = usersResponse.users .filter((u: AdminUserView) => new Date(u.createdAt) >= sevenDaysAgo) .sort((a: AdminUserView, b: AdminUserView) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ) .slice(0, 5) .map((u: AdminUserView) => ({ id: u.id, firstName: u.firstName || '', lastName: u.lastName || '', email: u.email, createdAt: u.createdAt, gciRange: u.gciRange, })); setRecentSignups(recent); } if (settingsResponse?.settings) { // Convert settings to string record for form const settingsRecord: Record = {}; Object.entries(settingsResponse.settings).forEach(([key, value]) => { settingsRecord[key] = value?.toString() || ''; }); setSettings(settingsRecord); } } catch (error) { console.error('Error fetching admin data:', error); } finally { setIsLoading(false); } }; fetchData(); }, []); const handleSaveSettings = async (newSettings: Record) => { await api.admin.updateSettings(newSettings); setSettings(newSettings); }; const handleTestGHL = async () => { return api.admin.testConnection('ghl'); }; const handleTestStripe = async () => { return api.admin.testConnection('stripe'); }; const handleRefreshUsers = async () => { try { const usersResponse = await api.admin.getUsers({ limit: 100 }); if (usersResponse?.users) { setUsers(usersResponse.users); } } catch (error) { console.error('Error refreshing users:', error); } }; return (
{/* Header */}

Admin Settings

System configuration and user management

{/* Tab Navigation */} {/* Tab Content */} {activeTab === 'overview' && ( )} {activeTab === 'users' && ( )} {activeTab === 'settings' && ( )}
); }