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 { connectSocket, disconnectSocket, getSocket } from '../services/socket';
|
||||||
import { DashboardSkeleton } from '../components/common/Skeleton';
|
import { DashboardSkeleton } from '../components/common/Skeleton';
|
||||||
import EmptyState from '../components/common/EmptyState';
|
import EmptyState from '../components/common/EmptyState';
|
||||||
|
import ConfirmationModal from '../components/modals/ConfirmationModal';
|
||||||
import {
|
import {
|
||||||
DashboardEventCard,
|
DashboardEventCard,
|
||||||
DashboardMatchCard,
|
DashboardMatchCard,
|
||||||
@@ -28,6 +29,9 @@ const DashboardPage = () => {
|
|||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [data, setData] = useState(null);
|
const [data, setData] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
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 [error, setError] = useState('');
|
||||||
const [processingMatchId, setProcessingMatchId] = useState(null);
|
const [processingMatchId, setProcessingMatchId] = useState(null);
|
||||||
|
|
||||||
@@ -105,12 +109,17 @@ const DashboardPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRejectMatch = async (matchSlug) => {
|
const handleRejectMatch = (matchSlug) => {
|
||||||
if (!confirm('Are you sure you want to decline this request?')) return;
|
setMatchToProcess(matchSlug);
|
||||||
|
setShowDeclineModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmRejectMatch = async () => {
|
||||||
|
if (!matchToProcess) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setProcessingMatchId(matchSlug);
|
setProcessingMatchId(matchToProcess);
|
||||||
await matchesAPI.deleteMatch(matchSlug);
|
await matchesAPI.deleteMatch(matchToProcess);
|
||||||
toast.success('Request declined.');
|
toast.success('Request declined.');
|
||||||
await loadDashboard();
|
await loadDashboard();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -118,15 +127,22 @@ const DashboardPage = () => {
|
|||||||
toast.error('Failed to decline request. Please try again.');
|
toast.error('Failed to decline request. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setProcessingMatchId(null);
|
setProcessingMatchId(null);
|
||||||
|
setShowDeclineModal(false);
|
||||||
|
setMatchToProcess(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancelRequest = async (matchSlug) => {
|
const handleCancelRequest = (matchSlug) => {
|
||||||
if (!confirm('Are you sure you want to cancel this request?')) return;
|
setMatchToProcess(matchSlug);
|
||||||
|
setShowCancelModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmCancelRequest = async () => {
|
||||||
|
if (!matchToProcess) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setProcessingMatchId(matchSlug);
|
setProcessingMatchId(matchToProcess);
|
||||||
await matchesAPI.deleteMatch(matchSlug);
|
await matchesAPI.deleteMatch(matchToProcess);
|
||||||
toast.success('Request cancelled.');
|
toast.success('Request cancelled.');
|
||||||
await loadDashboard();
|
await loadDashboard();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -134,6 +150,8 @@ const DashboardPage = () => {
|
|||||||
toast.error('Failed to cancel request. Please try again.');
|
toast.error('Failed to cancel request. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setProcessingMatchId(null);
|
setProcessingMatchId(null);
|
||||||
|
setShowCancelModal(false);
|
||||||
|
setMatchToProcess(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -316,6 +334,38 @@ const DashboardPage = () => {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Loader2, Users } from 'lucide-react';
|
|||||||
import { connectSocket, disconnectSocket, getSocket } from '../services/socket';
|
import { connectSocket, disconnectSocket, getSocket } from '../services/socket';
|
||||||
import { MatchCard } from '../components/matches';
|
import { MatchCard } from '../components/matches';
|
||||||
import { MATCH_STATUS, MATCH_FILTER } from '../constants';
|
import { MATCH_STATUS, MATCH_FILTER } from '../constants';
|
||||||
|
import ConfirmationModal from '../components/modals/ConfirmationModal';
|
||||||
|
|
||||||
const MatchesPage = () => {
|
const MatchesPage = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@@ -16,6 +17,8 @@ const MatchesPage = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [filter, setFilter] = useState(MATCH_FILTER.ALL);
|
const [filter, setFilter] = useState(MATCH_FILTER.ALL);
|
||||||
const [processingMatchId, setProcessingMatchId] = useState(null);
|
const [processingMatchId, setProcessingMatchId] = useState(null);
|
||||||
|
const [showRejectModal, setShowRejectModal] = useState(false);
|
||||||
|
const [matchToReject, setMatchToReject] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadMatches();
|
loadMatches();
|
||||||
@@ -90,22 +93,28 @@ const MatchesPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReject = async (matchSlug) => {
|
const handleReject = (matchSlug) => {
|
||||||
if (!confirm('Are you sure you want to reject this match request?')) {
|
setMatchToReject(matchSlug);
|
||||||
return;
|
setShowRejectModal(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const confirmReject = async () => {
|
||||||
|
if (!matchToReject) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setProcessingMatchId(matchSlug);
|
setProcessingMatchId(matchToReject);
|
||||||
await matchesAPI.deleteMatch(matchSlug);
|
await matchesAPI.deleteMatch(matchToReject);
|
||||||
|
|
||||||
// Remove from list
|
// 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) {
|
} catch (error) {
|
||||||
console.error('Failed to reject match:', error);
|
console.error('Failed to reject match:', error);
|
||||||
toast.error('Failed to reject match. Please try again.');
|
toast.error('Failed to reject match. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setProcessingMatchId(null);
|
setProcessingMatchId(null);
|
||||||
|
setShowRejectModal(false);
|
||||||
|
setMatchToReject(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,6 +241,22 @@ const MatchesPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</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>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { Mail, User, Calendar, Trash2, Check, Eye, AlertCircle, Loader2, Filter } from 'lucide-react';
|
||||||
import Layout from '../../components/layout/Layout';
|
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
|
// This would normally come from an admin API module, but for now we'll add it inline
|
||||||
const adminContactAPI = {
|
const adminContactAPI = {
|
||||||
@@ -73,6 +75,8 @@ export default function ContactMessagesPage() {
|
|||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [statusFilter, setStatusFilter] = useState('all');
|
const [statusFilter, setStatusFilter] = useState('all');
|
||||||
const [selectedMessage, setSelectedMessage] = useState(null);
|
const [selectedMessage, setSelectedMessage] = useState(null);
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
const [messageToDelete, setMessageToDelete] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const userData = localStorage.getItem('user');
|
const userData = localStorage.getItem('user');
|
||||||
@@ -115,19 +119,27 @@ export default function ContactMessagesPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = (id) => {
|
||||||
if (!confirm('Are you sure you want to delete this message?')) {
|
setMessageToDelete(id);
|
||||||
return;
|
setShowDeleteModal(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!messageToDelete) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await adminContactAPI.deleteMessage(id);
|
await adminContactAPI.deleteMessage(messageToDelete);
|
||||||
await fetchMessages();
|
await fetchMessages();
|
||||||
if (selectedMessage?.id === id) {
|
if (selectedMessage?.id === messageToDelete) {
|
||||||
setSelectedMessage(null);
|
setSelectedMessage(null);
|
||||||
}
|
}
|
||||||
|
toast.success('Message deleted successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to delete message:', 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>
|
</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>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user