mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 21:15:23 +02:00
Implement premium Confirmation Modal for all data-modifying actions
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { X, AlertTriangle, CheckCircle2, Info, Loader2 } from 'lucide-react';
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
interface ConfirmModalProps {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
title: string;
|
||||
message: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
type?: 'info' | 'warning' | 'danger';
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export function ConfirmModal({
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
title,
|
||||
message,
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
type = 'info',
|
||||
isLoading = false
|
||||
}: ConfirmModalProps) {
|
||||
const [shouldRender, setShouldRender] = useState(isOpen);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) setShouldRender(true);
|
||||
}, [isOpen]);
|
||||
|
||||
const onAnimationEnd = () => {
|
||||
if (!isOpen) setShouldRender(false);
|
||||
};
|
||||
|
||||
if (!shouldRender) return null;
|
||||
|
||||
const Icon = type === 'danger' || type === 'warning' ? AlertTriangle : Info;
|
||||
const iconColor = type === 'danger' ? 'text-red-500 bg-red-500/10' : type === 'warning' ? 'text-amber-500 bg-amber-500/10' : 'text-blue-500 bg-blue-500/10';
|
||||
const confirmBtnClass = type === 'danger' ? 'bg-red-600 hover:bg-red-700' : type === 'warning' ? 'bg-amber-600 hover:bg-amber-700' : 'bg-blue-600 hover:bg-blue-700';
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
"fixed inset-0 z-[110] flex items-center justify-center p-4 transition-all duration-300",
|
||||
isOpen ? "bg-black/80 backdrop-blur-sm opacity-100" : "bg-transparent backdrop-blur-0 opacity-0 pointer-events-none"
|
||||
)}>
|
||||
<div
|
||||
onAnimationEnd={onAnimationEnd}
|
||||
className={cn(
|
||||
"bg-slate-900 border border-slate-700 w-full max-w-md rounded-2xl shadow-2xl overflow-hidden transition-all duration-300 transform",
|
||||
isOpen ? "scale-100 translate-y-0" : "scale-95 translate-y-4"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b border-slate-700/50 bg-slate-800/20">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={cn("p-2 rounded-lg", iconColor)}>
|
||||
<Icon className="w-5 h-5" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-white uppercase tracking-tight text-xs">{title}</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="p-1.5 text-slate-500 hover:text-white hover:bg-slate-700 rounded-md transition-all"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<p className="text-slate-300 leading-relaxed font-medium">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-3 p-4 bg-slate-800/40 border-t border-slate-700/50">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
className="px-5 py-2.5 text-slate-400 hover:text-white font-bold text-xs uppercase tracking-widest transition-all disabled:opacity-50"
|
||||
>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
disabled={isLoading}
|
||||
className={cn(
|
||||
"px-6 py-2.5 text-white font-black text-xs uppercase tracking-widest rounded-lg shadow-lg transition-all active:scale-95 flex items-center gap-2 disabled:opacity-50",
|
||||
confirmBtnClass
|
||||
)}
|
||||
>
|
||||
{isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <CheckCircle2 className="w-4 h-4" />}
|
||||
{confirmText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user