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 ( {status.replace('_', ' ')} ); }; // ============================================================================ // Render Step Content // ============================================================================ const renderStepContent = () => { switch (currentStep) { case 1: return ; case 2: return ; case 3: return ; case 4: return ; case 5: return ; case 6: return ; default: return null; } }; // ============================================================================ // Step 1: Upload CSV // ============================================================================ const Step1Upload = () => (

Upload WordPress CSV Export

Select the WordPress user export CSV file. The file will be analyzed for data quality issues.

{uploadedFile && (

Selected: {uploadedFile.name}

)}
{uploadedFile && !analysisResult && ( )} {analysisResult && (

Analysis Complete

Total Rows

{analysisResult.total_rows}

Valid Rows

{analysisResult.valid_rows}

Warnings

{analysisResult.warnings}

Errors

{analysisResult.errors}

{analysisResult.data_quality && (
Data Quality Issues:
    {analysisResult.data_quality.invalid_dob > 0 && (
  • • {analysisResult.data_quality.invalid_dob} invalid dates of birth
  • )} {analysisResult.data_quality.missing_phone > 0 && (
  • • {analysisResult.data_quality.missing_phone} missing phone numbers
  • )} {analysisResult.data_quality.duplicate_email > 0 && (
  • • {analysisResult.data_quality.duplicate_email} duplicate emails
  • )}
)}
)}
); // ============================================================================ // Step 2: Field Mapping // ============================================================================ const Step2FieldMapping = () => (

Field Mapping

WordPress fields have been automatically mapped to LOAF platform fields.

WordPress Field LOAF Field user_email email first_name first_name last_name last_name cell_phone phone date_of_birth date_of_birth wp_capabilities role + status
WordPress roles will be automatically converted:
  • loaf_admin → admin (active)
  • loaf_treasure → finance (active)
  • administrator → superadmin (active)
  • pms_subscription_plan_63 → member
); // ============================================================================ // Step 3: Review & Adjust Status (KEY FEATURE) // ============================================================================ const Step3ReviewStatus = () => (

Review & Adjust User Status

Review suggested status mappings and override as needed before import.

{/* Bulk edit toolbar */}
0} onCheckedChange={toggleSelectAll} /> {selectedRows.size > 0 ? `${selectedRows.size} selected` : 'Select all'} {selectedRows.size > 0 && ( )}
{/* Data table */} {loading ? (
) : (
Row Email Name WP Role Suggested Status Override Status Issues {previewData.map((row) => ( toggleRowSelection(row.row_number)} /> {row.row_number} {row.email} {row.first_name} {row.last_name} {row.wordpress_roles?.join(', ') || 'N/A'} {row.warnings?.map((warning, idx) => ( {warning} ))} ))}
)} {/* Pagination */} {totalPages > 1 && (

Page {currentPage} of {totalPages}

)}
); // ============================================================================ // Step 4: Preview // ============================================================================ const Step4Preview = () => { const overrideCount = Object.keys(statusOverrides).length; return (

Import Preview

Review the final import settings before execution.

Total Users

{analysisResult?.total_rows}

Status Overrides

{overrideCount}

Expected Imports

{analysisResult?.valid_rows}

Import Options

Send password reset emails to all imported users
Skip rows with errors and continue import
Full rollback capability available after import
{overrideCount > 0 && ( You have overridden {overrideCount} user status{overrideCount > 1 ? 'es' : ''}. These will be applied during import. )}
); }; // ============================================================================ // Step 5: Execute // ============================================================================ const Step5Execute = () => (

{importing ? 'Import in Progress...' : 'Ready to Import'}

{importing ? 'Please wait while users are imported. This may take a few minutes.' : 'Click "Start Import" to begin importing users.'}

{importing && (

{importProgress.toFixed(1)}% complete

)} {!importing && !importResults && ( )}
); // ============================================================================ // Step 6: Results & Rollback // ============================================================================ const Step6Results = () => (

Import Complete

Review the import results and download error reports if needed.

{/* Stats cards */}

Successful Imports

{importResults?.successful_rows || 0}

Failed Imports

{importResults?.failed_rows || 0}

Password Emails Sent

{importResults?.password_emails_queued || 0}

{/* Action buttons */}
{importResults?.failed_rows > 0 && ( )}
{/* Rollback button (prominent, red) */} {importResults?.successful_rows > 0 && ( )}
{/* Rollback confirmation dialog */}
Confirm Rollback
This will permanently delete{' '} {importResults?.successful_rows} users that were imported. This action cannot be undone.

Type "DELETE {importResults?.successful_rows} USERS" to confirm:

setConfirmText(e.target.value)} className="mt-2" placeholder={`DELETE ${importResults?.successful_rows} USERS`} />
); // ============================================================================ // Main Render // ============================================================================ return ( WordPress Import Wizard Import WordPress users with interactive status review and full rollback capability {/* Step indicator */}
{steps.map((step, index) => { const StepIcon = step.icon; const isCompleted = currentStep > step.number; const isCurrent = currentStep === step.number; return (
{isCompleted ? ( ) : ( )}

{step.title}

{index < steps.length - 1 && (
)} ); })}
{/* Step content */}
{renderStepContent()}
{/* Navigation footer */} {currentStep < 5 && ( )} {currentStep === 6 && ( )}
); }