131 lines
5.0 KiB
TypeScript
131 lines
5.0 KiB
TypeScript
|
|
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
import { type Service } from '../types';
|
|
import { generateServiceDescription } from '../services/geminiService';
|
|
import { Globe, Loader2 } from 'lucide-react';
|
|
|
|
interface AddServiceModalProps {
|
|
onClose: () => void;
|
|
onAddService: (service: Omit<Service, 'id'>) => void;
|
|
}
|
|
|
|
const AddServiceModal: React.FC<AddServiceModalProps> = ({ onClose, onAddService }) => {
|
|
const [name, setName] = useState('');
|
|
const [url, setUrl] = useState('');
|
|
const [description, setDescription] = useState('');
|
|
const [isFetchingDesc, setIsFetchingDesc] = useState(false);
|
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
|
onClose();
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, [onClose]);
|
|
|
|
const handleFetchDescription = useCallback(async () => {
|
|
if (name.trim().length > 2 && process.env.API_KEY) {
|
|
setIsFetchingDesc(true);
|
|
const desc = await generateServiceDescription(name);
|
|
setDescription(desc);
|
|
setIsFetchingDesc(false);
|
|
}
|
|
}, [name]);
|
|
|
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
if (name && url) {
|
|
onAddService({
|
|
name,
|
|
url,
|
|
description: description || `A local service at ${url}`,
|
|
icon: Globe,
|
|
});
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-70 z-50 flex justify-center items-center p-4">
|
|
<div ref={modalRef} className="bg-gray-800 rounded-lg shadow-xl w-full max-w-md p-6 border border-gray-700">
|
|
<h2 className="text-2xl font-bold mb-6 text-white">Add a New Service</h2>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label htmlFor="name" className="block text-sm font-medium text-gray-300">
|
|
Service Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
onBlur={handleFetchDescription}
|
|
className="mt-1 block w-full bg-gray-900 border border-gray-600 rounded-md shadow-sm py-2 px-3 text-white focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm"
|
|
placeholder="e.g., Home Assistant"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="url" className="block text-sm font-medium text-gray-300">
|
|
URL (including http/https)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="url"
|
|
value={url}
|
|
onChange={(e) => setUrl(e.target.value)}
|
|
className="mt-1 block w-full bg-gray-900 border border-gray-600 rounded-md shadow-sm py-2 px-3 text-white focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm"
|
|
placeholder="http://192.168.1.100:8123"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label htmlFor="description" className="block text-sm font-medium text-gray-300">
|
|
Description
|
|
</label>
|
|
<div className="relative">
|
|
<textarea
|
|
id="description"
|
|
rows={3}
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
className="mt-1 block w-full bg-gray-900 border border-gray-600 rounded-md shadow-sm py-2 px-3 text-white focus:outline-none focus:ring-teal-500 focus:border-teal-500 sm:text-sm"
|
|
placeholder="A short description of the service..."
|
|
/>
|
|
{isFetchingDesc && (
|
|
<div className="absolute inset-y-0 right-3 flex items-center">
|
|
<Loader2 className="h-5 w-5 text-teal-400 animate-spin" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="mt-8 flex justify-end space-x-3">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="px-4 py-2 text-sm font-medium text-gray-300 bg-gray-700 rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-gray-500"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-4 py-2 text-sm font-medium text-white bg-teal-500 rounded-md shadow-sm hover:bg-teal-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-teal-500"
|
|
>
|
|
Add Service
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AddServiceModal;
|