IAM, package, luggage, app health, rate limit, and more

This commit is contained in:
Stephanos A
2026-06-24 14:02:51 +03:00
parent e10b013b62
commit 86760933e8
63 changed files with 3475 additions and 280 deletions

View File

@@ -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',
};

View File

@@ -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>
);
}

View File

@@ -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>