import React, { useEffect, useState, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import api from '../../utils/api'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Badge } from '../../components/ui/badge'; import { Avatar, AvatarImage, AvatarFallback } from '../../components/ui/avatar'; import { ArrowLeft, Mail, Phone, MapPin, Calendar, Lock, AlertTriangle, Camera, Upload, Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import ConfirmationDialog from '../../components/ConfirmationDialog'; const AdminUserView = () => { const { userId } = useParams(); const navigate = useNavigate(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [resetPasswordLoading, setResetPasswordLoading] = useState(false); const [resendVerificationLoading, setResendVerificationLoading] = useState(false); const [subscriptions, setSubscriptions] = useState([]); const [subscriptionsLoading, setSubscriptionsLoading] = useState(true); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [pendingAction, setPendingAction] = useState(null); const [uploadingPhoto, setUploadingPhoto] = useState(false); const [maxFileSizeMB, setMaxFileSizeMB] = useState(50); const [maxFileSizeBytes, setMaxFileSizeBytes] = useState(52428800); const fileInputRef = useRef(null); useEffect(() => { fetchConfig(); fetchUserProfile(); fetchSubscriptions(); }, [userId]); const fetchUserProfile = async () => { try { const response = await api.get(`/admin/users/${userId}`); setUser(response.data); } catch (error) { toast.error('Failed to load user profile'); navigate('/admin/members'); } finally { setLoading(false); } }; const fetchSubscriptions = async () => { try { const response = await api.get(`/admin/subscriptions?user_id=${userId}`); setSubscriptions(response.data); } catch (error) { console.error('Failed to fetch subscriptions:', error); } finally { setSubscriptionsLoading(false); } }; const fetchConfig = async () => { try { const response = await api.get('/config'); setMaxFileSizeMB(response.data.max_file_size_mb); setMaxFileSizeBytes(response.data.max_file_size_bytes); } catch (error) { console.error('Failed to fetch config, using defaults:', error); } }; const handlePhotoUpload = async (e) => { const file = e.target.files[0]; if (!file) return; // Validate file type if (!file.type.startsWith('image/')) { toast.error('Please select an image file'); return; } // Validate file size if (file.size > maxFileSizeBytes) { toast.error(`File size must be less than ${maxFileSizeMB}MB`); return; } setUploadingPhoto(true); try { const formData = new FormData(); formData.append('file', file); const response = await api.post(`/admin/users/${userId}/upload-photo`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); // Update user state with new photo URL setUser(prev => ({ ...prev, profile_photo_url: response.data.profile_photo_url })); toast.success('Profile photo updated successfully'); } catch (error) { toast.error(error.response?.data?.detail || 'Failed to upload photo'); } finally { setUploadingPhoto(false); // Reset file input if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handlePhotoDelete = async () => { if (!user?.profile_photo_url) return; setUploadingPhoto(true); try { await api.delete(`/admin/users/${userId}/delete-photo`); // Update user state to remove photo URL setUser(prev => ({ ...prev, profile_photo_url: null })); toast.success('Profile photo deleted successfully'); } catch (error) { toast.error(error.response?.data?.detail || 'Failed to delete photo'); } finally { setUploadingPhoto(false); } }; const handleResetPasswordRequest = () => { setPendingAction({ type: 'reset_password' }); setConfirmDialogOpen(true); }; const handleResendVerificationRequest = () => { setPendingAction({ type: 'resend_verification' }); setConfirmDialogOpen(true); }; const confirmAction = async () => { if (!pendingAction) return; const { type } = pendingAction; setConfirmDialogOpen(false); if (type === 'reset_password') { setResetPasswordLoading(true); try { await api.put(`/admin/users/${userId}/reset-password`, { force_change: true }); toast.success(`Password reset email sent to ${user.email}`); } catch (error) { const errorMessage = error.response?.data?.detail || 'Failed to reset password'; toast.error(errorMessage); } finally { setResetPasswordLoading(false); setPendingAction(null); } } else if (type === 'resend_verification') { setResendVerificationLoading(true); try { await api.post(`/admin/users/${userId}/resend-verification`); toast.success(`Verification email sent to ${user.email}`); // Refresh user data to get updated email_verified status if changed await fetchUserProfile(); } catch (error) { const errorMessage = error.response?.data?.detail || 'Failed to send verification email'; toast.error(errorMessage); } finally { setResendVerificationLoading(false); setPendingAction(null); } } }; const getActionMessage = () => { if (!pendingAction || !user) return {}; const { type } = pendingAction; const userName = `${user.first_name} ${user.last_name}`; if (type === 'reset_password') { return { title: 'Reset Password?', description: `This will send a temporary password to ${user.email}. ${userName} will be required to change it on their next login.`, variant: 'warning', confirmText: 'Yes, Reset Password', }; } if (type === 'resend_verification') { return { title: 'Resend Verification Email?', description: `This will send a new verification email to ${user.email}. ${userName} will need to click the link to verify their email address.`, variant: 'info', confirmText: 'Yes, Resend Email', }; } return {}; }; if (loading) return
{user.address}
{new Date(user.date_of_birth).toLocaleDateString()}
{user.partner_first_name} {user.partner_last_name}
{user.referred_by_member_name}
Loading subscriptions...
) : subscriptions.length === 0 ? (No subscriptions found for this member.
) : ({sub.plan.billing_cycle}
{new Date(sub.start_date).toLocaleDateString()}
{new Date(sub.end_date).toLocaleDateString()}
${(sub.base_subscription_cents / 100).toFixed(2)}
${(sub.donation_cents / 100).toFixed(2)}
${(sub.amount_paid_cents / 100).toFixed(2)}
{sub.payment_method}
{sub.stripe_subscription_id}