import React, { useCallback, useMemo, useState } from 'react'; import { Button, Group, Modal, Stack, Text } from '@mantine/core'; import { useForm, FormProvider } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import type { BaseEntity } from '@repo/core-api/data-services'; import type { ActionModalConfig, ActionModalState, ModuleActionType } from '../../entities/entity'; import { ModuleAction } from '../../entities/entity'; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface ActionConfirmationModalProps { /** Current modal state (opened, action, data, config) */ modalState: ActionModalState; /** Close the modal */ onClose: () => void; /** Execute the action with optional meta from form */ onExecute: (action: ModuleActionType, data: E, meta?: Record) => Promise; /** Translation function scoped to [moduleNamespace, 'common'] */ t: (key: string, options?: Record) => string; } // --------------------------------------------------------------------------- // Default Translation Keys per Action // --------------------------------------------------------------------------- export const ACTION_TRANSLATION_MAP: Record = { [ModuleAction.DELETE]: { titleKey: 'common:confirmDialog.delete.title', confirmKey: 'common:actions.delete', descriptionKey: 'common:confirmDialog.delete.description', }, [ModuleAction.ACTIVATE]: { titleKey: 'common:confirmDialog.activate.title', confirmKey: 'common:actions.activate', descriptionKey: 'common:confirmDialog.activate.description', }, [ModuleAction.DEACTIVATE]: { titleKey: 'common:confirmDialog.deactivate.title', confirmKey: 'common:actions.deactivate', descriptionKey: 'common:confirmDialog.deactivate.description', }, [ModuleAction.CONFIRM]: { titleKey: 'common:confirmDialog.confirm.title', confirmKey: 'common:actions.confirm', descriptionKey: 'common:confirmDialog.confirm.description', }, [ModuleAction.CANCEL]: { titleKey: 'common:confirmDialog.cancel.title', confirmKey: 'common:actions.cancel', descriptionKey: 'common:confirmDialog.cancel.description', }, [ModuleAction.ROLLBACK]: { titleKey: 'common:confirmDialog.rollback.title', confirmKey: 'common:actions.rollback', descriptionKey: 'common:confirmDialog.rollback.description', }, [ModuleAction.HOLD]: { titleKey: 'common:confirmDialog.hold.title', confirmKey: 'common:actions.hold', descriptionKey: 'common:confirmDialog.hold.description', }, }; // --------------------------------------------------------------------------- // Inner Form Body (memoized to prevent re-renders of the entire modal) // --------------------------------------------------------------------------- const ModalFormBody = React.memo(function ModalFormBody< TMeta extends Record = Record, >({ renderBody, form, }: { renderBody: NonNullable['renderBody']>; form?: ReturnType>; }) { return <>{renderBody(form)}; }) as >(props: { renderBody: NonNullable['renderBody']>; form?: ReturnType>; }) => React.ReactElement; // --------------------------------------------------------------------------- // ActionConfirmationModal // --------------------------------------------------------------------------- /** * Controlled confirmation modal for lifecycle actions (delete, activate, etc.). * * Supports three modes: * 1. **Simple** — no custom body, just title + default description + confirm/cancel. * 2. **Static body** — custom body without form (informational content). * 3. **Form body** — custom body with RHF FormProvider for validation + meta payload. * * @performance * - Modal is NOT mounted when `opened === false` (uses `keepMounted={false}`). * - Form is created with `mode: 'onSubmit'` to avoid re-renders on every keystroke. * - Inner body is wrapped in React.memo to isolate re-renders from modal state changes. */ export function ActionConfirmationModal(props: ActionConfirmationModalProps) { const { modalState, onClose, onExecute, t } = props; const { opened, action, data, config } = modalState; const [isSubmitting, setIsSubmitting] = useState(false); // Determine if this modal has a form (schema + defaultValues provided) const hasForm = Boolean(config?.schema && config?.defaultValues); // Create form instance only when form mode is active. // `useForm` is always called (Rules of Hooks) but resolver/defaultValues // are conditionally set so it becomes a no-op when hasForm is false. const form = useForm>({ mode: 'onSubmit', resolver: config?.schema ? zodResolver(config.schema as any) : undefined, defaultValues: config?.defaultValues ?? {}, }); // Resolve translation keys for the current action const translations = useMemo(() => { const actionKey = action ?? ''; const map = ACTION_TRANSLATION_MAP[actionKey]; return { title: config?.title ?? (map ? t(map.titleKey) : (action ?? '')), confirmLabel: config?.confirmLabel ?? (map ? t(map.confirmKey) : t('common:actions.confirm')), cancelLabel: config?.cancelLabel ?? t('common:actions.cancel'), description: map ? t(map.descriptionKey) : '', }; }, [action, config?.title, config?.confirmLabel, config?.cancelLabel, t]); // Determine button color based on action type const confirmColor = useMemo(() => { if (action === ModuleAction.DELETE) return 'red'; // if (action === ModuleAction.DEACTIVATE || action === ModuleAction.CANCEL) return 'orange'; return 'brand'; }, [action]); // Handle confirm button click const handleConfirmClick = useCallback(async () => { if (!action || !data) return; if (hasForm) { // Trigger RHF validation, then execute if valid const isValid = await form.trigger(); if (!isValid) return; const formValues = form.getValues(); setIsSubmitting(true); try { await onExecute(action, data, formValues); } finally { setIsSubmitting(false); } } else { // No form — execute directly setIsSubmitting(true); try { await onExecute(action, data, undefined); } finally { setIsSubmitting(false); } } }, [action, data, hasForm, form, onExecute]); // Handle modal close (prevent close while submitting) const handleClose = useCallback(() => { if (isSubmitting) return; form.reset(); onClose(); }, [isSubmitting, form, onClose]); return ( {/* Body: custom or default description */} {config?.renderBody ? ( hasForm ? ( ) : ( ) ) : ( translations.description && ( {translations.description} ) )} {/* Footer actions */} ); }