From 76be8a44191f02170858ab8ed3a6f2a91bac7cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Fri, 5 Dec 2025 22:14:09 +0100 Subject: [PATCH] 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. --- frontend/src/pages/DashboardPage.jsx | 66 ++++++++++++++++--- frontend/src/pages/MatchesPage.jsx | 39 +++++++++-- .../src/pages/admin/ContactMessagesPage.jsx | 38 +++++++++-- 3 files changed, 122 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 7359c94..4c03482 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -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 = () => { )} + + {/* Decline Confirmation Modal */} + { + 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 */} + { + 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..." + /> ); }; diff --git a/frontend/src/pages/MatchesPage.jsx b/frontend/src/pages/MatchesPage.jsx index 25d1033..10afa76 100644 --- a/frontend/src/pages/MatchesPage.jsx +++ b/frontend/src/pages/MatchesPage.jsx @@ -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 = () => { )} + + {/* Reject Confirmation Modal */} + { + 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..." + /> ); }; diff --git a/frontend/src/pages/admin/ContactMessagesPage.jsx b/frontend/src/pages/admin/ContactMessagesPage.jsx index 84a8f52..26884a6 100644 --- a/frontend/src/pages/admin/ContactMessagesPage.jsx +++ b/frontend/src/pages/admin/ContactMessagesPage.jsx @@ -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() { + + {/* Delete Confirmation Modal */} + { + 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" + /> ); }