diff --git a/ui/package-lock.json b/ui/package-lock.json index c8965cdb..06776549 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,6 +8,7 @@ "name": "ai-toolkit-ui", "version": "0.1.0", "dependencies": { + "@headlessui/react": "^2.2.0", "@prisma/client": "^6.3.1", "classnames": "^2.5.1", "lucide-react": "^0.475.0", @@ -15,6 +16,8 @@ "prisma": "^6.3.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-global-hooks": "^1.3.5", + "react-icons": "^5.5.0", "sqlite3": "^5.1.7" }, "devDependencies": { @@ -49,6 +52,54 @@ "tslib": "^2.4.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -56,6 +107,24 @@ "license": "MIT", "optional": true }, + "node_modules/@headlessui/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", + "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@tanstack/react-virtual": "^3.8.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -726,6 +795,86 @@ "@prisma/debug": "6.3.1" } }, + "node_modules/@react-aria/focus": { + "version": "3.19.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.1.tgz", + "integrity": "sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==", + "dependencies": { + "@react-aria/interactions": "^3.23.0", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.23.0.tgz", + "integrity": "sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-aria/utils": "^3.27.0", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz", + "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz", + "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==", + "dependencies": { + "@react-aria/ssr": "^3.9.7", + "@react-stately/utils": "^3.10.5", + "@react-types/shared": "^3.27.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz", + "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz", + "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -739,6 +888,31 @@ "tslib": "^2.8.0" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.0.tgz", + "integrity": "sha512-CchF0NlLIowiM2GxtsoKBkXA4uqSnY2KvnXo+kyUFD4a4ll6+J0qzoRsUPMwXV/H26lRsxgJIr/YmjYum2oEjg==", + "dependencies": { + "@tanstack/virtual-core": "3.13.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.0.tgz", + "integrity": "sha512-NBKJP3OIdmZY3COJdWkSonr50FMVIi+aj5ZJ7hI/DTpEKg2RMfo/KvP8A3B/zOSpMgIe52B5E2yn7rryULzA6g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1203,6 +1377,14 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2985,6 +3167,22 @@ "react": "^19.0.0" } }, + "node_modules/react-global-hooks": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/react-global-hooks/-/react-global-hooks-1.3.5.tgz", + "integrity": "sha512-xEvDSV6fkZ1ZAZ2qgrldw6d51awCtru6SzSVuWbrOi+tVIrGwroQLC2tdpFBYmszUCGOKi7UTuqOCYDyeJqvug==", + "peerDependencies": { + "react": "^16 || 17 || 18 || 19" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -3606,6 +3804,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/ui/package.json b/ui/package.json index 81d9c913..dc2787c8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,6 +10,7 @@ "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css,scss}\"" }, "dependencies": { + "@headlessui/react": "^2.2.0", "@prisma/client": "^6.3.1", "classnames": "^2.5.1", "lucide-react": "^0.475.0", @@ -17,6 +18,8 @@ "prisma": "^6.3.1", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-global-hooks": "^1.3.5", + "react-icons": "^5.5.0", "sqlite3": "^5.1.7" }, "devDependencies": { diff --git a/ui/src/app/layout.tsx b/ui/src/app/layout.tsx index 20f17137..1e4e19d0 100644 --- a/ui/src/app/layout.tsx +++ b/ui/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from 'next/font/google'; import './globals.css'; import Sidebar from '@/components/Sidebar'; import { ThemeProvider } from '@/components/ThemeProvider'; +import ConfirmModal from '@/components/ConfirmModal'; const inter = Inter({ subsets: ['latin'] }); @@ -21,6 +22,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
+ ); diff --git a/ui/src/components/Card.tsx b/ui/src/components/Card.tsx index c9a15feb..1942d81e 100644 --- a/ui/src/components/Card.tsx +++ b/ui/src/components/Card.tsx @@ -5,8 +5,8 @@ interface CardProps { const Card: React.FC = ({ title, children }) => { return ( -
- {title &&

{title}

} +
+ {title &&

{title}

} {children ? children : null}
); diff --git a/ui/src/components/ConfirmModal.tsx b/ui/src/components/ConfirmModal.tsx new file mode 100644 index 00000000..2aaa651c --- /dev/null +++ b/ui/src/components/ConfirmModal.tsx @@ -0,0 +1,138 @@ +'use client'; +import { createGlobalState } from 'react-global-hooks'; +import { Dialog, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react' +import { FaExclamationTriangle, FaInfo } from "react-icons/fa"; + + +export interface ConfirmState { + title: string; + message?: string; + confirmText?: string; + type?: 'danger' | 'warning' | 'info'; + onConfirm?: () => void; + onCancel?: () => void; +} + +const confirmstate = createGlobalState(null); + + +export default function ConfirmModal() { + + const [confirm, setConfirm] = confirmstate.use(); + const open = confirm !== null; + + const onCancel = () => { + if (confirm?.onCancel) { + confirm.onCancel(); + } + setConfirm(null); + }; + + const onConfirm = () => { + if (confirm?.onConfirm) { + confirm.onConfirm(); + } + setConfirm(null); + }; + + let Icon = FaExclamationTriangle; + let color = confirm?.type || 'danger'; + + // Use conditional rendering for icon + if (color === 'info') { + Icon = FaInfo; + } + + // Color mapping for background colors + const getBgColor = () => { + switch (color) { + case 'danger': return 'bg-red-500'; + case 'warning': return 'bg-yellow-500'; + case 'info': return 'bg-blue-500'; + default: return 'bg-red-500'; + } + }; + + // Color mapping for text colors + const getTextColor = () => { + switch (color) { + case 'danger': return 'text-red-950'; + case 'warning': return 'text-yellow-950'; + case 'info': return 'text-blue-950'; + default: return 'text-red-950'; + } + }; + + // Color mapping for titles + const getTitleColor = () => { + switch (color) { + case 'danger': return 'text-red-500'; + case 'warning': return 'text-yellow-500'; + case 'info': return 'text-blue-500'; + default: return 'text-red-500'; + } + }; + + // Button background color mapping + const getButtonBgColor = () => { + switch (color) { + case 'danger': return 'bg-red-700 hover:bg-red-500'; + case 'warning': return 'bg-yellow-700 hover:bg-yellow-500'; + case 'info': return 'bg-blue-700 hover:bg-blue-500'; + default: return 'bg-red-700 hover:bg-red-500'; + } + }; + + return ( + + + +
+
+ +
+
+
+
+
+ + {confirm?.title} + +
+

+ {confirm?.message} +

+
+
+
+
+
+ + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/ui/src/components/formInputs.tsx b/ui/src/components/formInputs.tsx index 2da85fad..5b4c3132 100644 --- a/ui/src/components/formInputs.tsx +++ b/ui/src/components/formInputs.tsx @@ -1,9 +1,9 @@ import React from 'react'; import classNames from 'classnames'; -const labelClasses = 'block text-sm mb-2 text-gray-300'; +const labelClasses = 'block text-xs mb-1 mt-2 text-gray-300'; const inputClasses = - 'w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-gray-600 focus:border-transparent'; + 'w-full text-sm px-3 py-1 bg-gray-800 border border-gray-700 rounded-sm focus:ring-2 focus:ring-gray-600 focus:border-transparent'; export interface InputProps { label?: string;