From 96603d31f7191a44e1ff22207f2cd9288c68361c Mon Sep 17 00:00:00 2001 From: alisaza Date: Sun, 13 Sep 2026 10:14:18 +0330 Subject: [PATCH] feat(alert-modal): integrate alert modal for confirmation actions Enhanced various components to utilize the alert modal for user confirmations before executing critical actions. This includes marking messages as read, saving notification rules, restoring reviews, sending replies, changing ticket statuses, and managing event commissions. The integration improves user experience by ensuring actions are intentional and provides clear feedback on the outcomes. --- app/(dashboard)/contact-messages/page.tsx | 18 +- .../NotificationRulesPanel.test.tsx | 8 + .../_components/NotificationRulesPanel.tsx | 12 +- app/(dashboard)/reviews/page.tsx | 4 +- app/(dashboard)/support-tickets/[id]/page.tsx | 46 +++- .../users/[id]/_components/UserEditModal.tsx | 16 +- e2e/admin/event-management.spec.ts | 6 +- e2e/admin/event-review-workflow.spec.ts | 1 + features/events/detail/AdminEventDetail.tsx | 96 +++++---- .../detail/EventDiscountsPanel.test.tsx | 7 + .../events/detail/EventDiscountsPanel.tsx | 95 +++++---- .../AdminEventBookingsTab.tsx | 23 +- .../AdminEventCommissionModal.test.tsx | 7 + .../AdminEventCommissionModal.tsx | 45 ++-- .../AdminEventLifecycleActions.test.tsx | 54 ++++- .../AdminEventLifecycleActions.tsx | 196 +++++++++--------- .../adminEventLifecycleHints.test.ts | 34 +++ .../adminEventLifecycleHints.ts | 28 +++ texts/events.ts | 6 + 19 files changed, 473 insertions(+), 229 deletions(-) create mode 100644 features/events/detail/admin-event-detail/adminEventLifecycleHints.test.ts create mode 100644 features/events/detail/admin-event-detail/adminEventLifecycleHints.ts diff --git a/app/(dashboard)/contact-messages/page.tsx b/app/(dashboard)/contact-messages/page.tsx index 8813031..444a8ad 100644 --- a/app/(dashboard)/contact-messages/page.tsx +++ b/app/(dashboard)/contact-messages/page.tsx @@ -13,6 +13,7 @@ import AdminTableViewButton from '@/components/ui/AdminTableViewButton' import StatusChip from '@/components/ui/StatusChip' import axiosInstance from '@/config/axios' import useAdminMutation from '@/hooks/useAdminMutation' +import useAlertModal from '@/hooks/useAlertModal' import { coerceToString } from '@/helpers' import { formatIranianMobile, formatPersianDate, truncateValue } from '@/lib/formatters' import { getBooleanStatus } from '@/constants/status' @@ -79,9 +80,16 @@ const columns: PaginationListColumnType[] = [ ] const ContactMessagesPage = () => { + const { showAlert } = useAlertModal() const { pendingId, runAction } = useAdminMutation({ url: API_ROUTES.CONTACT_MESSAGES.ADMIN_LIST }) const [selected, setSelected] = useState(null) + const handleMarkRead = (message: ContactMessageRow) => { + showAlert('این پیام به‌عنوان خوانده‌شده علامت‌گذاری شود؟', () => + runAction(message.id, () => axiosInstance.patch(API_ROUTES.CONTACT_MESSAGES.ADMIN_READ(message.id)), 'پیام خوانده شد') + ) + } + return (
@@ -150,13 +158,9 @@ const ContactMessagesPage = () => { isLoading={pendingId === message.id} size="sm" variant="light" - onClick={() => - void runAction( - message.id, - () => axiosInstance.patch(API_ROUTES.CONTACT_MESSAGES.ADMIN_READ(message.id)), - 'پیام خوانده شد' - ) - } + onClick={() => { + handleMarkRead(message) + }} > diff --git a/app/(dashboard)/notifications/_components/NotificationRulesPanel.test.tsx b/app/(dashboard)/notifications/_components/NotificationRulesPanel.test.tsx index eaa87b7..77c620a 100644 --- a/app/(dashboard)/notifications/_components/NotificationRulesPanel.test.tsx +++ b/app/(dashboard)/notifications/_components/NotificationRulesPanel.test.tsx @@ -16,7 +16,12 @@ vi.mock('@/config/axios', () => ({ patch: axiosMocks.patch, }, })) +const showAlert = vi.fn((_message: string, onConfirm?: () => unknown) => { + void onConfirm?.() +}) + vi.mock('@/lib/toast', () => ({ addToast: vi.fn() })) +vi.mock('@/hooks/useAlertModal', () => ({ default: () => ({ showAlert }) })) vi.mock('@/components/formElements/Input', () => ({ default: ({ description, @@ -109,6 +114,9 @@ describe('NotificationRulesPanel', () => { }) beforeEach(() => { + showAlert.mockImplementation((_message: string, onConfirm?: () => unknown) => { + void onConfirm?.() + }) axiosMocks.get.mockReset() axiosMocks.patch.mockReset() axiosMocks.get.mockResolvedValue({ diff --git a/app/(dashboard)/notifications/_components/NotificationRulesPanel.tsx b/app/(dashboard)/notifications/_components/NotificationRulesPanel.tsx index 4b6e539..1eb170f 100644 --- a/app/(dashboard)/notifications/_components/NotificationRulesPanel.tsx +++ b/app/(dashboard)/notifications/_components/NotificationRulesPanel.tsx @@ -10,6 +10,7 @@ import Button from '@/components/formElements/Button' import Input from '@/components/formElements/Input' import AdminState from '@/components/feedback/AdminState' import axiosInstance from '@/config/axios' +import useAlertModal from '@/hooks/useAlertModal' import { unwrapApiData, type ApiSuccessBody } from '@/services/apiResponse' import { API_ROUTES } from '@/services/config' import { extractServerErrorDetail } from '@/services/errorHandler' @@ -52,6 +53,7 @@ const isDirtyDraft = (current: RuleDraft, saved: RuleDraft | undefined) => Boolean(saved) && JSON.stringify(current) !== JSON.stringify(saved) const NotificationRulesPanel = () => { + const { showAlert } = useAlertModal() const [rules, setRules] = useState([]) const [savedDrafts, setSavedDrafts] = useState>({}) const [loading, setLoading] = useState(true) @@ -111,6 +113,12 @@ const NotificationRulesPanel = () => { } } + const confirmSave = (rule: Rule) => { + showAlert(`تغییرات قاعدهٔ «${rule.displayName}» ذخیره شود؟`, () => { + void save(rule) + }) + } + return (
@@ -142,7 +150,9 @@ const NotificationRulesPanel = () => { rule={rule} saving={saving === rule.eventKey} onChange={change} - onSave={() => void save(rule)} + onSave={() => { + confirmSave(rule) + }} /> ))}
diff --git a/app/(dashboard)/reviews/page.tsx b/app/(dashboard)/reviews/page.tsx index ddf5c99..7e9c602 100644 --- a/app/(dashboard)/reviews/page.tsx +++ b/app/(dashboard)/reviews/page.tsx @@ -115,7 +115,9 @@ const ReviewsPage = () => { } const handleRestore = (row: ReviewRow) => { - void runAction(row.id, () => axiosInstance.patch(API_ROUTES.REVIEWS.ADMIN_RESTORE(row.id)), 'نظر بازگردانده شد') + showAlert('این نظر دوباره در نمایش عمومی قرار گیرد؟', () => + runAction(row.id, () => axiosInstance.patch(API_ROUTES.REVIEWS.ADMIN_RESTORE(row.id)), 'نظر بازگردانده شد') + ) } const handleDelete = (row: ReviewRow) => { diff --git a/app/(dashboard)/support-tickets/[id]/page.tsx b/app/(dashboard)/support-tickets/[id]/page.tsx index 7c646ad..7fd2e0d 100644 --- a/app/(dashboard)/support-tickets/[id]/page.tsx +++ b/app/(dashboard)/support-tickets/[id]/page.tsx @@ -58,15 +58,39 @@ const AdminSupportTicketDetail = () => { } } + const confirmSend = () => { + if (!reply.trim()) return + + showAlert('این پاسخ ثبت و پیامک اطلاع‌رسانی برای کاربر ارسال شود؟', () => { + void send() + }) + } + + const confirmStatusChange = (nextStatus: string) => { + if (!ticket || nextStatus === ticket.status) return + + if (nextStatus === 'closed') { + showAlert( + 'این تیکت بسته شود؟ کاربر دیگر نمی‌تواند پیام بفرستد و در صورت بستن توسط ادمین، خودش نمی‌تواند دوباره باز کند.', + () => { + void changeStatus('closed') + }, + undefined, + { dangerAccept: true } + ) + + return + } + + const label = SUPPORT_ADMIN_STATUS_LABELS[nextStatus as keyof typeof SUPPORT_ADMIN_STATUS_LABELS] ?? nextStatus + + showAlert(`وضعیت تیکت به «${label}» تغییر کند؟`, () => { + void changeStatus(nextStatus) + }) + } + const confirmClose = () => { - showAlert( - 'این تیکت بسته شود؟ کاربر دیگر نمی‌تواند پیام بفرستد و در صورت بستن توسط ادمین، خودش نمی‌تواند دوباره باز کند.', - () => { - void changeStatus('closed') - }, - undefined, - { dangerAccept: true } - ) + confirmStatusChange('closed') } return ( @@ -104,7 +128,9 @@ const AdminSupportTicketDetail = () => {