Files
membership-fe/src/pages/admin/AdminUserView.js
2025-12-07 16:59:21 +07:00

235 lines
8.1 KiB
JavaScript

import React, { useEffect, useState } 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 { ArrowLeft, Mail, Phone, MapPin, Calendar, Lock, AlertTriangle } from 'lucide-react';
import { toast } from 'sonner';
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);
useEffect(() => {
fetchUserProfile();
}, [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 handleResetPassword = async () => {
const confirmed = window.confirm(
`Reset password for ${user.first_name} ${user.last_name}?\n\n` +
`A temporary password will be emailed to ${user.email}.\n` +
`They will be required to change it on next login.`
);
if (!confirmed) return;
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);
}
};
const handleResendVerification = async () => {
const confirmed = window.confirm(
`Resend verification email to ${user.email}?`
);
if (!confirmed) return;
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);
}
};
if (loading) return <div>Loading...</div>;
if (!user) return null;
return (
<>
{/* Back Button */}
<Button
variant="ghost"
onClick={() => navigate(-1)}
className="mb-6"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Back
</Button>
{/* User Profile Header */}
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
<div className="flex items-start gap-6">
{/* Avatar */}
<div className="h-24 w-24 rounded-full bg-[#F2CC8F] flex items-center justify-center text-[#3D405B] font-semibold text-3xl">
{user.first_name?.[0]}{user.last_name?.[0]}
</div>
{/* User Info */}
<div className="flex-1">
<div className="flex items-center gap-4 mb-4">
<h1 className="text-3xl font-semibold fraunces text-[#3D405B]">
{user.first_name} {user.last_name}
</h1>
{/* Status & Role Badges */}
<Badge>{user.status}</Badge>
<Badge>{user.role}</Badge>
</div>
{/* Contact Info */}
<div className="grid md:grid-cols-2 gap-4 text-[#6B708D]">
<div className="flex items-center gap-2">
<Mail className="h-4 w-4" />
<span>{user.email}</span>
</div>
<div className="flex items-center gap-2">
<Phone className="h-4 w-4" />
<span>{user.phone}</span>
</div>
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4" />
<span>{user.city}, {user.state} {user.zipcode}</span>
</div>
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4" />
<span>Joined {new Date(user.created_at).toLocaleDateString()}</span>
</div>
</div>
</div>
</div>
</Card>
{/* Admin Actions */}
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
<h2 className="text-lg font-semibold fraunces text-[#3D405B] mb-4">
Admin Actions
</h2>
<div className="flex flex-wrap gap-3">
<Button
onClick={handleResetPassword}
disabled={resetPasswordLoading}
variant="outline"
className="border-2 border-[#E07A5F] text-[#E07A5F] hover:bg-[#FFF3E0] rounded-full px-4 py-2 disabled:opacity-50"
>
<Lock className="h-4 w-4 mr-2" />
{resetPasswordLoading ? 'Resetting...' : 'Reset Password'}
</Button>
{!user.email_verified && (
<Button
onClick={handleResendVerification}
disabled={resendVerificationLoading}
variant="outline"
className="border-2 border-[#E07A5F] text-[#E07A5F] hover:bg-[#FFF3E0] rounded-full px-4 py-2 disabled:opacity-50"
>
<Mail className="h-4 w-4 mr-2" />
{resendVerificationLoading ? 'Sending...' : 'Resend Verification Email'}
</Button>
)}
<div className="flex items-center gap-2 text-sm text-[#6B708D] ml-2">
<AlertTriangle className="h-4 w-4" />
<span>User will receive a temporary password via email</span>
</div>
</div>
</Card>
{/* Additional Details */}
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5]">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6">
Additional Information
</h2>
<div className="grid md:grid-cols-2 gap-6">
<div>
<label className="text-sm font-medium text-[#6B708D]">Address</label>
<p className="text-[#3D405B] mt-1">{user.address}</p>
</div>
<div>
<label className="text-sm font-medium text-[#6B708D]">Date of Birth</label>
<p className="text-[#3D405B] mt-1">
{new Date(user.date_of_birth).toLocaleDateString()}
</p>
</div>
{user.partner_first_name && (
<div>
<label className="text-sm font-medium text-[#6B708D]">Partner</label>
<p className="text-[#3D405B] mt-1">
{user.partner_first_name} {user.partner_last_name}
</p>
</div>
)}
{user.referred_by_member_name && (
<div>
<label className="text-sm font-medium text-[#6B708D]">Referred By</label>
<p className="text-[#3D405B] mt-1">{user.referred_by_member_name}</p>
</div>
)}
{user.lead_sources && user.lead_sources.length > 0 && (
<div className="md:col-span-2">
<label className="text-sm font-medium text-[#6B708D]">Lead Sources</label>
<div className="flex flex-wrap gap-2 mt-2">
{user.lead_sources.map((source, idx) => (
<Badge key={idx} variant="outline">{source}</Badge>
))}
</div>
</div>
)}
</div>
</Card>
{/* Subscription Info (if applicable) */}
{user.role === 'member' && (
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] mt-8">
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6">
Subscription Information
</h2>
{/* TODO: Fetch and display subscription data */}
<p className="text-[#6B708D]">Subscription details coming soon...</p>
</Card>
)}
</>
);
};
export default AdminUserView;