mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 09:28:19 +00:00
IAM, package, luggage, app health, rate limit, and more
This commit is contained in:
@@ -18,8 +18,8 @@ interface ActionButtonProps {
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-[rgb(20,113,76)] text-white hover:bg-[rgb(16,90,61)] shadow-sm',
|
||||
secondary: 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600',
|
||||
danger: 'bg-[rgb(20,113,76)] text-white hover:bg-[rgb(16,90,61)] shadow-sm',
|
||||
secondary: 'bg-muted text-foreground hover:bg-muted/80 border border-border/60',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 shadow-sm',
|
||||
success: 'bg-green-600 text-white hover:bg-green-700 shadow-sm',
|
||||
export: 'bg-[rgb(20,113,76)] text-white hover:bg-[rgb(16,90,61)] shadow-sm',
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { AlertCircle, AlertTriangle } from 'lucide-react';
|
||||
import Modal from './Modal';
|
||||
import ActionButton from './ActionButton'
|
||||
import { useEffect } from 'react';
|
||||
import { AlertTriangle, AlertCircle, Info, Loader2, X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import ActionButton from './ActionButton';
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
isOpen: boolean;
|
||||
@@ -31,43 +32,99 @@ export default function ConfirmDialog({
|
||||
warning,
|
||||
error,
|
||||
}: ConfirmDialogProps) {
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = isOpen ? 'hidden' : 'unset';
|
||||
return () => { document.body.style.overflow = 'unset'; };
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title} size="sm">
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-3">
|
||||
{isDanger && (
|
||||
<AlertCircle className="h-6 w-6 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
)}
|
||||
<p className="text-foreground">{message}</p>
|
||||
</div>
|
||||
{warning && (
|
||||
<div className="rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-3 flex gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-semibold text-amber-900 dark:text-amber-200 text-sm">Warning</p>
|
||||
<p className="text-amber-800 dark:text-amber-300 text-sm mt-1">{warning}</p>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/60 backdrop-blur-sm"
|
||||
onClick={!isLoading ? onClose : undefined}
|
||||
/>
|
||||
|
||||
{/* Panel */}
|
||||
<div className={cn(
|
||||
'relative w-full max-w-md z-10 rounded-2xl shadow-2xl border border-border/50 bg-background animate-fade-up overflow-hidden',
|
||||
)}>
|
||||
{/* Top accent */}
|
||||
<div className={cn(
|
||||
'h-1 w-full',
|
||||
isDanger
|
||||
? 'bg-gradient-to-r from-red-500 to-red-400'
|
||||
: 'bg-gradient-to-r from-[rgb(20,113,76)] to-emerald-400',
|
||||
)} />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between px-6 pt-5 pb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={cn(
|
||||
'flex items-center justify-center w-10 h-10 rounded-full shrink-0',
|
||||
isDanger
|
||||
? 'bg-red-100 dark:bg-red-900/30'
|
||||
: 'bg-emerald-100 dark:bg-emerald-900/30',
|
||||
)}>
|
||||
{isDanger
|
||||
? <AlertTriangle className="w-5 h-5 text-red-600 dark:text-red-400" />
|
||||
: <Info className="w-5 h-5 text-emerald-600 dark:text-emerald-400" />
|
||||
}
|
||||
</div>
|
||||
<h2 className="text-base font-semibold text-foreground">{title}</h2>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 p-3 flex gap-3">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-red-800 dark:text-red-300 text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 pt-4">
|
||||
{!isLoading && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex items-center justify-center w-7 h-7 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors ml-2 shrink-0"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="px-6 pb-2 space-y-3">
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">{message}</p>
|
||||
|
||||
{warning && (
|
||||
<div className="flex gap-3 rounded-xl bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 px-4 py-3">
|
||||
<AlertTriangle className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-amber-800 dark:text-amber-300 leading-relaxed">{warning}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="flex gap-3 rounded-xl bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 px-4 py-3">
|
||||
<AlertCircle className="h-4 w-4 text-red-600 dark:text-red-400 shrink-0 mt-0.5" />
|
||||
<p className="text-xs text-red-800 dark:text-red-300 leading-relaxed">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-end gap-2 px-6 py-4 border-t border-border/60 mt-3">
|
||||
<ActionButton variant="secondary" onClick={onClose} disabled={isLoading}>
|
||||
{cancelText}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
variant={isDanger ? 'danger' : 'primary'}
|
||||
onClick={onConfirm}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? 'Processing...' : confirmText}
|
||||
{confirmText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { ReactNode, useEffect, useRef } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -19,33 +20,57 @@ const sizeClasses = {
|
||||
};
|
||||
|
||||
export default function Modal({ isOpen, onClose, title, children, size = 'md' }: ModalProps) {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = 'unset';
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset';
|
||||
};
|
||||
document.body.style.overflow = isOpen ? 'hidden' : 'unset';
|
||||
return () => { document.body.style.overflow = 'unset'; };
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => document.removeEventListener('keydown', onKey);
|
||||
}, [isOpen, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50" onClick={onClose} />
|
||||
<div className={`relative w-full ${sizeClasses[size]} rounded-lg bg-background shadow-xl flex flex-col max-h-[90vh]`}>
|
||||
<div className="sticky top-0 bg-background border-b border-muted px-6 py-4 flex items-center justify-between z-10">
|
||||
<h2 className="text-xl font-semibold text-foreground">{title}</h2>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/60 backdrop-blur-sm transition-opacity"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Panel */}
|
||||
<div
|
||||
ref={panelRef}
|
||||
className={cn(
|
||||
'relative w-full flex flex-col max-h-[90vh] z-10',
|
||||
'bg-background rounded-2xl shadow-2xl border border-border/50',
|
||||
'animate-fade-up',
|
||||
sizeClasses[size],
|
||||
)}
|
||||
>
|
||||
{/* Accent bar */}
|
||||
<div className="h-1 w-full rounded-t-2xl bg-gradient-to-r from-[rgb(20,113,76)] to-emerald-400" />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-border/60">
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">{title}</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-lg p-1 hover:bg-muted"
|
||||
className="flex items-center justify-center w-8 h-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<X className="h-5 w-5 text-muted-foreground" />
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto flex-1 px-6 py-4">
|
||||
|
||||
{/* Body */}
|
||||
<div className="overflow-y-auto flex-1 px-6 py-5">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user