import React, { useState, useEffect, useCallback } from 'react';
import { useQuery } from '@tanstack/react-query';
import { partnerApi } from '../../api';
import { AppLayout } from '../../components/layout/AppLayout';
import { StatCard } from '../../components/ui/StatCard';
import { NextMoveWidget } from '../../components/ui/NextMoveWidget';
import { PageLoader } from '../../components/ui/LoadingSpinner';
import { OnboardingTour } from '../../components/ui/OnboardingTour';
import { Building2, Users, TrendingUp, DollarSign, Plug, Lightbulb, Target, CheckCircle2, Circle } from 'lucide-react';
import type { DashboardStats } from '../../types';
const ONBOARDING_STORAGE_KEY = 'cs_onboarding_complete';
// Empty stats object used as fallback when API call fails (e.g. 403)
const emptyStats: DashboardStats = {
totalOrganizations: 0,
totalUsers: 0,
totalActive: 0,
totalSuspended: 0,
totalRevenue: 0,
avgRevenue: 0,
recentActivity: [],
};
const onboardingTasks = [
{ id: 'profile', label: 'Complete your profile', icon: Users },
{ id: 'integration', label: 'Connect your first integration', icon: Plug },
{ id: 'goals', label: 'Set your revenue goals', icon: Target },
{ id: 'coaching', label: 'Explore Smart Coaching tips', icon: Lightbulb },
];
export function PartnerDashboard() {
const { data: stats, isLoading, isError } = useQuery<DashboardStats>({
queryKey: ['partner-overview'],
queryFn: () => partnerApi.getOverview(),
retry: false,
});
// If the scoped partner endpoint fails, show zeros instead of crashing
const effectiveStats = isError ? emptyStats : stats;
const { data: partner } = useQuery({
queryKey: ['partner-profile'],
queryFn: () => partnerApi.getProfile(),
});
// Onboarding tour state
const [showTour, setShowTour] = useState(false);
const [tourChecked, setTourChecked] = useState(false);
// Check on mount if tour should show
useEffect(() => {
if (tourChecked) return;
setTourChecked(true);
const completed = localStorage.getItem(ONBOARDING_STORAGE_KEY);
if (completed) return;
// Check for manual restart trigger from settings
const tourPending = sessionStorage.getItem('cs_tour_pending');
if (tourPending) {
sessionStorage.removeItem('cs_tour_pending');
setShowTour(true);
return;
}
// Show tour for new users (no organizations or no data yet)
const isNewUser =
effectiveStats?.totalOrganizations === 0 || effectiveStats === undefined;
if (isNewUser && partner?.is_active) {
setShowTour(true);
}
}, [effectiveStats, partner, tourChecked]);
const handleTourComplete = useCallback(() => {
localStorage.setItem(ONBOARDING_STORAGE_KEY, 'true');
setShowTour(false);
}, []);
if (isLoading) {
return (
<AppLayout title="Dashboard">
<PageLoader />
</AppLayout>
);
}
return (
<AppLayout title="Dashboard">
{/* Onboarding Tour Overlay */}
{showTour && <OnboardingTour onComplete={handleTourComplete} />}
{/* Welcome */}
<div className="mb-6">
<h1 className="text-xl sm:text-2xl font-bold text-surface-900 truncate">
Welcome back, {partner?.full_name?.split(' ')[0] || 'Partner'}
</h1>
<p className="mt-1 text-surface-500">
Here's what's happening with your portfolio
</p>
</div>
{/* Trial Status Banner */}
{(partner?.is_active) && (effectiveStats?.totalOrganizations === 0 || effectiveStats === undefined) && (
<div className="mb-6 rounded-lg border border-brand-200 bg-brand-50 p-4">
<div className="flex items-start gap-3">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-brand-100 text-brand-600">
<Lightbulb className="h-4 w-4" />
</div>
<div>
<h3 className="text-sm font-semibold text-brand-900">Welcome to your Command Center!</h3>
<p className="mt-1 text-sm text-brand-700">
You're on a 7-day free trial. Connect your first integration to start seeing your data and unlock the full experience.
</p>
</div>
</div>
</div>
)}
{/* Empty State */}
{(effectiveStats?.totalOrganizations === 0 || effectiveStats === undefined) && (
<div className="mb-8 rounded-xl border border-surface-200 bg-white p-8 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-surface-100">
<Building2 className="h-8 w-8 text-surface-400" />
</div>
<h2 className="text-lg font-semibold text-surface-900">No data yet</h2>
<p className="mx-auto mt-2 max-w-md text-sm text-surface-500">
Connect your first integration to start seeing your data.
</p>
</div>
)}
{/* Get Started Checklist */}
{(effectiveStats?.totalOrganizations === 0 || effectiveStats === undefined) && (
<div className="mb-8 rounded-xl border border-surface-200 bg-white p-5">
<h3 className="mb-4 text-sm font-semibold text-surface-700">Get Started</h3>
<div className="space-y-3">
{onboardingTasks.map((task) => {
const Icon = task.icon;
return (
<div key={task.id} className="flex items-center gap-3 rounded-lg p-3">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-surface-100 text-surface-400">
<Circle className="h-4 w-4" />
</div>
<Icon className="h-4 w-4 shrink-0 text-surface-400" />
<span className="text-sm text-surface-600">{task.label}</span>
</div>
);
})}
</div>
</div>
)}
{/* Next Move — Smart Coaching */}
<div className="mb-6">
<NextMoveWidget coachingPath="/app/coaching" />
</div>
{/* Stats Grid */}
<div data-testid="stats-grid" className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<StatCard
title="Total Organizations"
value={effectiveStats?.totalOrganizations ?? 0}
icon={Building2}
iconBg="bg-brand-100 text-brand-600"
href="/app/organizations"
/>
<StatCard
title="Active Markets"
value={effectiveStats?.totalActive ?? 0}
icon={TrendingUp}
iconBg="bg-success/10 text-success"
change={`${effectiveStats?.totalSuspended ?? 0} suspended`}
changeType="neutral"
href="/app/integrations"
/>
<StatCard
title="Total Revenue"
value={`$${effectiveStats?.totalRevenue?.toLocaleString() ?? 0}`}
icon={DollarSign}
iconBg="bg-success/10 text-success"
href="/app/commissions"
/>
<StatCard
title="Avg. Revenue"
value={`$${effectiveStats?.avgRevenue?.toLocaleString() ?? 0}`}
icon={Users}
iconBg="bg-warning/10 text-warning"
href="/app/coaching"
/>
</div>
{/* System Health */}
{effectiveStats?.systemHealth && (
<div className="mb-8 rounded-xl border border-surface-200 bg-white p-5">
<h3 className="mb-3 text-sm font-semibold text-surface-700">System Health</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<p className="text-xs text-surface-500">Email Failures</p>
<p className="text-lg font-semibold text-surface-900">
{effectiveStats.systemHealth.emailFailures}
</p>
</div>
<div>
<p className="text-xs text-surface-500">Integration Errors</p>
<p className="text-lg font-semibold text-surface-900">
{effectiveStats.systemHealth.integrationErrors}
</p>
</div>
<div>
<p className="text-xs text-surface-500">Active Alerts</p>
<p className="text-lg font-semibold text-surface-900">
{effectiveStats.systemHealth.activeAlerts}
</p>
</div>
<div>
<p className="text-xs text-surface-500">Last Sync</p>
<p className="text-lg font-semibold text-surface-900">
{effectiveStats.systemHealth.lastSync ? new Date(effectiveStats.systemHealth.lastSync).toLocaleDateString() : 'N/A'}
</p>
</div>
</div>
</div>
)}
{/* Recent Activity */}
<div className="rounded-xl border border-surface-200 bg-white p-5">
<h3 className="mb-3 text-sm font-semibold text-surface-700">Recent Activity</h3>
{effectiveStats?.recentActivity?.length === 0 ? (
<p className="text-sm text-surface-500">No recent activity</p>
) : (
<div className="space-y-3">
{effectiveStats?.recentActivity?.map((activity) => (
<div key={activity.id} className="flex items-start gap-3 rounded-lg p-3 hover:bg-surface-50">
<div className="mt-0.5 flex h-8 w-8 items-center justify-center rounded-full bg-brand-100 text-brand-600">
<span className="text-xs">•</span>
</div>
<div className="flex-1">
<p className="text-sm font-medium text-surface-900">{activity.description}</p>
<p className="text-xs text-surface-500">
{activity.timestamp ? new Date(activity.timestamp).toLocaleString() : 'Recent'}
</p>
</div>
</div>
))}
</div>
)}
</div>
</AppLayout>
);
}