'use client'; import { FunctionComponent, ReactNode, useState } from 'react'; import { CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, } from '@mui/material'; import AppButton from '../AppButton'; export interface ConfirmDialogProps { open: boolean; /** Already-translated title. */ title: string; /** Already-translated body (string or node). */ body?: ReactNode; confirmLabel: string; cancelLabel: string; /** Called with the entered reason (undefined when `requireReason` is false). */ onConfirm: (reason?: string) => void; onClose: () => void; loading?: boolean; /** When true a required reason field shows; confirm is disabled until it is non-empty. */ requireReason?: boolean; reasonLabel?: string; reasonPlaceholder?: string; /** * When set, confirm stays disabled until the typed value case-insensitively matches ONE of these * (e.g. the literal word «تایید» OR the exact amount digit-string) — the guard for an irreversible * action like running a payout batch. A gate, not a payload: `onConfirm`'s signature is unchanged, the * typed value itself is never passed to the caller. */ requireTypedConfirmation?: string[]; typedConfirmationLabel?: string; typedConfirmationPlaceholder?: string; /** MUI color for the confirm button — `error` for a destructive action. */ confirmColor?: 'primary' | 'error' | 'secondary'; } /** * The shared confirmation dialog behind every audited/irreversible action — approve/reject a * verification, run/retry a payout, save a config, resolve an alert, archive a patient, delete an * address. Optionally collects a **required reason** (reject/hide/resolve) and disables confirm until it * is provided. The dialog owns the reason field only; the caller owns the mutation and closes on success. * `loading` disables the buttons and shows a spinner so a double-submit is impossible. Promoted from * `components/admin` (kept there as a thin alias) — the contract is unchanged. * @component ConfirmDialog */ const ConfirmDialog: FunctionComponent = ({ open, title, body, confirmLabel, cancelLabel, onConfirm, onClose, loading = false, requireReason = false, reasonLabel, reasonPlaceholder, requireTypedConfirmation, typedConfirmationLabel, typedConfirmationPlaceholder, confirmColor = 'primary', }) => { const [reason, setReason] = useState(''); const [typedValue, setTypedValue] = useState(''); const close = () => { setReason(''); setTypedValue(''); onClose(); }; const confirm = () => { onConfirm(requireReason ? reason.trim() : undefined); setReason(''); setTypedValue(''); }; const confirmDisabled = loading || (requireReason && reason.trim().length === 0) || (requireTypedConfirmation != null && !requireTypedConfirmation.some((v) => v.trim().toLowerCase() === typedValue.trim().toLowerCase())); return ( {title} {body ? ( typeof body === 'string' ? ( {body} ) : ( body ) ) : null} {requireReason ? ( setReason(e.target.value)} label={reasonLabel} placeholder={reasonPlaceholder} sx={{ mt: 2 }} /> ) : null} {requireTypedConfirmation ? ( setTypedValue(e.target.value)} label={typedConfirmationLabel} placeholder={typedConfirmationPlaceholder} sx={{ mt: 2 }} /> ) : null} {cancelLabel} : undefined} > {confirmLabel} ); }; export default ConfirmDialog;