import React, { useState, useEffect } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from './ui/dialog';
import { Button } from './ui/button';
import { Card } from './ui/card';
import { Badge } from './ui/badge';
import { Checkbox } from './ui/checkbox';
import { Input } from './ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table';
import { Progress } from './ui/progress';
import { Alert, AlertDescription } from './ui/alert';
import { toast } from 'sonner';
import api from '../utils/api';
import {
Upload,
FileCheck,
CheckCircle,
Eye,
Play,
CheckCircle2,
AlertCircle,
AlertTriangle,
Trash2,
FileDown,
Users,
ChevronLeft,
ChevronRight,
Loader2
} from 'lucide-react';
/**
* WordPress Import Wizard Component
*
* A comprehensive 6-step wizard for importing WordPress users to LOAF platform.
* Features:
* - CSV upload and analysis
* - Interactive status review and adjustment
* - Preview before import
* - Real-time import progress
* - Full rollback capability
* - Error reporting
*/
export default function WordPressImportWizard({ open, onOpenChange, onSuccess }) {
// Wizard state
const [currentStep, setCurrentStep] = useState(1);
const [importJobId, setImportJobId] = useState(null);
// Data state
const [uploadedFile, setUploadedFile] = useState(null);
const [analysisResult, setAnalysisResult] = useState(null);
const [previewData, setPreviewData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
// Override state
const [statusOverrides, setStatusOverrides] = useState({});
const [selectedRows, setSelectedRows] = useState(new Set());
// Import execution state
const [importing, setImporting] = useState(false);
const [importProgress, setImportProgress] = useState(0);
const [importResults, setImportResults] = useState(null);
// UI state
const [uploading, setUploading] = useState(false);
const [loading, setLoading] = useState(false);
// Step definitions
const steps = [
{ number: 1, title: 'Upload CSV', icon: Upload },
{ number: 2, title: 'Field Mapping', icon: FileCheck },
{ number: 3, title: 'Review Status', icon: CheckCircle },
{ number: 4, title: 'Preview', icon: Eye },
{ number: 5, title: 'Execute', icon: Play },
{ number: 6, title: 'Results', icon: CheckCircle2 }
];
// Reset wizard state when dialog opens/closes
useEffect(() => {
if (!open) {
setTimeout(() => {
setCurrentStep(1);
setImportJobId(null);
setUploadedFile(null);
setAnalysisResult(null);
setPreviewData([]);
setStatusOverrides({});
setSelectedRows(new Set());
setImporting(false);
setImportProgress(0);
setImportResults(null);
}, 300); // Wait for dialog close animation
}
}, [open]);
// ============================================================================
// Step Navigation
// ============================================================================
const canProceed = () => {
switch (currentStep) {
case 1:
return uploadedFile && analysisResult;
case 2:
return true; // Field mapping auto-detected
case 3:
return true; // Status review is optional
case 4:
return true; // Preview is informational
case 5:
return !importing;
case 6:
return true;
default:
return false;
}
};
const handleNext = () => {
if (currentStep < 6 && canProceed()) {
if (currentStep === 3) {
// Load preview data when moving from step 3 to 4
loadPreviewData(1);
}
setCurrentStep(currentStep + 1);
}
};
const handleBack = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
// ============================================================================
// Step 1: Upload CSV
// ============================================================================
const handleFileSelect = (e) => {
const file = e.target.files[0];
if (file) {
if (!file.name.endsWith('.csv')) {
toast.error('Please upload a CSV file');
return;
}
setUploadedFile(file);
}
};
const handleUpload = async () => {
if (!uploadedFile) return;
setUploading(true);
const formData = new FormData();
formData.append('file', uploadedFile);
try {
const response = await api.post('/admin/import/upload-csv', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
setImportJobId(response.data.import_job_id);
setAnalysisResult(response.data);
toast.success(`CSV analyzed: ${response.data.valid_rows} valid rows, ${response.data.warnings} warnings`);
// Auto-advance to next step
setTimeout(() => setCurrentStep(2), 500);
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to upload CSV');
} finally {
setUploading(false);
}
};
// ============================================================================
// Step 3: Review & Adjust Status
// ============================================================================
const loadPreviewData = async (page = 1) => {
if (!importJobId) return;
setLoading(true);
try {
const response = await api.get(`/admin/import/${importJobId}/preview`, {
params: { page, page_size: 50 }
});
setPreviewData(response.data.rows);
setCurrentPage(response.data.page);
setTotalPages(response.data.total_pages);
} catch (error) {
toast.error('Failed to load preview data');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (currentStep === 3 && importJobId && previewData.length === 0) {
loadPreviewData(1);
}
}, [currentStep, importJobId]);
const handleStatusOverride = (rowNum, status) => {
setStatusOverrides(prev => ({
...prev,
[rowNum]: { status }
}));
};
const handleBulkStatusChange = (status) => {
const newOverrides = { ...statusOverrides };
selectedRows.forEach(rowNum => {
newOverrides[rowNum] = { status };
});
setStatusOverrides(newOverrides);
toast.success(`Updated ${selectedRows.size} users to ${status}`);
};
const toggleRowSelection = (rowNum) => {
setSelectedRows(prev => {
const newSet = new Set(prev);
if (newSet.has(rowNum)) {
newSet.delete(rowNum);
} else {
newSet.add(rowNum);
}
return newSet;
});
};
const toggleSelectAll = () => {
if (selectedRows.size === previewData.length) {
setSelectedRows(new Set());
} else {
setSelectedRows(new Set(previewData.map(row => row.row_number)));
}
};
// ============================================================================
// Step 5: Execute Import
// ============================================================================
const handleExecuteImport = async () => {
setImporting(true);
setCurrentStep(5);
try {
// Start import
const response = await api.post(`/admin/import/${importJobId}/execute`, {
overrides: statusOverrides,
options: {
send_password_emails: true,
skip_errors: true
}
});
setImportResults(response.data);
toast.success(`Import completed: ${response.data.successful_rows} users imported`);
// Move to results step
setCurrentStep(6);
} catch (error) {
toast.error(error.response?.data?.detail || 'Import failed');
} finally {
setImporting(false);
}
};
// Poll for import progress
useEffect(() => {
if (currentStep === 5 && importing && importJobId) {
const interval = setInterval(async () => {
try {
const response = await api.get(`/admin/import/${importJobId}/status`);
setImportProgress(response.data.progress_percent);
} catch (error) {
console.error('Failed to fetch import status:', error);
}
}, 1000);
return () => clearInterval(interval);
}
}, [currentStep, importing, importJobId]);
// ============================================================================
// Step 6: Rollback
// ============================================================================
const [rollbackConfirmOpen, setRollbackConfirmOpen] = useState(false);
const [confirmText, setConfirmText] = useState('');
const handleRollback = async () => {
try {
await api.post(`/admin/import/${importJobId}/rollback`, { confirm: true });
toast.success(`Rolled back ${importResults.successful_rows} users`);
onOpenChange(false);
if (onSuccess) onSuccess();
} catch (error) {
toast.error(error.response?.data?.detail || 'Rollback failed');
}
};
const handleDownloadErrors = async () => {
try {
const response = await api.get(`/admin/import/${importJobId}/errors/download`, {
responseType: 'blob'
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `import_errors_${importJobId}.csv`);
document.body.appendChild(link);
link.click();
link.remove();
toast.success('Error report downloaded');
} catch (error) {
toast.error('Failed to download error report');
}
};
// ============================================================================
// Status Badge Component
// ============================================================================
const StatusBadge = ({ status }) => {
const colors = {
active: 'bg-green-100 text-green-800 border-green-300',
pre_validated: 'bg-blue-100 text-blue-800 border-blue-300',
payment_pending: 'bg-yellow-100 text-yellow-800 border-yellow-300',
inactive: 'bg-gray-100 text-gray-800 border-gray-300'
};
return (
Select the WordPress user export CSV file. The file will be analyzed for data quality issues.
Selected: {uploadedFile.name}
)}Total Rows
{analysisResult.total_rows}
Valid Rows
{analysisResult.valid_rows}
Warnings
{analysisResult.warnings}
Errors
{analysisResult.errors}
WordPress fields have been automatically mapped to LOAF platform fields.
loaf_admin → admin (active)loaf_treasure → finance (active)administrator → superadmin (active)pms_subscription_plan_63 → memberReview suggested status mappings and override as needed before import.
Page {currentPage} of {totalPages}
Review the final import settings before execution.
Total Users
{analysisResult?.total_rows}
Status Overrides
{overrideCount}
Expected Imports
{analysisResult?.valid_rows}
{importing ? 'Please wait while users are imported. This may take a few minutes.' : 'Click "Start Import" to begin importing users.'}
{importProgress.toFixed(1)}% complete
Review the import results and download error reports if needed.
Successful Imports
{importResults?.successful_rows || 0}
Failed Imports
{importResults?.failed_rows || 0}
Password Emails Sent
{importResults?.password_emails_queued || 0}