27d2ab0f0d
v1.0 Phase 1.1 — repo-wide lint/format baseline. - eslint.config.mjs (flat config) lints server, client, shared - .prettierrc.json, .prettierignore, .editorconfig, .nvmrc - Root package.json holds shared devDeps; per-package scripts keep their typecheck + test runners - Fix 7 lint issues surfaced by the baseline run: - TicketDetail.tsx: replace ternary-with-side-effects with if/else - admin/Users.tsx: escape apostrophe in JSX - errorHandler.ts: typed err as unknown with ErrorLike refinement - users.ts: Prisma.UserUpdateInput instead of Record<string, any> - seed.ts: drop unused goddard binding - Run prettier across tracked sources for a clean formatting baseline
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
import { ReactNode, useEffect } from 'react';
|
|
import { X } from 'lucide-react';
|
|
|
|
interface ModalProps {
|
|
title: string;
|
|
onClose: () => void;
|
|
children: ReactNode;
|
|
size?: 'md' | 'lg';
|
|
}
|
|
|
|
export default function Modal({ title, onClose, children, size = 'md' }: ModalProps) {
|
|
useEffect(() => {
|
|
const handler = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
document.addEventListener('keydown', handler);
|
|
return () => document.removeEventListener('keydown', handler);
|
|
}, [onClose]);
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) onClose();
|
|
}}
|
|
>
|
|
<div
|
|
className={`bg-gray-900 border border-gray-800 rounded-xl shadow-2xl w-full mx-4 ${size === 'lg' ? 'max-w-2xl' : 'max-w-md'}`}
|
|
>
|
|
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-800">
|
|
<h3 className="text-base font-semibold text-gray-100">{title}</h3>
|
|
<button onClick={onClose} className="text-gray-500 hover:text-gray-300 transition-colors">
|
|
<X size={18} />
|
|
</button>
|
|
</div>
|
|
<div className="px-6 py-5">{children}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|