refactor(frontend): replace confirm() with modern confirmation modals
Replaced all confirm() dialogs with reusable ConfirmationModal component for better UX. Modal dialogs provide clearer context, visual consistency, and prevent accidental confirmations. Changes: - MatchesPage: Reject match request confirmation modal - DashboardPage: Decline and cancel request confirmation modals - ContactMessagesPage: Delete message confirmation modal All modals support loading states during async operations and provide clear action descriptions with destructive action styling.
This commit is contained in:
@@ -7,6 +7,7 @@ import { dashboardAPI, matchesAPI } from '../services/api';
|
||||
import { connectSocket, disconnectSocket, getSocket } from '../services/socket';
|
||||
import { DashboardSkeleton } from '../components/common/Skeleton';
|
||||
import EmptyState from '../components/common/EmptyState';
|
||||
import ConfirmationModal from '../components/modals/ConfirmationModal';
|
||||
import {
|
||||
DashboardEventCard,
|
||||
DashboardMatchCard,
|
||||
@@ -28,6 +29,9 @@ const DashboardPage = () => {
|
||||
const { user } = useAuth();
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showDeclineModal, setShowDeclineModal] = useState(false);
|
||||
const [showCancelModal, setShowCancelModal] = useState(false);
|
||||
const [matchToProcess, setMatchToProcess] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [processingMatchId, setProcessingMatchId] = useState(null);
|
||||
|
||||
@@ -105,12 +109,17 @@ const DashboardPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRejectMatch = async (matchSlug) => {
|
||||
if (!confirm('Are you sure you want to decline this request?')) return;
|
||||
const handleRejectMatch = (matchSlug) => {
|
||||
setMatchToProcess(matchSlug);
|
||||
setShowDeclineModal(true);
|
||||
};
|
||||
|
||||
const confirmRejectMatch = async () => {
|
||||
if (!matchToProcess) return;
|
||||
|
||||
try {
|
||||
setProcessingMatchId(matchSlug);
|
||||
await matchesAPI.deleteMatch(matchSlug);
|
||||
setProcessingMatchId(matchToProcess);
|
||||
await matchesAPI.deleteMatch(matchToProcess);
|
||||
toast.success('Request declined.');
|
||||
await loadDashboard();
|
||||
} catch (err) {
|
||||
@@ -118,15 +127,22 @@ const DashboardPage = () => {
|
||||
toast.error('Failed to decline request. Please try again.');
|
||||
} finally {
|
||||
setProcessingMatchId(null);
|
||||
setShowDeclineModal(false);
|
||||
setMatchToProcess(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelRequest = async (matchSlug) => {
|
||||
if (!confirm('Are you sure you want to cancel this request?')) return;
|
||||
const handleCancelRequest = (matchSlug) => {
|
||||
setMatchToProcess(matchSlug);
|
||||
setShowCancelModal(true);
|
||||
};
|
||||
|
||||
const confirmCancelRequest = async () => {
|
||||
if (!matchToProcess) return;
|
||||
|
||||
try {
|
||||
setProcessingMatchId(matchSlug);
|
||||
await matchesAPI.deleteMatch(matchSlug);
|
||||
setProcessingMatchId(matchToProcess);
|
||||
await matchesAPI.deleteMatch(matchToProcess);
|
||||
toast.success('Request cancelled.');
|
||||
await loadDashboard();
|
||||
} catch (err) {
|
||||
@@ -134,6 +150,8 @@ const DashboardPage = () => {
|
||||
toast.error('Failed to cancel request. Please try again.');
|
||||
} finally {
|
||||
setProcessingMatchId(null);
|
||||
setShowCancelModal(false);
|
||||
setMatchToProcess(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -316,6 +334,38 @@ const DashboardPage = () => {
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Decline Confirmation Modal */}
|
||||
<ConfirmationModal
|
||||
isOpen={showDeclineModal}
|
||||
onClose={() => {
|
||||
setShowDeclineModal(false);
|
||||
setMatchToProcess(null);
|
||||
}}
|
||||
onConfirm={confirmRejectMatch}
|
||||
title="Decline Match Request"
|
||||
message="Are you sure you want to decline this request? This action cannot be undone."
|
||||
confirmText="Decline"
|
||||
cancelText="Cancel"
|
||||
isLoading={processingMatchId === matchToProcess}
|
||||
loadingText="Declining..."
|
||||
/>
|
||||
|
||||
{/* Cancel Confirmation Modal */}
|
||||
<ConfirmationModal
|
||||
isOpen={showCancelModal}
|
||||
onClose={() => {
|
||||
setShowCancelModal(false);
|
||||
setMatchToProcess(null);
|
||||
}}
|
||||
onConfirm={confirmCancelRequest}
|
||||
title="Cancel Match Request"
|
||||
message="Are you sure you want to cancel this request? This action cannot be undone."
|
||||
confirmText="Cancel Request"
|
||||
cancelText="Keep Request"
|
||||
isLoading={processingMatchId === matchToProcess}
|
||||
loadingText="Cancelling..."
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Loader2, Users } from 'lucide-react';
|
||||
import { connectSocket, disconnectSocket, getSocket } from '../services/socket';
|
||||
import { MatchCard } from '../components/matches';
|
||||
import { MATCH_STATUS, MATCH_FILTER } from '../constants';
|
||||
import ConfirmationModal from '../components/modals/ConfirmationModal';
|
||||
|
||||
const MatchesPage = () => {
|
||||
const { user } = useAuth();
|
||||
@@ -16,6 +17,8 @@ const MatchesPage = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState(MATCH_FILTER.ALL);
|
||||
const [processingMatchId, setProcessingMatchId] = useState(null);
|
||||
const [showRejectModal, setShowRejectModal] = useState(false);
|
||||
const [matchToReject, setMatchToReject] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadMatches();
|
||||
@@ -90,22 +93,28 @@ const MatchesPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async (matchSlug) => {
|
||||
if (!confirm('Are you sure you want to reject this match request?')) {
|
||||
return;
|
||||
}
|
||||
const handleReject = (matchSlug) => {
|
||||
setMatchToReject(matchSlug);
|
||||
setShowRejectModal(true);
|
||||
};
|
||||
|
||||
const confirmReject = async () => {
|
||||
if (!matchToReject) return;
|
||||
|
||||
try {
|
||||
setProcessingMatchId(matchSlug);
|
||||
await matchesAPI.deleteMatch(matchSlug);
|
||||
setProcessingMatchId(matchToReject);
|
||||
await matchesAPI.deleteMatch(matchToReject);
|
||||
|
||||
// Remove from list
|
||||
setMatches(prev => prev.filter(m => m.slug !== matchSlug));
|
||||
setMatches(prev => prev.filter(m => m.slug !== matchToReject));
|
||||
toast.success('Match request rejected');
|
||||
} catch (error) {
|
||||
console.error('Failed to reject match:', error);
|
||||
toast.error('Failed to reject match. Please try again.');
|
||||
} finally {
|
||||
setProcessingMatchId(null);
|
||||
setShowRejectModal(false);
|
||||
setMatchToReject(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,6 +241,22 @@ const MatchesPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Reject Confirmation Modal */}
|
||||
<ConfirmationModal
|
||||
isOpen={showRejectModal}
|
||||
onClose={() => {
|
||||
setShowRejectModal(false);
|
||||
setMatchToReject(null);
|
||||
}}
|
||||
onConfirm={confirmReject}
|
||||
title="Reject Match Request"
|
||||
message="Are you sure you want to reject this match request? This action cannot be undone."
|
||||
confirmText="Reject"
|
||||
cancelText="Cancel"
|
||||
isLoading={processingMatchId === matchToReject}
|
||||
loadingText="Rejecting..."
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import toast from 'react-hot-toast';
|
||||
import { Mail, User, Calendar, Trash2, Check, Eye, AlertCircle, Loader2, Filter } from 'lucide-react';
|
||||
import Layout from '../../components/layout/Layout';
|
||||
import ConfirmationModal from '../../components/modals/ConfirmationModal';
|
||||
|
||||
// This would normally come from an admin API module, but for now we'll add it inline
|
||||
const adminContactAPI = {
|
||||
@@ -73,6 +75,8 @@ export default function ContactMessagesPage() {
|
||||
const [total, setTotal] = useState(0);
|
||||
const [statusFilter, setStatusFilter] = useState('all');
|
||||
const [selectedMessage, setSelectedMessage] = useState(null);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [messageToDelete, setMessageToDelete] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const userData = localStorage.getItem('user');
|
||||
@@ -115,19 +119,27 @@ export default function ContactMessagesPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
if (!confirm('Are you sure you want to delete this message?')) {
|
||||
return;
|
||||
}
|
||||
const handleDelete = (id) => {
|
||||
setMessageToDelete(id);
|
||||
setShowDeleteModal(true);
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!messageToDelete) return;
|
||||
|
||||
try {
|
||||
await adminContactAPI.deleteMessage(id);
|
||||
await adminContactAPI.deleteMessage(messageToDelete);
|
||||
await fetchMessages();
|
||||
if (selectedMessage?.id === id) {
|
||||
if (selectedMessage?.id === messageToDelete) {
|
||||
setSelectedMessage(null);
|
||||
}
|
||||
toast.success('Message deleted successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete message:', error);
|
||||
toast.error('Failed to delete message. Please try again.');
|
||||
} finally {
|
||||
setShowDeleteModal(false);
|
||||
setMessageToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -343,6 +355,20 @@ export default function ContactMessagesPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<ConfirmationModal
|
||||
isOpen={showDeleteModal}
|
||||
onClose={() => {
|
||||
setShowDeleteModal(false);
|
||||
setMessageToDelete(null);
|
||||
}}
|
||||
onConfirm={confirmDelete}
|
||||
title="Delete Message"
|
||||
message="Are you sure you want to delete this contact message? This action cannot be undone."
|
||||
confirmText="Delete"
|
||||
cancelText="Cancel"
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user