Initial commit

This commit is contained in:
2025-09-14 13:09:39 -07:00
commit 9dcc0868de
20 changed files with 956 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
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;