All files / src/components/organizer/NewsletterSubscribers UnsuppressDialog.tsx

93.33% Statements 14/15
75% Branches 9/12
100% Functions 4/4
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106                                                                    2x           8x 8x 8x 8x   8x 8x     8x 2x 2x   1x 1x         8x                                                                                            
/**
 * UnsuppressDialog — Confirmation dialog for unsuppressing a bounced subscriber
 *
 * Story 10.29 AC8: Admin visibility — bounce status in subscriber list
 */
 
import React from 'react';
import {
  Alert,
  Box,
  Button,
  CircularProgress,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  IconButton,
  Typography,
} from '@mui/material';
import { Close, WarningAmber } from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import type { components } from '@/types/generated/event-newsletter-api.types';
import { useBreakpoints } from '@/hooks/useBreakpoints';
import { useUnsuppressSubscriber } from '@/hooks/useNewsletterSubscribers';
 
type SubscriberResponse = components['schemas']['SubscriberResponse'];
 
interface UnsuppressDialogProps {
  open: boolean;
  subscriber: SubscriberResponse | null;
  onClose: () => void;
  onSuccess: () => void;
}
 
const UnsuppressDialog: React.FC<UnsuppressDialogProps> = ({
  open,
  subscriber,
  onClose,
  onSuccess,
}) => {
  const { t } = useTranslation('newsletterSubscribers');
  const { t: tCommon } = useTranslation('common');
  const { isMobile } = useBreakpoints();
  const mutation = useUnsuppressSubscriber();
 
  React.useEffect(() => {
    if (open) mutation.reset();
  }, [open, mutation]);
 
  const handleConfirm = () => {
    Iif (!subscriber?.id) return;
    mutation.mutate(subscriber.id, {
      onSuccess: () => {
        onSuccess();
        onClose();
      },
    });
  };
 
  return (
    <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth fullScreen={isMobile}>
      <DialogTitle>
        <Box display="flex" justifyContent="space-between" alignItems="center">
          <Box display="flex" alignItems="center" gap={1}>
            <WarningAmber color="warning" />
            <Typography variant="h6">{t('dialogs.unsuppress.title')}</Typography>
          </Box>
          <IconButton onClick={onClose} aria-label={tCommon('actions.close')}>
            <Close />
          </IconButton>
        </Box>
      </DialogTitle>
      <DialogContent dividers>
        <Typography>{t('dialogs.unsuppress.message', { email: subscriber?.email })}</Typography>
        <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
          {t('dialogs.unsuppress.detail', {
            bounceType: subscriber?.bounceType ?? '—',
            bounceCount: subscriber?.bounceCount ?? 0,
          })}
        </Typography>
        {mutation.isError && (
          <Alert severity="error" sx={{ mt: 2 }}>
            {tCommon('errors.unexpected')}
          </Alert>
        )}
      </DialogContent>
      <DialogActions>
        <Button onClick={onClose} variant="outlined">
          {tCommon('actions.cancel')}
        </Button>
        <Button
          onClick={handleConfirm}
          variant="contained"
          color="warning"
          disabled={mutation.isPending}
          data-testid="confirm-unsuppress"
        >
          {mutation.isPending ? <CircularProgress size={20} /> : t('actions.unsuppress')}
        </Button>
      </DialogActions>
    </Dialog>
  );
};
 
export default UnsuppressDialog;