Translate all hardcoded English strings to use i18n translation keys: Components: - TransactionDetailModal: payment details, refunds, technical info - ConnectOnboarding/ConnectOnboardingEmbed: Stripe Connect setup - StripeApiKeysForm: API key management - DomainPurchase: domain registration flow - Sidebar: navigation labels - Schedule/Sidebar, PendingSidebar: scheduler UI - MasqueradeBanner: masquerade status - Dashboard widgets: metrics, capacity, customers, tickets - Marketing: PricingTable, PluginShowcase, BenefitsSection - ConfirmationModal, ServiceList: common UI Pages: - Staff: invitation flow, role management - Customers: form placeholders - Payments: transactions, payouts, billing - BookingSettings: URL and redirect configuration - TrialExpired: upgrade prompts and features - PlatformSettings, PlatformBusinesses: admin UI - HelpApiDocs: API documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
4.4 KiB
TypeScript
135 lines
4.4 KiB
TypeScript
import React from 'react';
|
|
import { X, AlertTriangle, CheckCircle, Info, AlertCircle } from 'lucide-react';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
type ModalVariant = 'info' | 'warning' | 'danger' | 'success';
|
|
|
|
interface ConfirmationModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
onConfirm: () => void;
|
|
title: string;
|
|
message: string | React.ReactNode;
|
|
confirmText?: string;
|
|
cancelText?: string;
|
|
variant?: ModalVariant;
|
|
isLoading?: boolean;
|
|
}
|
|
|
|
const variantConfig: Record<ModalVariant, {
|
|
icon: React.ReactNode;
|
|
iconBg: string;
|
|
confirmButtonClass: string;
|
|
}> = {
|
|
info: {
|
|
icon: <Info size={24} className="text-blue-600 dark:text-blue-400" />,
|
|
iconBg: 'bg-blue-100 dark:bg-blue-900/30',
|
|
confirmButtonClass: 'bg-blue-600 hover:bg-blue-700 text-white',
|
|
},
|
|
warning: {
|
|
icon: <AlertTriangle size={24} className="text-amber-600 dark:text-amber-400" />,
|
|
iconBg: 'bg-amber-100 dark:bg-amber-900/30',
|
|
confirmButtonClass: 'bg-amber-600 hover:bg-amber-700 text-white',
|
|
},
|
|
danger: {
|
|
icon: <AlertCircle size={24} className="text-red-600 dark:text-red-400" />,
|
|
iconBg: 'bg-red-100 dark:bg-red-900/30',
|
|
confirmButtonClass: 'bg-red-600 hover:bg-red-700 text-white',
|
|
},
|
|
success: {
|
|
icon: <CheckCircle size={24} className="text-green-600 dark:text-green-400" />,
|
|
iconBg: 'bg-green-100 dark:bg-green-900/30',
|
|
confirmButtonClass: 'bg-green-600 hover:bg-green-700 text-white',
|
|
},
|
|
};
|
|
|
|
const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
|
|
isOpen,
|
|
onClose,
|
|
onConfirm,
|
|
title,
|
|
message,
|
|
confirmText,
|
|
cancelText,
|
|
variant = 'info',
|
|
isLoading = false,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
|
|
if (!isOpen) return null;
|
|
|
|
const config = variantConfig[variant];
|
|
|
|
const handleConfirm = () => {
|
|
onConfirm();
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-xl w-full max-w-md mx-4">
|
|
{/* Modal Header */}
|
|
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-center gap-3">
|
|
<div className={`p-2 rounded-lg ${config.iconBg}`}>
|
|
{config.icon}
|
|
</div>
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{title}</h3>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
disabled={isLoading}
|
|
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors disabled:opacity-50"
|
|
>
|
|
<X size={20} className="text-gray-500 dark:text-gray-400" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Modal Body */}
|
|
<div className="p-6">
|
|
<div className="text-gray-600 dark:text-gray-300">
|
|
{typeof message === 'string' ? <p>{message}</p> : message}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modal Footer */}
|
|
<div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200 dark:border-gray-700">
|
|
<button
|
|
onClick={onClose}
|
|
disabled={isLoading}
|
|
className="px-4 py-2 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50"
|
|
>
|
|
{cancelText || t('common.cancel')}
|
|
</button>
|
|
<button
|
|
onClick={handleConfirm}
|
|
disabled={isLoading}
|
|
className={`px-4 py-2 rounded-lg font-medium transition-colors disabled:opacity-50 flex items-center gap-2 ${config.confirmButtonClass}`}
|
|
>
|
|
{isLoading && (
|
|
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
|
<circle
|
|
className="opacity-25"
|
|
cx="12"
|
|
cy="12"
|
|
r="10"
|
|
stroke="currentColor"
|
|
strokeWidth="4"
|
|
fill="none"
|
|
/>
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
)}
|
|
{confirmText || t('common.confirm')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ConfirmationModal;
|