'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 (
onTabChange(tab.id)}
className={`flex items-center gap-2 px-4 py-2.5 rounded-xl font-medium transition-all ${
isActive
? 'bg-primary-600 text-white shadow-[4px_4px_8px_rgba(0,0,0,0.15),-2px_-2px_6px_rgba(255,255,255,0.8)]'
: 'bg-white text-slate-600 hover:bg-slate-100 shadow-[4px_4px_8px_rgba(0,0,0,0.1),-2px_-2px_6px_rgba(255,255,255,0.9)]'
}`}
>
{tab.label}
);
})}
);
}
// =============================================================================
// 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 ? (
) : (
{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 */}
setFilterType('all')}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all ${
filterType === 'all'
? 'bg-primary-600 text-white'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
All
setFilterType('highGCI')}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-1 ${
filterType === 'highGCI'
? 'bg-yellow-500 text-white'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
High GCI
setFilterType('incompleteSetup')}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-1 ${
filterType === 'incompleteSetup'
? 'bg-red-500 text-white'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Incomplete
setFilterType('completeSetup')}
className={`px-4 py-2 rounded-xl text-sm font-medium transition-all flex items-center gap-1 ${
filterType === 'completeSetup'
? 'bg-green-500 text-white'
: 'bg-slate-100 text-slate-600 hover:bg-slate-200'
}`}
>
Complete
Refresh
{/* Users Table */}
{isLoading ? (
{[1, 2, 3, 4, 5].map((i) => (
))}
) : (
handleSort('name')}
>
Name
handleSort('email')}
>
Email
handleSort('brokerage')}
>
Brokerage
handleSort('gci')}
>
GCI Range
handleSort('setupCompletion')}
>
Setup Status
handleSort('createdAt')}
>
Joined
{filteredAndSortedUsers.map((user) => {
const userIsHighGCI = isHighGCI(user.gciRange);
const userHasIncompleteSetup = hasIncompleteSetup(user.setupStatus);
return (
{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) => (
);
const renderTextInput = (key: string, label: string, placeholder: string) => (
{label}
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
{testingGHL ? (
) : ghlStatus === 'success' ? (
) : ghlStatus === 'error' ? (
) : null}
Test Connection
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
{testingStripe ? (
) : stripeStatus === 'success' ? (
) : stripeStatus === 'error' ? (
) : null}
Test Connection
{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 */}
{saving ? (
) : (
)}
Save Settings
);
}
// =============================================================================
// 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' && (
)}
);
}