import { useEffect, useState, useCallback } from 'react'; import { Dashboard } from '@/components/layout'; import { ThermostatOverlay } from '@/components/climate'; import { LightsOverlay } from '@/components/lights'; import { LocksOverlay } from '@/components/locks'; import { ControlsOverlay } from '@/components/controls'; import { CalendarWidget } from '@/components/calendar'; import { TodoWidget } from '@/components/todo'; import { ChoreChart } from '@/components/chores'; import { SettingsPanel, ConnectionModal } from '@/components/settings'; import { CameraOverlay } from '@/components/cameras'; import { CameraFeed } from '@/components/cameras/CameraFeed'; import { JellyfinOverlay } from '@/components/media'; import { GlobalKeyboard } from '@/components/keyboard'; import { PhotoFrame } from '@/components/photoframe'; import { useHomeAssistant } from '@/hooks'; import { useIdle } from '@/hooks/useIdle'; import { useHAStore } from '@/stores/haStore'; import { useUIStore, useCameraOverlay } from '@/stores/uiStore'; import { useSettingsStore } from '@/stores/settingsStore'; import { env } from '@/config/environment'; // Person detection alert overlay - shows for 30 seconds when person detected on any configured camera function PersonAlert({ cameraName, onClose }: { cameraName: string; onClose: () => void }) { const cameras = useSettingsStore((state) => state.config.cameras); const cameraLower = cameraName.toLowerCase(); const camera = cameras.find( (c) => c.frigateCamera?.toLowerCase() === cameraLower || c.name.toLowerCase() === cameraLower, ); useEffect(() => { const timer = setTimeout(onClose, 30000); return () => clearTimeout(timer); }, [onClose]); if (!camera) return null; return (

Person Detected - {camera.displayName}

); } function ConnectionPrompt() { const openSettings = useUIStore((state) => state.openSettings); return (

Imperial Command Center

Enter your Home Assistant long-lived access token to connect.

); } function DashboardContent() { const config = useSettingsStore((state) => state.config); return (
{config.calendar && (
)}
{config.todoList && (
)}
); } export default function App() { const { connectionState } = useHomeAssistant(); const accessToken = useHAStore((state) => state.accessToken); const connect = useHAStore((state) => state.connect); const settingsOpen = useUIStore((state) => state.settingsOpen); const lightsOverlayOpen = useUIStore((state) => state.lightsOverlayOpen); const locksOverlayOpen = useUIStore((state) => state.locksOverlayOpen); const thermostatsOverlayOpen = useUIStore((state) => state.thermostatsOverlayOpen); const mediaOverlayOpen = useUIStore((state) => state.mediaOverlayOpen); const controlsOverlayOpen = useUIStore((state) => state.controlsOverlayOpen); const { isOpen: cameraOverlayOpen } = useCameraOverlay(); const isIdle = useIdle(env.photoFrameIdleTimeout); // Person detection alert state (via MQTT from Electron main process) const [alertCamera, setAlertCamera] = useState(null); // Report touch/click activity to main process for screen wake on Wayland useEffect(() => { const handleActivity = () => { if (window.electronAPI?.screen?.activity) { window.electronAPI.screen.activity(); } }; // Listen for any touch or click events document.addEventListener('touchstart', handleActivity, { passive: true }); document.addEventListener('mousedown', handleActivity, { passive: true }); return () => { document.removeEventListener('touchstart', handleActivity); document.removeEventListener('mousedown', handleActivity); }; }, []); // Auto-connect if token is stored (check localStorage first, then config file) useEffect(() => { const initConfig = async () => { // Load HA token let storedToken = localStorage.getItem('ha_access_token'); // If no token in localStorage, try to get from config file if (!storedToken && window.electronAPI?.config?.getStoredToken) { const fileToken = await window.electronAPI.config.getStoredToken(); if (fileToken) { storedToken = fileToken; localStorage.setItem('ha_access_token', fileToken); } } if (storedToken && !accessToken) { connect(storedToken); } // Load Jellyfin API key from config file if (window.electronAPI?.config?.getJellyfinApiKey) { const jellyfinKey = await window.electronAPI.config.getJellyfinApiKey(); if (jellyfinKey) { useSettingsStore.getState().setJellyfinApiKey(jellyfinKey); } } }; initConfig(); }, [accessToken, connect]); // Listen for person detection via MQTT (from Electron main process) useEffect(() => { const api = window.electronAPI; if (!api?.frigate?.onPersonDetected) return; const unsub = api.frigate.onPersonDetected((camera: string) => { setAlertCamera(camera); useUIStore.getState().setIdle(false); }); return unsub; }, []); const closePersonAlert = useCallback(() => { setAlertCamera(null); }, []); // Set up screen idle timeout useEffect(() => { if (window.electronAPI) { window.electronAPI.screen.setIdleTimeout(env.screenIdleTimeout); } }, []); // Show connection prompt if no token if (!accessToken) { return ( <> {settingsOpen && } ); } // Show loading state if (connectionState === 'connecting') { return (

Connecting to Home Assistant...

); } // Show error state if (connectionState === 'error') { return (

Connection Error

Failed to connect to Home Assistant. Please check your configuration.

); } return ( <> {lightsOverlayOpen && } {locksOverlayOpen && } {thermostatsOverlayOpen && } {mediaOverlayOpen && } {controlsOverlayOpen && } {cameraOverlayOpen && } {settingsOpen && } {isIdle && !alertCamera && } {alertCamera && } ); }