Email SMTP Fix
This commit is contained in:
@@ -21,12 +21,6 @@ const AdminMembers = () => {
|
||||
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
|
||||
const [selectedUserForPayment, setSelectedUserForPayment] = useState(null);
|
||||
|
||||
const tabs = [
|
||||
{ name: 'All Users', path: '/admin/users' },
|
||||
{ name: 'Staff', path: '/admin/staff' },
|
||||
{ name: 'Members', path: '/admin/members' }
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
fetchMembers();
|
||||
}, []);
|
||||
@@ -105,30 +99,6 @@ const AdminMembers = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<div className="border-b border-[#EAE0D5] mb-8">
|
||||
<nav className="flex gap-8">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={`
|
||||
pb-4 px-2 font-medium transition-colors relative
|
||||
${location.pathname === tab.path
|
||||
? 'text-[#E07A5F]'
|
||||
: 'text-[#6B708D] hover:text-[#3D405B]'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{tab.name}
|
||||
{location.pathname === tab.path && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-[#E07A5F]" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid md:grid-cols-4 gap-4 mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 } from 'lucide-react';
|
||||
import { ArrowLeft, Mail, Phone, MapPin, Calendar, Lock, AlertTriangle } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const AdminUserView = () => {
|
||||
@@ -12,6 +12,8 @@ const AdminUserView = () => {
|
||||
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();
|
||||
@@ -29,6 +31,52 @@ const AdminUserView = () => {
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -86,6 +134,41 @@ const AdminUserView = () => {
|
||||
</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">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Badge } from '../../components/ui/badge';
|
||||
import { Input } from '../../components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select';
|
||||
import { toast } from 'sonner';
|
||||
import { Users, Search, CheckCircle, Clock } from 'lucide-react';
|
||||
import { Users, Search, CheckCircle, Clock, Mail, Eye } from 'lucide-react';
|
||||
|
||||
const AdminUsers = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -17,12 +17,7 @@ const AdminUsers = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('all');
|
||||
|
||||
const tabs = [
|
||||
{ name: 'All Users', path: '/admin/users' },
|
||||
{ name: 'Staff', path: '/admin/staff' },
|
||||
{ name: 'Members', path: '/admin/members' }
|
||||
];
|
||||
const [resendingUserId, setResendingUserId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsers();
|
||||
@@ -80,6 +75,25 @@ const AdminUsers = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const handleAdminResendVerification = async (userId, userEmail) => {
|
||||
const confirmed = window.confirm(
|
||||
`Resend verification email to ${userEmail}?`
|
||||
);
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
setResendingUserId(userId);
|
||||
try {
|
||||
await api.post(`/admin/users/${userId}/resend-verification`);
|
||||
toast.success(`Verification email sent to ${userEmail}`);
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.detail || 'Failed to send verification email';
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setResendingUserId(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
@@ -91,30 +105,6 @@ const AdminUsers = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Tab Navigation */}
|
||||
<div className="border-b border-[#EAE0D5] mb-8">
|
||||
<nav className="flex gap-8">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.path}
|
||||
onClick={() => navigate(tab.path)}
|
||||
className={`
|
||||
pb-4 px-2 font-medium transition-colors relative
|
||||
${location.pathname === tab.path
|
||||
? 'text-[#E07A5F]'
|
||||
: 'text-[#6B708D] hover:text-[#3D405B]'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{tab.name}
|
||||
{location.pathname === tab.path && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-[#E07A5F]" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
@@ -176,6 +166,30 @@ const AdminUsers = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() => navigate(`/admin/users/${user.id}`)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#6B708D] hover:text-[#3D405B]"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
View
|
||||
</Button>
|
||||
|
||||
{!user.email_verified && (
|
||||
<Button
|
||||
onClick={() => handleAdminResendVerification(user.id, user.email)}
|
||||
disabled={resendingUserId === user.id}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#E07A5F] hover:text-[#D0694E]"
|
||||
>
|
||||
<Mail className="h-4 w-4 mr-1" />
|
||||
{resendingUserId === user.id ? 'Sending...' : 'Resend Verification'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user